SEAL (Simple Encrypted Arithmetic Library) 是微软开源的一个同态加密软件库。简单来说,它是一套工具,让开发者可以在不解密数据的情况下,直接对加密数据进行分析和计算。

示例 1: BFV 基础 (1_bfv_basics.cpp) - 加密整数运算


https://github.com/microsoft/SEAL/blob/main/native/examples/1_bfv_basics.cpp

BFV 是一种主流的全同态加密方案,其核心特点是在加密数据上支持精确的整数运算

加密计算的全过程流程图:

  1. 参数设置:配置了多项式次数、系数模数、明文模数。

  2. 密钥与工具生成:创建了公私钥、加密器、计算器、解密器。

  3. 朴素计算路径

    • 计算 x²+1 和 (x+1)²,观察到密文膨胀、噪声消耗。

    • 执行最终乘法,观察到噪声预算归零。

    • 解密失败,得到错误结果。

  4. 重线性化优化路径

    • 生成重线性化密钥。

    • 每做一次乘法,立刻用重线性化把密文大小从3压回2。

    • 用压缩后的标准尺寸密文执行最终乘法。

    • 噪声预算充足,解密成功,得到正确结果 0x54

  5. 无效参数演示:故意改错参数,展示如何查看错误信息。

这种方法存在两个显著问题:

  1. 实际应用通常使用整数或实数运算,而非模算术运算;

  2. 我们只使用了明文多项式中的一个系数。这非常浪费,因为明文多项式本身很大,而且无论如何都会被完整加密。

注:噪声预算”是衡量“故意引入的随机数(噪声)”还能增加多少而不导致解密失败的一个安全指标。每次同态运算(尤其是乘法)都会让噪声增大。加法的噪声增长是线性的,而乘法的噪声增长是乘性的,会让噪声急剧膨胀。当总噪声大到超过一个绝对阈值(由加密参数决定)时,它就彻底淹没了原始数据。此时,噪声预算就归零了。这时再去解密,解密算法会试图移除一层固定的噪声,但因为总噪声已经过大,移除后剩下的就是一个被严重破坏的、随机的错误结果。结论:噪声预算归零,意味着噪声已经大到必然导致解密失败。

示例 2: 编码器 (2_encoders.cpp) - 编码复杂数据


https://github.com/microsoft/SEAL/blob/main/native/examples/2_encoders.cpp

BatchEncoder (BFV方案批处理)

  1. 参数设置:配置了多项式次数、系数模数,并使用 PlainModulus::Batching 生成一个特殊的素数作为明文模数,以开启批处理功能

  2. 数据准备与编码:将待处理数据视为一个 2 × (N/2) 矩阵,展平为向量用 BatchEncoder 将其打包编码为一个明文多项式。

  3. 同态运算:对密文进行加、乘等操作,效果会自动作用在矩阵的每一个元素上,实现单指令多数据流并行。

  4. 解码与验证:解密后,用 BatchEncoder 将明文多项式解码回向量,验证所有元素的计算结果。

BatchEncoder批处理器:它的作用是把一个包含几千个数字的向量(数组),打包编码成一个能放进密文箱子的明文多项式。反过来,它也能把解密后的明文多项式解码还原成那个向量。它就像一个数据打包和拆包的流水线机器。

CKKSEncoder (CKKS方案实数编码)

  1. 参数设置:配置了多项式次数、系数模数,但没有明文模数,因为它处理的是近似的小数,而不是对某个模数取余的整数。这是CKKS与BFV的根本区别。
  2. 数据准备与编码:准备一个浮点数向量,并设定一个关键参数 scale(缩放因子,如2^30)。CKKSEncoder 会将所有小数乘以 scale 放大为整数,再编码为明文。
  3. 同态运算与Scale变化:对密文进行乘法时,其内部的 scale 值会随之倍增(如平方后从 2^30 变成 2^60)。这是CKKS方案需要管理的核心特性。
  4. 解码与验证:解密后,CKKSEncoder 利用当前的 scale 值将整数缩小,解码得到近似的实数结果。

示例 3: 级别 (3_levels.cpp) - 理解密文级别


https://github.com/microsoft/SEAL/blob/main/native/examples/3_levels.cpp

1. 概念建立:通过手动指定 coeff_modulus 为一串素数{50, 30, 30, 50, 50},创造了“模数切换链”这个楼层结构。

2. 结构可视化:遍历并打印了整个链条,看到了密钥在顶层,数据从下一层开始。

3. 规则验证:通过打印密钥对象id和密文对象id与层级id,验证了密钥对象和密文对象各自身处不同的固定楼层。

4. 核心操作:演示了 mod_switch_to_next 如何把密文逐层下搬,并观察到随意切换会损失大量噪声预算,但是解密后发现结果正确,这说明模数切换本身没有破坏数据。

5. 正确用法:在计算阶段之间进行模数切换,切换后的噪声预算和切换前一样,同时为后续计算“瘦身”提速。

6. 可选配置:SEALContext(parms, false),通过 false 参数禁用长链,SEAL只创建必不可少的最少层级密钥层 (Key Level)和最高数据层 (First Data Level),以适应不需要频繁切换的场景。

注:模数切换是一种将密文参数沿链向下改变的技术,在完成一个计算阶段、噪声预算消耗自然结束时,再进行切换,就不会额外损失预算(在完成一个计算阶段后,噪声预算通常就是接近被消耗到上限的时候)那么在无损失的前提下,降低密文级别,可以缩减密文大小,提升后续运算速度。这是一种“瘦身”优化。

同态运算(尤其是乘法)的复杂度,和密文多项式系数的位数直接相关。位数越小,计算越快。切换到底层后,系数的模数变小了,所以后续的乘法等操作就会更快

