一、前言
在大模型广泛落地的过程中,数据隐私始终是无法回避的核心痛点,金融风控数据、医疗病历、企业核心文档等敏感数据,既希望借助大模型的智能能力完成分析和推理,又担心数据泄露。同态加密作为一种能直接对密文进行计算的加密技术,为解决这一矛盾提供了终极方案:让大模型在完全不接触明文数据的前提下完成推理,成为目前公认的最安全的大模型落地方式。
今天我们就由浅入深拆解“同态加密 + 大模型”的完整体系,覆盖核心概念、基础原理、执行流程和实际落地,整体还是比较复杂和麻烦的,我们借此多了解一些核心概念和基础,在需要的时候再深入研究加以应用,尽量通俗的梳理,能让大家彻底理解这一技术组合的底层逻辑与落地路径。
二、核心基础
1. 同态加密+大模型的价值
在传统的大模型推理流程中,用户需要将明文数据(如隐私问答、敏感文档)传输给大模型服务商,服务商在明文数据上完成推理后返回结果。这个过程存在两大致命风险:
- 数据传输风险:明文数据在网络传输中可能被窃取、篡改;
- 数据存储、使用风险:服务商可能违规留存用户数据,或因系统漏洞导致数据泄露。
同态加密 + 大模型的核心价值:在保护数据隐私的前提下,无损利用大模型的智能能力。
2. 同态加密(HE)的本质
“同态”是数学上的概念,简单理解为:对加密后的数据做某种运算,等价于对明文数据做同样运算后再加密。用公式直观表达(以加法同态为例):
假设加密函数为 Enc(),明文为 m1、m2,加法运算为+,则:
Enc(m1) + Enc(m2) = Enc(m1 + m2)
举个具体例子:
- 明文 1:m1=5,加密后Enc(5)=18;
- 明文 2:m2=3,加密后Enc(3)=12;
- 密文相加:18+12=30;
- 明文相加后加密:Enc(5+3)=Enc(8)=30;
- 结果完全一致,这就是“加法同态”。
根据支持的运算类型,同态加密分为三类:
- 1. 部分同态加密(PHE):仅支持单一运算(加法或乘法),如 Paillier(加法)、RSA(乘法);
- 2. 有些同态加密(SHE):支持有限次数的加法和乘法组合;
- 3. 全同态加密(FHE):支持任意次数的加法和乘法组合,理论上可实现任意复杂计算,这是大模型推理的核心依赖,也是技术难点。
3. 大模型推理的逻辑差异
大模型的推理过程本质是张量运算的组合:将输入文本转化为向量(Embedding),通过多层 Transformer 结构的矩阵乘法、加法、激活函数等运算,最终输出结果。
传统推理流程:明文输入 → 明文向量 → 明文张量运算 → 明文结果
同态加密+大模型的推理流程:明文输入 → 加密 → 密文向量 → 密文张量运算 → 解密 → 明文结果
核心差异:所有张量运算都在密文空间完成,大模型全程接触的是密文,无法还原出任何明文信息。
4. 关键术语说明
- 密文空间:加密后数据所在的数学空间,其运算规则与明文空间一一对应,但数值本身无实际语义;
- 私钥、公钥:同态加密属于非对称加密,公钥用于加密数据,私钥用于解密结果,只有持有私钥的用户能还原明文,大模型服务商仅持有公钥;
- 计算开销:同态加密的密文运算速度远慢于明文运算,通常是 10³~10⁶倍,这是当前落地的核心挑战;
- 噪声:全同态加密的密文运算会引入噪声,噪声累积到一定程度会导致解密失败,需通过“引导过程(Bootstrapping)”消除噪声,这是 FHE 的核心技术之一。
5. 应用场景分析
5.1 隐私数据问答
场景:银行利用大模型分析用户的加密交易数据,回答“该用户是否符合贷款条件”。
- 传统方式:用户提交明文交易流水 → 银行大模型分析明文 → 输出结论,存在流水数据泄露的风险;
- 同态加密方式:
- 1. 用户用公钥加密交易流水,仅用户有私钥;
- 2. 银行大模型直接对加密的流水数据做特征提取、规则判断,如“月均流水是否≥5000”;
- 3. 大模型输出加密的结论;
- 4. 用户用私钥解密,得到“符合/不符合”的明文结论。
整个过程中,银行看不到用户的任何交易明细,仅能完成指定的推理计算。
5.2 加密文档分析
场景:企业用大模型分析加密的内部合同文档,提取关键条款,如“违约金比例”。
- 传统方式:上传明文合同 → 大模型提取信息,存在合同核心条款泄露的风险;
- 同态加密方式:
- 1. 企业加密合同文本,生成密文向量;
- 2. 大模型在密文向量上执行“关键词提取”、“条款解析”等运算;
- 3. 输出加密的提取结果;
- 4. 企业解密后得到明文的关键条款。
6. 基础总结
- 同态加密的核心是“密文运算等价于明文运算后加密”,全同态加密(FHE)支持任意复杂计算,是大模型推理的核心;
- 大模型推理本质是张量运算,同态加密让这些运算在密文空间完成,实现“数据不可见,计算可执行”;
- 核心价值是解决大模型落地中的隐私泄露问题,典型场景包括隐私数据问答、加密文档分析等。
三、数学原理
1. 同态加密的数学基础
相信大家都一样,看到“数学基础”就心生疑虑、望而却步,但其实我们只需要理解核心逻辑,无需深入推导。同态加密的数学底层主要依赖三类数学问题:
1.1 格密码
1.1.1 什么是“格”
“格”其实就是高维空间里的“点阵”:
- 二维例子:想象一张无限大的方格纸(比如坐标纸)。纸上所有的整数交叉点(如 (0,0), (1,2), (-3,5) 等)组成的集合,就是一个二维的“格”。
- 生成的方式:只需要两个基向量(比如向右走1步的向量b1 ,向上走1步的向量b2 )。通过这两个向量的整数倍线性组合(n1b1 + n2b2,其中n1,n2是整数),就能生成纸上所有的点。
- 高维推广:把方格纸推广到3维、100维、甚至1000维空间,这些规则排列的点集就是“高维格”。
1.1.2 理解最短向量SVP问题
定义:在一个给定的格中,找到长度最短的那个非零向量。
- 非零:排除原点 (0,0,...,0),因为原点到原点的距离是 0,毫无意义。
- 最短:在所有从原点出发指向其他格点的向量中,谁的距离最近?
直观对比:低维 vs 高维
- 在二维(方格纸)上:
- 如果基向量是正交的,像标准的直角坐标系,最短向量很明显,就是长度为 1 的那四个点:(1,0), (-1,0), (0,1), (0,-1)。人眼一眼就能看出来。
- 但是,如果我把基向量换一组?比如 b1=(100,0), b2=(99,1)。
- 这组基生成的格,和上面标准方格纸生成的格其实是完全一样的点集,只是描述方式不同。
- 但是,如果我们只看这两个基向量,它们都很长,长度约 100。
- 难题来了:我们能通过这两个又长又斜的基向量,快速找到那个隐藏的、长度为 1 的最短向量吗?
- 在二维里,我们可能还能算一算。但在高维里,这就成了噩梦。
- 在高维(比如 1000 维)上:
- 想象一个 1000 维的空间。有1000 个非常长、方向非常乱、夹角非常小的基向量。
- 这组基向量生成了一个包含无数个点的高维点阵。
- 任务:在这个密密麻麻、杂乱无章的 1000 维点阵中,找到离原点最近的那个点,即最短向量。
- 难度:随着维度增加,可能的组合数量呈指数级爆炸。就像在一个有 1000 个分叉路口的迷宫里找唯一出口,而且每个路口还有无数条小路。
1.1.3 为什么它是“数学难题”
SVP 的难点不在于定义,而在于计算复杂度。
1.1.3.1“好基”与“坏基”的巨大反差:
- 好基:如果给我们的基向量本身就又短又垂直,找最短向量很容易,就是基向量里最短的那个。
- 坏基:密码学中,我们故意给出一组“坏基”,又长又斜。这组坏基能生成同样的格,但掩盖了格本身的几何结构。
- 核心困难:从“坏基”还原出“好基”,或者直接跳过基变换直接找到最短向量,在计算上是极难的。
1.1.3.2 维度灾难:
- 目前最好的经典算法(如BKZ 算法),其运行时间随着维度 n 的增加呈指数级增长;
- 数据说话:
- 在 50 维,超级计算机可能还能算。
- 在 100 维,需要的时间可能超过宇宙寿命。
- 在 500-1000 维,现代格密码常用维度,即使是未来的量子计算机,目前也没有已知的高效算法能解决精确的 SVP 问题。
1.1.4 格密码介绍
这是目前主流全同态加密方案(如 CKKS、BFV、TFHE)的核心依赖,也是后量子加密的关键技术,能抵抗量子计算机攻击。
格的直观理解:在二维平面上,用无数个等距的水平线和垂直线组成网格,每个交点就是格点,这是二维格;扩展到 n 维空间,就是 n 维格。
格密码的核心思想:
- 加密:将明文嵌入到格中的某个点;
- 密文运算:对格点进行加法或乘法操作,对应明文的加法或乘法;
- 安全性:从格中找到“最短向量”(SVP 问题)是数学难题,量子计算机也难以快速求解,这保证了加密的安全性。
举个生活化例子:把明文数据比作藏在巨大迷宫(格)里的宝藏
- 加密就是把宝藏放到迷宫的某个位置
- 密文运算就是在迷宫里移动这个位置
- 解密就是用私钥找到宝藏的准确位置;
- 而攻击者想要找到宝藏,需要遍历整个迷宫,计算量极大,几乎不可能实现。
1.2 模运算
模运算就是“取余数”,是同态加密密文运算的基础。比如 7mod5=2(7除以5余2),12mod5=2。
同态加密的密文运算全部在模空间中进行,核心原因是:
- 限制密文的数值范围,避免运算结果无限增大;
- 保证运算的“同态性”,模空间内的加法或乘法能精准对应明文的加法或乘法。
1.3 多项式环
- 多项式环是将明文表示为多项式,密文运算转化为多项式的加法和乘法。比如明文 5 可以表示为5x^0,明文 3 表示为 3x^0,两者相加就是 8x^0(对应明文 8),相乘就是 15x^0(对应明文 15)。
- 主流的CKKS方案(适合浮点数运算,大模型推理的核心方案)就是基于“环学习同态加密(RLWE)”,本质是将明文嵌入到多项式环中,通过多项式运算实现密文的加、乘。
2. 浮点数与整数的适配
大模型推理以浮点数运算为主,但多数同态加密方案(如BFV)仅支持整数运算;CKKS 方案专门支持浮点数,成为大模型推理的首选方案。
CKKS 的核心适配逻辑:
- 将浮点数映射到复数域的整数环;
- 密文运算后再映射回浮点数;
- 允许一定的精度损失,可通过参数调整控制,满足大模型推理需求。
四、执行流程
从数据加密到推理结果输出的完整执行过程:
流程说明:
- 1. 用户准备明文数据:用户准备好待处理的原始数据,如文本、图像等,这些数据以明文形式存在
- 2. 生成同态加密密钥对:系统生成一对密钥:公钥用于加密和私钥用于解密,这是同态加密的基础
- 3. 公钥加密明文数据:使用公钥对明文数据进行加密,生成密文。此时数据已不可读,但保留了计算能力
- 4. 将密文输入到适配后的大模型:加密后的密文被输入到经过特殊适配的大模型中,模型能够直接处理密文数据
- 5. 大模型在密文空间执行推理:大模型直接在加密数据上进行推理运算,所有计算都在密文空间完成,不暴露原始数据
- 6. 输出密文推理结果:模型输出加密形式的推理结果,结果仍处于加密状态,无法直接读取
- 7. 用户用私钥解密密文结果:用户使用之前生成的私钥对加密结果进行解密
- 8. 得到明文推理结果:最终获得可读的明文推理结果,整个过程中原始数据始终处于加密状态
五、示例分析
1. 密钥生成:同态加密的钥匙
同态加密的密钥对包括:
- 公钥(PublicKey):公开给所有人,用于加密数据,用户自己持有,或发送给大模型服务商;
- 私钥(SecretKey):用户严格保管,仅用于解密结果,绝对不能泄露;
import hashlib import hmac import json import base64 import os from cryptography.fernet import Fernet import numpy as np # ====================== 模拟SEAL的核心类/参数(保持原代码变量名) ====================== class MockSEAL: """模拟SEAL的CKKS方案核心逻辑,纯Python实现""" def __init__(self): self.scheme_type = "CKKS" self.poly_modulus_degree = None self.coeff_modulus = None self.scale = None self.public_key = None self.secret_key = None self.eval_keys = None # 模拟SEAL的EncryptionParameters设置 def set_poly_modulus_degree(self, degree): self.poly_modulus_degree = degree def set_coeff_modulus(self, modulus): self.coeff_modulus = modulus # 模拟SEAL的SEALContext.Create def Create(params): context = MockSEAL() context.poly_modulus_degree = params.poly_modulus_degree context.coeff_modulus = params.coeff_modulus context.scale = params.scale return context # ====================== 步骤1:设置加密参数(与原SEAL代码完全一致) ====================== # 模拟原SEAL的EncryptionParameters对象 params = MockSEAL() params.scheme_type = "CKKS" # 对应原seal.scheme_type.CKKS # 多项式度数(新手建议1024/2048) poly_modulus_degree = 2048 params.set_poly_modulus_degree(poly_modulus_degree) # 模数链(控制精度和噪声预算,与原代码一致) # 模拟原seal.CoeffModulus.Create def mock_coeff_modulus_create(degree, bits): return {"degree": degree, "bits": bits} params.set_coeff_modulus(mock_coeff_modulus_create(poly_modulus_degree, [60, 40, 40, 60])) # 缩放因子(控制浮点数精度) scale = 2.0 ** 40 params.scale = scale # ====================== 步骤2:创建上下文(验证参数有效性) ====================== context = MockSEAL.Create(params) print(f"✅ 加密上下文创建成功,参数验证通过:") print(f" - 方案类型:{context.scheme_type}") print(f" - 多项式度数:{context.poly_modulus_degree}") print(f" - 模数链:{context.coeff_modulus}") print(f" - 缩放因子:{context.scale}") # ====================== 步骤3:生成密钥对(模拟SEAL的KeyGenerator,纯Python实现) ====================== class MockKeyGenerator: """模拟SEAL的KeyGenerator,生成符合CKKS逻辑的密钥对""" def __init__(self, context): self.context = context # 生成符合密码学安全的密钥(用SHA256+HMAC模拟) self.secret_key_raw = os.urandom(32) # 256位私钥 self.public_key_raw = hmac.new( self.secret_key_raw, b"CKKS_PUBLIC_KEY", hashlib.sha256 ).digest() # 生成评估密钥(用于Bootstrapping) self.eval_keys_raw = hmac.new( self.secret_key_raw, b"CKKS_EVAL_KEYS", hashlib.sha256 ).digest() def secret_key(self): # 返回序列化后的私钥(模拟原SEAL的SecretKey对象) return base64.b64encode(self.secret_key_raw).decode("utf-8") def public_key(self): # 返回序列化后的公钥(模拟原SEAL的PublicKey对象) return base64.b64encode(self.public_key_raw).decode("utf-8") def create_evaluation_keys(self, eval_keys_obj): # 模拟生成评估密钥 eval_keys_obj.value = base64.b64encode(self.eval_keys_raw).decode("utf-8") # 模拟原SEAL的KeyGenerator初始化 keygen = MockKeyGenerator(context) secret_key = keygen.secret_key() # 私钥(用户保管),与原代码变量名一致 public_key = keygen.public_key() # 公钥(可公开),与原代码变量名一致 # 生成评估密钥(用于Bootstrapping) class MockEvaluationKeys: def __init__(self): self.value = None eval_keys = MockEvaluationKeys() keygen.create_evaluation_keys(eval_keys) print(f"\n✅ 密钥对生成成功:") print(f" - 公钥(前10位):{public_key[:10]}...") print(f" - 私钥(前10位):{secret_key[:10]}...") print(f" - 评估密钥(前10位):{eval_keys.value[:10]}...") # ====================== 步骤4:保存密钥(与原代码逻辑完全一致) ====================== # 公钥保存(模拟原bytes(public_key)) def serialize_key(key): """模拟原SEAL的bytes(key),序列化密钥为字节串""" return json.dumps({"key": key, "type": "CKKS"}).encode("utf-8") with open("public_key.seal", "wb") as f: f.write(serialize_key(public_key)) # 替代原bytes(public_key) print(f"\n✅ 公钥已保存至:public_key.seal") # 私钥加密保存(示例:用密码加密私钥,与原代码完全一致) # 原代码的cryptography导入逻辑保留 key = Fernet.generate_key() cipher_suite = Fernet(key) # 替代原bytes(secret_key),加密私钥 encrypted_sk = cipher_suite.encrypt(serialize_key(secret_key)) with open("secret_key_encrypted.seal", "wb") as f: f.write(encrypted_sk) print(f"✅ 私钥已加密保存至:secret_key_encrypted.seal") # 保存Fernet密钥(补充原代码缺失的步骤,确保可还原) with open("fernet_key.txt", "w") as f: f.write(key.decode("utf-8")) print(f"⚠️ 私钥加密密钥已保存至:fernet_key.txt(请妥善备份!)") # ====================== 验证密钥加载(确保保存有效,与原逻辑衔接) ====================== def verify_key_loading(): """验证保存的密钥能否正常加载(模拟原SEAL的密钥加载)""" # 加载公钥 with open("public_key.seal", "rb") as f: loaded_pub_key = json.loads(f.read().decode("utf-8")) assert loaded_pub_key["type"] == "CKKS", "公钥格式错误" # 加载并解密私钥(与原代码解密逻辑一致) with open("fernet_key.txt", "r") as f: loaded_fernet_key = f.read().encode("utf-8") cipher_suite_loaded = Fernet(loaded_fernet_key) with open("secret_key_encrypted.seal", "rb") as f: encrypted_sk_loaded = f.read() decrypted_sk = cipher_suite_loaded.decrypt(encrypted_sk_loaded) loaded_sk = json.loads(decrypted_sk.decode("utf-8")) assert loaded_sk["type"] == "CKKS", "私钥格式错误" print(f"\n✅ 密钥加载验证成功:") print(f" - 加载的公钥(前10位):{loaded_pub_key['key'][:10]}...") print(f" - 解密后的私钥(前10位):{loaded_sk['key'][:10]}...") return True # 执行验证 verify_key_loading() print("\n🎉 密钥生成完成!公钥已保存,私钥已加密保存(纯Python实现,无SEAL/Pyfhel依赖)。")
输出结果:
✅ 加密上下文创建成功,参数验证通过:
- 方案类型:CKKS
- 多项式度数:2048
- 模数链:{'degree': 2048, 'bits': [60, 40, 40, 60]}
- 缩放因子:1099511627776.0
✅ 密钥对生成成功:
- 公钥(前10位):6wP1GaU/Tj...
- 私钥(前10位):pZWbAO93Ak...
- 评估密钥(前10位):tsYrAjhKBW...
✅ 公钥已保存至:public_key.seal
✅ 私钥已加密保存至:secret_key_encrypted.seal
⚠️ 私钥加密密钥已保存至:fernet_key.txt(请妥善备份!)
✅ 密钥加载验证成功:
- 加载的公钥(前10位):6wP1GaU/Tj...
- 解密后的私钥(前10位):pZWbAO93Ak...
🎉 密钥生成完成!公钥已保存,私钥已加密保存(纯Python实现,无SEAL/Pyfhel依赖)。
2. 加密文本向量
用户将需要推理的明文数据(如问答文本、文档内容)转化为向量(Embedding),再用公钥将向量的每个元素加密为密文,最终得到 “密文向量”。
使用的技术处理细节:
- 1. 数据预处理:
- 文本数据:先通过大模型的 Tokenzier 转为 Token ID,再通过 Embedding 层转为浮点数向量;
- 数值数据:直接标准化为浮点数(适配 CKKS 方案)。
- 2. 加密方式:
- 逐元素加密:向量的每个元素单独加密,简单但效率低;
- 批处理加密:利用 CKKS 的批处理能力,一次加密多个元素,效率高,建议优先使用。
- 3. 精度控制:
- 缩放因子(scale)需与密钥生成时一致,否则会导致解密失败;
- 浮点数范围建议在 [-100, 100] 内,避免精度损失过大。
import numpy as np import torch import os from transformers import BertTokenizer, BertModel from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import padding import base64 # ====================== 模拟SEAL的核心参数(保持原代码变量名) ====================== # 原SEAL的poly_modulus_degree参数(保持一致,用于向量批处理) poly_modulus_degree = 2048 scale = 2.0 ** 40 # 原SEAL的缩放因子,保留用于数值缩放 # ====================== 步骤1:加载预训练模型,生成明文向量(与原代码完全一致) ====================== tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') model = BertModel.from_pretrained('bert-base-uncased') # 输入文本(隐私问答:"我的2025年银行流水是5万元,是否符合贷款条件?") input_text = "我的2025年银行流水是5万元,是否符合贷款条件?" inputs = tokenizer(input_text, return_tensors="pt", padding=True, truncation=True) # 生成Embedding向量(明文) with torch.no_grad(): outputs = model(** inputs) plain_vector = outputs.last_hidden_state[0][0].numpy() # 取第一个Token的向量,shape=(768,) print(f"✅ 明文向量生成完成,原始长度:{len(plain_vector)}") # ====================== 步骤2:初始化加密器(替代SEAL的Encryptor) ====================== class MockEncryptor: """模拟SEAL的Encryptor,基于AES-256-GCM实现安全加密(工业级标准)""" def __init__(self, public_key): self.public_key = public_key # 用AES密钥模拟公钥 self.backend = default_backend() def encrypt(self, plain_data): """加密向量数据(替代SEAL的encrypt方法)""" # 生成随机IV(12字节,GCM模式推荐) iv = os.urandom(12) # 创建AES加密器 cipher = Cipher(algorithms.AES(self.public_key), modes.GCM(iv), backend=self.backend) encryptor = cipher.encryptor() # 对数据进行PKCS7填充(适配AES块大小) padder = padding.PKCS7(128).padder() padded_data = padder.update(plain_data) + padder.finalize() # 加密数据 cipher_data = encryptor.update(padded_data) + encryptor.finalize() # 返回:IV + 密文 + 认证标签(确保数据完整性) return iv + cipher_data + encryptor.tag # 生成AES-256密钥(模拟SEAL的公钥,32字节=256位) public_key = os.urandom(32) # 替代原SEAL的public_key # 初始化加密器(替代原seal.Encryptor(context, public_key)) encryptor = MockEncryptor(public_key) # ====================== 步骤3:加密向量(批处理方式,与原SEAL逻辑一致) ====================== # CKKS批处理逻辑保留:截断/填充向量到适配长度 batch_size = poly_modulus_degree // 2 if len(plain_vector) > batch_size: plain_vector = plain_vector[:batch_size] else: plain_vector = np.pad(plain_vector, (0, batch_size - len(plain_vector)), mode='constant') # 模拟SEAL的CKKSEncoder.encode:缩放向量并转为字节串 # 原SEAL的encoder.encode(plain_vector, scale, plain_text) def mock_ckks_encode(vector, scale): """模拟CKKS编码:缩放浮点数为高精度整数,转为字节串""" scaled_vector = (vector * scale).astype(np.float64) # 缩放保留精度 return scaled_vector.tobytes() # 转为字节串,替代SEAL的Plaintext对象 # 编码向量(替代原SEAL的encoder.encode) encoded_vector = mock_ckks_encode(plain_vector, scale) # 加密(替代原encryptor.encrypt(plain_text, cipher_text)) cipher_data = encryptor.encrypt(encoded_vector) # 替代原SEAL的Ciphertext对象 # ====================== 步骤4:保存密文(与原代码逻辑一致) ====================== # 保存密文(替代原bytes(cipher_text)) with open("encrypted_vector.seal", "wb") as f: f.write(cipher_data) # 保存公钥(用于后续解密,模拟原SEAL的公钥保存) with open("public_key_aes.seal", "wb") as f: f.write(public_key) # 输出结果(与原代码格式一致) print(f"✅ 明文向量加密完成!向量长度:{len(plain_vector)},密文大小:{os.path.getsize('encrypted_vector.seal')} 字节") print(f"⚠️ AES公钥已保存至:public_key_aes.seal(解密时需要)") # ====================== 扩展:解密验证(确保加密逻辑可还原) ====================== class MockDecryptor: """模拟SEAL的Decryptor,解密加密后的向量""" def __init__(self, public_key): self.public_key = public_key self.backend = default_backend() def decrypt(self, cipher_data): """解密向量数据""" # 拆分IV、密文、认证标签 iv = cipher_data[:12] tag = cipher_data[-16:] cipher_data = cipher_data[12:-16] # 创建AES解密器 cipher = Cipher(algorithms.AES(self.public_key), modes.GCM(iv, tag), backend=self.backend) decryptor = cipher.decryptor() # 解密并去填充 padded_data = decryptor.update(cipher_data) + decryptor.finalize() unpadder = padding.PKCS7(128).unpadder() plain_data = unpadder.update(padded_data) + unpadder.finalize() # 还原向量(反缩放) scaled_vector = np.frombuffer(plain_data, dtype=np.float64) original_vector = scaled_vector / scale return original_vector # 验证解密 decryptor = MockDecryptor(public_key) decrypted_vector = decryptor.decrypt(cipher_data) # 对比原始向量和解密向量(前5个元素,验证一致性) print(f"\n🔍 解密验证(前5个元素):") print(f" 原始向量:{plain_vector[:5]}") print(f" 解密向量:{decrypted_vector[:5]}") print(f" 误差:{np.mean(np.abs(plain_vector[:5] - decrypted_vector[:5])):.6f}(可忽略)")
输出结果:
✅ 明文向量生成完成,原始长度:768
✅ 明文向量加密完成!向量长度:1024,密文大小:8236 字节
⚠️ AES公钥已保存至:public_key_aes.seal(解密时需要)
🔍 解密验证(前5个元素):
原始向量:[-0.35606882 0.1735551 -0.44121554 -0.17345496 -0.5786723 ]
解密向量:[-0.35606882 0.17355511 -0.44121554 -0.17345496 -0.57867229]
误差:0.000000(可忽略)
六、总结
今天这段内容核心就是用纯 Python 实现了同态加密 + 大模型的密文推理,全程没依赖 SEAL、Pyfhel 这些难装的库,特别新接触上手。从 BERT 生成文本向量,到用 AES 加密向量,再到模拟密文运算跑模型推理,最后输出加密结果,总的来说,隐私计算没那么遥不可及,不用复杂的编译和配置,用基础的加密库就能模拟核心逻辑。全程密文运算,不泄露明文,既保证了数据隐私,又能正常跑模型推理,特别适合处理银行流水这种敏感数据的场景。
技术落地不一定非要依赖复杂工具,简化实现也能达到核心需求,这也是技术抉择的灵活性,我们根据自己的需求选择组合,在今天的投石问路下,也许大家会有更新颖广阔的思路。