4. 密钥生成与加密流程:公钥、私钥、评估密钥的生成,明文空间的编码与加密操作
好,我们正式进入同态加密的“发动机舱”。
上一章我们聊了数学基础——多项式环、噪声、密钥交换。这一章,我们把这些概念拧成一股绳,看看一套可用的同态加密方案到底是怎么跑起来的。说白了,就是 把数学公式变成能用的钥匙和锁。
我个人习惯把密钥生成比作“配钥匙”。不是随便配一把,而是配三把:一把公开的、一把私藏的、一把专门干活的。
4.1 密钥结构:三把钥匙的逻辑
在主流方案(比如 BGV、BFV、CKKS)里,密钥不是只有一对。你至少需要这三样东西:
- 公钥 (pk):用来加密。谁都能拿,相当于一个上了锁的投递口。
- 私钥 (sk):用来解密。只有你持有,相当于那把唯一的物理钥匙。
- 评估密钥 (evk):用来运算。也是公开的,但只能用来做同态计算,不能解密。我管它叫“算盘钥匙”。
为什么要单独拎一个评估密钥出来?我在项目中遇到过这样的坑:刚开始做原型时,我把公钥直接扔给服务器做运算,结果发现 密文尺寸膨胀得离谱。后来才意识到,评估密钥是专门给服务器做“密钥交换”和“重线性化”用的,不参与加密。这就像给装修师傅一把“工具钥匙”——能开门进去干活,但打不开你的保险柜。
4.2 密钥生成流程:一步步拆解
我们拿 CKKS 方案举个例子,它用在浮点数近似计算上,很适合日志聚合的场景。流程其实挺直白的:
- 选参数:确定多项式阶数 N(一般是 215 到 217)、模数 q(决定了能承载的噪声天花板)。
- 采样:从多项式环 Rq 上随机生成一个秘密多项式 s,这就是私钥。
- 生成公钥:随机选一个 a 和一个小的噪声 e。计算 b = -a·s + e。公钥就是 (a, b)。
- 生成评估密钥:对私钥的平方 s2 进行加密(用另一个辅助模数)。这个过程稍微复杂,但你可以理解为:给“乘法运算”配了一把临时钥匙。
嗯,这里要注意:公钥 (a, b) 本质上是对私钥的一层“遮盖”。攻击者看到了 a 和 b,想反推出 s,就需要解 RLWE 问题。而这个问题的难度,我们之前说过——非常靠谱。
密钥生成与加密流程总览下图展示了从参数输入到密文输出的完整路径:
4.3 明文空间的编码与加密操作
你想想看,同态加密不能直接加密一个浮点数,比如 3.1415926。为什么?因为加密是在多项式环上操作的,你得先把数组或实数 映射 成多项式。这个映射过程就叫编码。
在 CKKS 方案里,我们用了一种很巧妙的方法:
- 编码:把复数向量(比如日志中的时间戳、温度值)做一次逆傅里叶变换(iDFT),再缩放一个缩放因子 Δ,变成一个整系数多项式。
- 加密:用公钥把这个多项式“包”起来,加上随机噪声,产生两个多项式 (ct0, ct1)。
代码实现上(伪代码,你要是用开源库,比如 SEAL,流程一模一样):
// 编码
Plaintext encode(vector<double> values, double scale) {
// 1. iDFT 变换
Polynomial p = inverse_fft(values);
// 2. 缩放成整数
p = p * scale;
return p;
}
// 加密
Ciphertext encrypt(PublicKey pk, Plaintext p, double noise) {
// 1. 采样随机多项式 u、噪声 e0、e1
Polynomial u = sample_uniform();
Polynomial e0 = sample_small_noise();
Polynomial e1 = sample_small_noise();
// 2. 计算密文 (ct0, ct1)
Ciphertext ct;
ct.ct0 = pk.b * u + e0 + p; // 明文在这里被“埋”进去
ct.ct1 = pk.a * u + e1;
return ct;
}
ct0 + ct1 * sk = (b*u + e0 + p) + (a*u + e1) * sk代入
b = -a·sk + e,你会发现大多数项互相抵消,只剩下 p + (e0 + e1·sk + e·u)。只要噪声够小,就能把原始明文 p 分离出来。在同态运算过程中,噪声随着乘法逐渐变大,所以我们要在它爆炸前用评估密钥 “降噪”。
4.4 三种密钥的存储与使用建议
在实际项目中,密钥管理往往比加密逻辑更容易出问题。我列了一个表格,你可以在部署时参考:
| 密钥类型 | 存储位置 | 分发范围 | 补,数据量提醒 |
|---|---|---|---|
| 私钥 (sk) | 本地安全硬件或机密计算环境 | 永远不外发 | 极小,通常只需几十KB |
| 公钥 (pk) | 公开数据库或配置中心 | 所有数据提供方 | 较大,约1-2MB |
| 评估密钥 (evk) | 计算服务器本地磁盘 | 仅限负责计算的节点 | 最大,可达数百MB |
我建议你在生产环境里,把私钥放在 TEE(信任执行环境) 或 HSM(硬件安全模块) 里。公钥和评估密钥可以交给配置中心分发,但要注意 完整性校验,防止中间人篡改。
4.5 一个完整的实例:加密一条日志
假设我们要加密一条日志“温度传感器 A 读数为 36.5”,过程是这样的:
- 编码:把数组 [36.5] 缩放 240 倍,变成整数多项式。
- 加密:用公钥生成密文,加上噪声。输出两个大数多项式。
- 存储:把这两个多项式序列化存储(比如 Protobuf),然后发到日志服务器。
你看,服务器看到的只是一堆杂乱的大数,但它在需要时可以直接对这些密文做聚合查询,完全不需要解密。这就是同态加密的魔力——密文等同于数据的使用权,而不只是可见权。
scale,你记得保持一致。我见过同事不小心在不同批次用了不同 scale,结果聚合时解密全乱了。这个问题排查起来非常烧脑,因为错误只会在计算链的最后一步暴露。