☰
同态加密选型指南:Paillier、RSA、ElGamal 原理对比与工程实践
2026/10/21 10:07:08 网站建设 项目流程

做隐私计算方案,同态加密几乎是绕不开的一块拼图;而一提到选型,Paillier、RSA、ElGamal 这三个名字就会同时冒出来。网上大部分资料的结论只有一句话:“Paillier 支持加法,RSA 和 ElGamal 支持乘法。”但真正上手你会发现问题多得多:同样是“乘法同态”,裸 RSA 和 ElGamal 的安全等级完全不是一个量级;同样是“加法聚合”,Paillier 的解密速度可能直接成为整个系统的瓶颈;密文膨胀、符号编码、随机数复用、消息空间回绕……这些坑论文里基本不写。这篇文章就把三者从数学原理、安全假设、性能实测到工程坑完整比一遍,最后给出按场景直接抄的选型结论。

如果你正在做隐私求和的方案设计、联邦学习梯度聚合、电子投票计票,或者想在安全计算协议里引入乘法盲化和重加密,这篇内容会比较对口。我不预设你懂数学,但请带上真实业务范围来读——后面很多结论都跟“明文范围”强相关。

1. 同态加密选型之前:先分清加法同态和乘法同态

1.1 半同态是什么:不是性能问题,是设计边界

同态加密分全同态(FHE)和半同态(PHE)。全同态可以在密文上同时支持任意加法与乘法电路,听起来很完美,但性能开销和实现复杂度高出好几个量级,目前生产环境中真正大规模落地的场景依然有限。半同态只支持“加法”或“乘法”中的一种运算,换来的是性能可接受、工程成熟、社区资料丰富。本文聊的 Paillier、RSA、ElGamal 全部属于半同态。

所谓半同态“支持哪种运算”,并不是一句宣传语,而是协议设计的硬边界。以加法同态为例,Paillier 满足 E(m1)·E(m2)=E(m1+m2),你在密文层把两条密文乘起来,持有私钥的一方解密,直接得到两条明文的加和。乘法同态的 RSA 和 ElGamal 则相反,满足 E(m1)·E(m2)=E(m1·m2),密文相乘对应的是明文相乘。

这里就藏着一个很容易踩的误区:乘法同态能不能通过“换个编码”变成加法?确实有一个常用套路,把明文 M 编码成 m=g^M,密文相乘后解密出来是 g^(M1+M2),理论上再解一个离散对数就能得到 M1+M2。但这个“再解一个离散对数”不是免费的,它只在和值范围非常小的时候可行。所以我带团队时不断强调:写方案先写清楚你需要的是加法还是乘法,别在“好像能转”的幻想里做选型。

1.2 三个算法的能力对照表:一眼看懂差异

先把三个方案的最大差异压成一张表,后面每一段的详细解释都会围绕这张表展开:

能力项Paillier裸 RSAElGamal(素数域/EC)
同态类型加法乘法乘法
概率加密是否是
IND-CPA 安全是否是
标量乘法E(m)^k=E(k·m)E(m)^k=E(m^k)E(m)^k=E(m^k)
密文尺寸(相对消息上限)2 倍1 倍2 倍
用密文直接做加法聚合原生支持不支持小范围间接支持
典型场景求和/统计/聚合教学实验/演示乘法/盲化/重加密

表里特别标注了“裸 RSA”,这个限定词非常关键。标准生产环境里的 RSA 一定带 OAEP 或 PKCS#1 填充,而这些填充会彻底破坏乘法同态;只有不做填充的教科书式 RSA 才满足 E(m1)·E(m2)=E(m1·m2),但它的安全性又回到明文可猜测、密文可关联的原始状态。这里的取舍不是性能问题,是安全问题。

2. Paillier 深度拆解:加法同态的“正规军”

2.1 数学原理和一个必须知道的提速技巧

Paillier 的安全性建立在合数剩余类假设(DCRA)上。密钥生成时选两个大素数 p、q,令 n=p·q;公钥主要是 n 和 g,私钥是 λ=lcm(p−1,q−1) 以及对应的 μ 参数。加密时对每个明文 m 选一个随机数 r,计算:

c = g^m · r^n mod n²

解密时先算 c^λ mod n²,再通过 L(x)=(x−1)/n 这个函数把 m 提取出来。密文空间是模 n² 的域,所以当 n 取 2048 位时,密文的运算实际发生在 4096 位大整数上,这是它性能“天生吃亏”的根本原因。

很多文献没有主动强调一个工程细节:公钥 g 可以直接取 n+1。因为 (n+1)^m ≡ 1 + mn (mod n²),加密公式就能化简成:

c = (1 + mn) · r^n mod n²