示例 4: BGV 基础 (4_bgv_basics.cpp)


https://github.com/microsoft/SEAL/blob/main/native/examples/4_bgv_basics.cpp

1. 参数设置:配置了多项式次数(8192)、系数模数(默认自动推荐),并设置了明文模数以开启批处理。最关键的是,方案类型指定为 bgv

2. 密钥与工具生成:创建了公私钥、加密器、计算器、解密器,以及重线性化密钥。与BFV流程完全一致。

3. 数据编码与加密:使用 BatchEncoder 将整数矩阵(如 [1, 2, 3, 4])打包编码为明文,然后加密成密文。流程与BFV的批处理完全一致。

4. BGV核心计算循环:对于每一次乘法(如计算平方),严格执行一个固定的三步操作组合:

  • 乘法:执行密文乘法(如平方),密文大小膨胀。
  • 重线性化:立即压缩密文大小,从3降回2。
  • 模数切换:紧接着执行 mod_switch_to_next,将密文降至更低的模数级别。这是BGV控制噪声、保证深度计算可行的关键步骤,是必需操作而非可选优化。

4. 解密与解码:计算完成后,解密获得明文多项式,再使用 BatchEncoder 解码,恢复出最终的结果矩阵。计算结果正确(没有使用模数切换噪声预算在x^8后noise budget=0,得到结果是错误的)。

结果输出noise budget对比:

注:通过每次乘法后强制进行模数切换,主动、持续地重置噪声增长率,虽然让单次噪声预算看起来更少,但换来了完成更深度计算的能力。要在实际应用中达到最优的噪声预算消耗速率,需要仔细选择插入模数切换的位置,并手动挑选 coeff_modulus。

示例 5: CKKS 基础 (5_ckks_basics.cpp) - 加密实数运算(最常用)


https://github.com/microsoft/SEAL/blob/main/native/examples/5_ckks_basics.cpp

1. 参数设置:搭配 coeff_modulus 的素数序列{ 60, 40, 40, 60 }和初始 scale(2^40),为重缩放奠定基础。

2. 标准运算循环:对于每次乘法,执行 乘法 -> 重线性化 -> 重缩放(模数切换) ,以稳定缩放因子。每次乘法会导致scale扩大,需重缩放降低scale,而模数切换会使密文级别下降,并且,每次重缩放除以的素数是“接近”而不是“等于” 2^40,所以scale精确值有微小差异。

3. 解决对齐难题:在混合不同“深度”的计算结果(如多项式求和)时,必须通过手动设置缩放因子普通模数切换,强行统一缩放因子为理论目标值 2^40,统一所有项的楼层(模数切换)拉到最低楼层,才能正确相加。

4. 验证结果:代码先用明文计算预期真实值,解密、解码密文并对比结果,结果高度近似。结论:CKKS 给出的是高精度的近似结果,而非像 BFV/BGV 那样的精确整数。

示例 6: 旋转 (6_rotation.cpp) - 加密向量循环旋转


https://github.com/microsoft/SEAL/blob/main/native/examples/6_rotation.cpp

BFV 旋转总结

1. 操作函数:BFV使用 evaluator.rotate_rows_inplace() 进行的循环左/右移,使用 evaluator.rotate_columns_inplace() 进行旋转(即两行互换)。

2. 通用性:BFV的旋转需要 GaloisKeys(伽罗瓦密钥),并且也基本不消耗噪声预算

3. 数据结构:BFV的批处理将数据视为二维矩阵。因此,它的旋转操作是针对矩阵的行或列进行。

CKKS 旋转总结

1. 操作函数:CKKS使用 evaluator.rotate_vector() 来旋转一维向量。注意函数名是 rotate_vector(),不是BFV里用的 rotate_rows

2. 通用性:和BFV的旋转一样,CKKS的旋转同样需要 GaloisKeys,并且也不消耗噪声预算

3. 数据结构:CKKS操作的是一维向量,而BFV批处理操作的是二维矩阵的行或列。这是两者在应用旋转时的关键区别。

示例 7: 序列化 (7_serialization.cpp) - 保存和加载对象


https://github.com/microsoft/SEAL/blob/main/native/examples/7_serialization.cpp

解决的核心问题:如何高效地在不同角色间(如客户端与服务器)传输同态加密的各种对象,并最大限度地压缩数据体积,从而构建实用的外包计算通信协议。

1. 服务器设置并共享参数

创建CKKS参数,然后使用 parms.save(parms_stream) 将参数对象序列化到一个共享的C++字符串流(模拟网络),压缩模式(zlib/zstd)可以显著减小数据体积,

2. 客户端生成密钥并加密数据

从流中加载参数,生成秘密密钥,并使用 sk.save(sk_stream) 保存。

密钥体积:代码创建了两个重线性化密钥:一个是“种子”态的 Serializable<RelinKeys>,一个是完全展开的 RelinKeys

密文体积:代码用两种模式加密了两个数(2.3 和 4.5):标准的 encrypt(公钥模式,密文不可种子化)和 encrypt_symmetric(秘密密钥模式,密文可种子化)。观察打印出的密文大小,后者会小得多。这说明了为什么在不需要公钥分发时,秘密密钥模式是更好的选择。

3. 服务器计算并传回结果

从流中加载重线性化密钥和两个密文,执行乘法、重线性化和重缩放。

将计算结果密文保存到数据流。注意,这个结果密文已经过计算,无法再被保存为“种子”状态。

4. 客户端解密验证

  • 从流中加载秘密密钥和结果密文,解密、解码,验证结果是否接近 2.3 * 4.5 = 10.35

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