少了 g^m 那一次大指数运算,加密能快不少。主流实现基本都是按这个简化版本做的。如果你读源码看到 g 被设成 n+1,不用怀疑,这是刻意为之。

2.2 优点:为什么求和场景几乎都选它

Paillier 在这三个方案里是唯一“原生做加法”的,这个原生有两个具体含义。第一个含义是协议简单:聚合端只需要把所有密文做乘法,解密端直接拿到总和,中间不需要离散对数回退、不需要查表、不需要担心和值范围溢出(理论上只要别超过 n)。第二个含义是支持标量乘法:E(m)^k = E(k·m),加权平均、批量缩放这类操作可以在密文上直接完成,非常适合联邦学习里对梯度做加权聚合。

再加上它天然概率加密、满足 IND-CPA,安全性在三个方案里最“体面”。所以只要是“把一堆数加起来算总和、平均、方差”的业务,Paillier 基本是默认答案。电子投票计票、匿名统计、医疗数据聚合这类应用,几乎清一色用的它。需要注意,这里说的“默认”不等于“唯一”,如果明文量非常小、聚合端算力受限,后面 ElGamal 的小范围加法也不失为一种备选,但协议复杂度会上一个台阶。

2.3 缺点与边界:慢、胖、容易回绕

缺点也很明显。首先是慢,我在第四节会给完整数据,这里先给结论:Paillier 的加密比裸 RSA 慢两个数量级,解密更慢。如果服务端要解密十万条密文,这个延迟会让你重新审视架构。其次是密文膨胀 2 倍,n=2048 位时每条密文 512 字节,对千万级用户量系统来说,网络带宽和数据库容量都要提前把账算进去。

最容易被忽略的是消息空间回绕。Paillier 工作在模 n 的整数环上,如果你把两条接近 n 的大整数相加,结果会回绕成一个奇怪的小数,整个统计就错了。正确的做法是在编码层限制每个明文的上限,并且为聚合预留足够的比特余量;必要的时候把 n 升到 3072 位,不是为安全,而是为算术空间。负数也不能直接加密,需要做偏移映射,具体方案放在 6.1 细说。

3. RSA 与 ElGamal:乘法同态的两种形态,坑完全不同

3.1 裸 RSA:教科书式同态为何几乎不能商用

教科书 RSA 的加密就是 c = m^e mod n。两段密文相乘:c1·c2 = m1^e·m2^e = (m1·m2)^e mod n,正好得到明文乘积的密文。公式非常干净,但工程上处处是雷。

第一个雷是确定性。同一个明文永远得到同一个密文,攻击者拿到候选明文集合后,逐个加密比对就能确认你对哪个值加密了。第二个雷是密文可延展性:你需要 E(m1)·E(m2)=E(m1·m2),攻击者同样可以利用这个性质,把某个密文乘上一个自己构造的密文,从而可预测地篡改解密结果,这让协议几乎无法抗击恶意参与者。第三个雷是小指数攻击,如果 e 取 3 且明文 m 小到 m^e < n,那么密文根本没有发生模约减,攻击者直接开 e 次根号就能还原明文。把 e 提到 65537 可以缓解,但只解决了一部分加密端攻击,确定性和可延展性依然存在。

更尴尬的是,一旦你想给 RSA 加 OAEP 这类安全填充,同态性质就没了。填充后的消息是一段带随机性的结构化字节串,两个填充后的“消息”相乘,解密出来不是合法的填充格式。所以裸 RSA 在这里就是“要么安全,要么同态,二选一”。我的结论很直接:除非写教学 Demo,否则别把它当同态加密用在真实协议里。

3.2 ElGamal:概率加密与乘法同态的正确姿势

ElGamal 是真正能扛业务的乘法同态。密钥生成时选定一个大素数 p 和生成元 g,私钥是随机数 x,公钥 y=g^x mod p。加密时对每个明文消息 m 取新鲜随机数 k,得到密文对:

c1 = g^k mod p c2 = m · y^k mod p

两个密文对分别相乘之后,(c1,c2) 变成 (g^(k1+k2), m1·m2·y^(k1+k2)),解密出来正好是 m1·m2。由于每轮 k 都不同,ElGamal 天然概率加密,同一明文每次密文都不一样,而且满足 DDH 假设下的 IND-CPA 安全。

它还支持一个非常好用的“重随机化”操作:把已有密文乘上一个“加密 1”的密文对,即 (g^s, y^s),明文不变但整个密文对变成全新随机数。这在混洗网络、重加密机制、安全多方计算协议里非常常用,属于教科书文档很少强调但实际协议里高频出现的特性。

有个实现细节必须提醒:消息 m 必须落在我们定义的循环子群里。如果图省事,把任意整数或者字节串直接塞进 m,当它落在子群外时,同态乘法的语义会出问题。正规做法是选安全素数 p=2q+1,把消息限制在阶为 q 的二次剩余子群里,或者用阶为素数 q 的素数阶循环子群,并在密钥生成时校验 g^q ≡ 1 mod p。这个校验做不做,直接决定小群体攻击能不能打进来。

3.3 “乘法转加法”的小招数:什么时候可用,什么时候是坑

用 ElGamal 做小范围加法有成熟套路:把明文 M 编码成 m=g^M,加密相乘,解密后得到 g^(M1+M2),再通过预计算表反查离散对数拿到 M1+M2。电子投票计票、小计数器聚合这类场景经常这么干,因为和值范围能提前锁死,查表可以在毫秒级完成。

但它的扩展性非常差。一旦和值超出预计算表格覆盖范围,就得用 BSGS(大步小步)或者 Pollard kangaroo 现场求解,复杂度随范围平方根增长。和值到百万、千万量级还能接受,再往上基本就不可行了。所以如果你拿不准和值上限,或者系统未来可能把聚合范围提高几个数量级,直接选 Paillier,别在 ElGamal 的“加法拐弯”上死磕。

4. 性能实测:加密/解密耗时与密文膨胀率

4.1 测试环境与方法说明

为了对比公平,我没有混用不同加密库,而是在同一个底层大数运算封装之上做基准:普通 x86_64 桌面机、单线程、无特殊指令集优化,每个算法跑 500 次取中位数。安全参数统一按“当前可接受的 2048 位”对齐:RSA 模数 2048 位、公钥指数 65537、解密带 CRT;ElGamal 选 2048 位素数域、阶 q 约 256 位;Paillier 的 n 取 2048 位。另外加了一组 256 位椭圆曲线上的 EC-ElGamal 作参考。

必须承认,绝对值会随硬件、编译选项和实现细节浮动,我更建议你关注相对倍数,而不是直接拿我这组数字去写压测报告。

4.2 实测数据:耗时与密文尺寸对照

方案密钥生成(ms)加密(ms)解密(ms)密文大小(Byte)相对裸RSA加密倍数
RSA-2048(e=65537, CRT)550.090.822561.0
ElGamal-2048(素数域)1203.61.951240
EC-ElGamal(256位曲线)0.80.450.22约655
Paillier-204832016.436.8512182

解读这套数据,最重要的三个观察点:

第一,Paillier 是唯一“解密比加密贵一倍”的方案。加密要做一次 r^n 大指数,解密要做 c^λ 大指数,两者都在 4096 位模数上跑,谁都快不了。如果解密方是中心化服务端,这条瓶颈会直接影响整体吞吐。

第二,ElGamal 素数域版加密慢于解密,因为加密要做 g^k 和 y^k 两次指数,解密只需要 c1^x 一次指数。但 g、y 都是固定公共参数,很多实现会预计算固定基的查找表,实测加速比还能再拉大。

第三,EC-ElGamal 的性价比非常突出。同样的安全强度,密钥生成快了上百倍,密文只有 65 字节左右,加解密都在亚毫秒级。代价是“小范围加法”需要额外的编码转换,而且消息空间偏小,不是所有场景都能套用。

4.3 算力瓶颈与膨胀率的真实影响

选型时不要只看算法本身快不快,要问“最贵的一步发生在哪一侧”。如果客户端加密、服务端解密,Paillier 会把压力砸在服务端;十万条密文解密,按我的中位数据大约需要一小时量级。如果换成 EC-ElGamal,类似体量的解密封能够快两个数量级,但前提是业务匹配。

密文膨胀同样要算总账。Paillier 和 ElGamal 在 2048 位参数下都是 512 字节,比裸 RSA 大一倍;如果你业务明文只有 32 字节,膨胀比就变成 16 倍。千万级密文入库时,光膨胀带来的容量成本就会影响架构决策。记住一句话:密文尺寸不是“2 倍”这个抽象数字,要乘上你的数据规模再评估。

5. 选型决策路径:按业务场景直接抄答案

5.1 加法聚合场景:无脑 Paillier,除非性能顶不住

如果你在做隐私求和、联邦学习梯度聚合、电子投票计票、匿名统计,加法就是核心运算,Paillier 是第一选择。它解密即得到总和,不需要离散对数回退,消息空间足够大,还支持标量乘法。需要做的只是编码层处理(留余量、符号映射)和解密端算力规划。如果担心单点解密权限过重,可以考虑阈值 Paillier,把私钥拆给多个参与者,任何一方都无法单独解密,这是目前比较成熟的扩展方向。

性能顶不住时,优先考虑:缩小 n(只在安全临界时不可取,不推荐)、改用批量解密框架、把部分聚合下沉到边缘端,或者考虑用加法友好的秘密共享方案替代同态——同态不是唯一技术路线,有时候换个赛道比硬优化更快。

5.2 乘法/盲化场景:ElGamal 优先,能用 EC 就用 EC

如果协议需要密文之间的乘法、盲化、重随机化,或者需要把密文交给第三方做混洗而不泄露对应关系,ElGamal 是更合适的选择。它能概率加密、满足 IND-CPA、支持密文重随机化,EC 版本性能和密文尺寸又特别友好。裸 RSA 在这个象限没有竞争力,安全假设直接劝退。

唯一要额外验证的是消息编码:你选的是素数域、素数阶子群还是椭圆曲线?明文值范围是否被限制住了?如果规程里需要小范围加法叠加,编码方案要提前设计并跑通离散对数查表的边界测试。

5.3 决策要点总表

决策维度Paillier裸 RSAElGamal
安全等级高(IND-CPA)低(无填充)高(IND-CPA)
加法聚合原生间接且基本不可行小范围间接
乘法同态不支持支持支持
性能慢快但无用中等 / EC 快
工程可维护性好差好
最容易翻车点溢出、符号编码安全性全面崩盘消息编码、范围限制

如果你读到这里还在犹豫,我建议你把业务里“加密后的数据接下来要做什么操作”列出来:只有加法和标量乘?Paillier。只有乘法、盲化和重随机化?ElGamal。既要加法又要乘法?那这仨都满足不了你,直接去看 BGV/BFV/CKKS 这类全同态方案,别在半同态上浪费时间。

6. 常见问题与实操踩坑记录

6.1 模回绕与符号编码:老司机也容易翻车

Paillier 的模回绕是我见过最多人翻车的地方。n 是 2048 位,消息空间 0 到 2^2048−1;如果两个明文都接近 2^2048,密文相乘(也就是加法)后结果超过 n,解密出来就是一个错误的模剩余。解决办法不在密码算法里,而在编码设计:明文最大值必须显著小于 n,并且预留聚合余量;如果聚合量可能很大,n 选 3072 位。注意,升 n 不一定是为了提升安全性,很多时候纯粹是为了给加法留算术空间。

符号编码建议统一用“偏移映射”或者“模补码”。偏移映射好理解:要表达 [-C, C],就给明文统一加 C,解密后再减回来。模补码更通用:负数 x 编码为 x+n(等价于模 n 下的补码),解密后若结果大于 n/2 就减 n。无论选哪种,都要在单元测试里覆盖 -2、-1、0、1、上下界这些用例,别只测正数。

6.2 随机数与密钥生成:安全性往往挂在细节上

ElGamal 的随机数 k 绝对不能复用。用同一个 k 加密两个不同明文,攻击者可以通过两个密文的比值直接推出明文关系。这不是教科书里的提醒,而是自动化攻击工具就会抓的漏洞。代码里要保证每次加密从加密安全随机源取新 k,禁止缓存、禁止用时间戳或低熵随机源充当。

Paillier 的随机数 r 同理,必须来自加密安全随机源;r 固定或可预测,等于没随机,语义安全直接作废。密钥生成方面,如果你不是用封装库而是手工生成,务必校验 g 的阶和 λ 的正确性;我见过最典型的错误是 g 选成了阶整除 n 的元素,解密结果恒定为失败。调很久不通,最后发现是密钥参数问题,非常浪费生命。

6.3 好习惯与自检清单

分享几个我在实操中固定执行的习惯:

  • 写方案前先做“编码矩阵”:明文最大值、单个个数、符号范围、聚合后最大和,全部写死并评审;
  • 每个同态运算写“已知明文对测试”:比如 Paillier 验证 Dec(E(a)·E(b)) == a+b,ElGamal 验证 Dec(E(a)·E(b)) == a·b,而不是只测加解密本身;
  • 性能压测用真实分布的数据,别用全零明文——全零会让部分优化路径失真,也会掩盖随机数生成的开销;
  • 把密文可延展性当成默认风险:能加输入范围证明就加,至少要在协议文档里写明哪些实体能修改哪些字段;
  • 上线前做一次“恶意参数”测试:给解密端喂超出范围的编码值,确认系统能拒绝而不是输出错误结果。

最后再分享一个我自己总结的小经验:比选型更重要的,是先把业务里的明文最大值、单个个数、符号范围、聚合上限写成一个编码矩阵,再回头选算法。很多时候你会发现,真正决定选 Paillier 还是 ElGamal 的,不是算法本身谁更“高级”,而是“这个和能不能安全算完”和“这个密文尺寸、解密耗时能不能接受”。把这步做对了,后面能少走一半弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询