Caloogy 文章
English

生日悖论与哈希碰撞:为什么 SHA-256 只有 128 位抗碰撞强度

日期
2026 年 4 月 24 日
系列
密码学
阅读
3 分钟
主题
HashBirthday paradoxCollisionSHA-256

一句话版本

一个房间里只要有 23 个人,就有超过一半的概率有两人同一天生日。原因是"配对"的数量随人数平方增长。 同样道理,输出 nn 位的哈希函数,不需要试 2n2^n 次,只要试大约 2n=2n/2\sqrt{2^n} = 2^{n/2} 次就能找到两个哈希值相同的输入。所以 256 位的 SHA-256 只提供 128 位的抗碰撞强度。

全文分三层:

  1. 通俗理解:生日悖论,哈希函数要满足的三个性质
  2. 实际用处:区块链、Git、证书,以及 MD5、SHA-1 被攻破的事件
  3. 数学原理:N\sqrt{N} 的推导、Pollard rho 式的无存储碰撞搜索,附一次真实的截断碰撞实验

第一层:通俗理解

1.1 生日悖论

一年 365 天。问:一个房间里至少多少人,才能让"有两人同一天生日"的概率超过 50%?

直觉往往回答 183(365 的一半)。正确答案是 23。

关键在于,我们问的不是"有人和我同一天生日",而是"任意两人同一天"。23 个人可以组成 (232)=253\binom{23}{2} = 253 对,每一对撞上的概率是 1/3651/365,253 对加起来,机会就相当可观了。

人数 至少两人同一天生日的概率
10 11.7%
23 50.7%
50 97.0%
70 99.9%

1.2 哈希函数是什么

哈希函数把任意长的数据压缩成固定长度的"指纹",比如 SHA-256 输出 256 位。好的哈希函数要满足:

性质 含义 理想难度(nn 位输出)
抗原像 给定指纹,找不到对应的输入 2n2^n
抗第二原像 给定一个输入,找不到另一个同指纹的输入 2n2^n
抗碰撞 找不到任意两个同指纹的输入 2n/22^{n/2}(生日界)

第三条和生日悖论完全对应:碰撞攻击者可以自由选择两个输入,就像在房间里找"任意两人"同生日,所以只需要 2n\sqrt{2^n} 次。

1.3 碰撞为什么危险

如果能找到两份文件 A、B 哈希相同:让别人对无害的 A 签名(签名实际签的是哈希值),这个签名就同样适用于有害的 B。


第二层:实际用处

哈希函数无处不在

碰撞攻击的历史

2004王小云团队公布 MD5碰撞,震动密码学界2005王小云团队给出 SHA-1理论攻击,远快于生日界2008研究者用 MD5碰撞伪造出受信任的 CA证书2012Flame 间谍软件用 MD5碰撞伪造微软代码签名2017SHAttered,首个公开的SHA-1 碰撞(两份PDF)2020SHA-1选择前缀碰撞(Shambles),可用于伪造PGP 密钥哈希碰撞大事记

SHAttered 大约用了 2632^{63} 次 SHA-1 计算,远少于生日攻击的 2802^{80}。这说明它利用的是 SHA-1 的结构弱点,而不只是生日悖论。

设计结论:要 128 位安全,抗碰撞场景就要 256 位输出。这就是 SHA-256 成为默认选择的原因。

量子计算机对哈希的影响

理论上的量子碰撞算法(BHT)可以做到 2n/32^{n/3},但需要海量量子内存,实际意义存疑。主流观点认为 SHA-256 和 SHA-3 在后量子时代仍然够用,基于哈希的签名(SPHINCS+)甚至是后量子标准之一,见《后量子密码》。


第三层:数学原理

3.1 生日界的推导

从 NN 个可能值中独立均匀地取 kk 个,全部不相同的概率是

P无碰撞=∏i=0k−1(1−iN).P_{\text{无碰撞}} = \prod_{i=0}^{k-1}\Big(1 - \frac{i}{N}\Big).

用 1−x≤e−x1 - x \le e^{-x}(xx 小时近似相等):

P无碰撞≈exp⁡(−∑i=0k−1iN)=exp⁡(−k(k−1)2N)≈e−k2/(2N).P_{\text{无碰撞}} \approx \exp\Big(-\sum_{i=0}^{k-1}\frac{i}{N}\Big) = \exp\Big(-\frac{k(k-1)}{2N}\Big) \approx e^{-k^2/(2N)}.

令碰撞概率为 1/21/2,解得

k≈2Nln⁡2≈1.18N.k \approx \sqrt{2N \ln 2} \approx 1.18\sqrt{N}.

代入 N=365N = 365,k≈22.5k \approx 22.5,和 23 吻合。代入 N=2256N = 2^{256},k≈2128k \approx 2^{128}。

3.2 手算例子:真实找一次碰撞

SHA-256 截断到 16 位

只取 SHA-256 输出的前 4 个十六进制字符(16 位,N=65536N = 65536)。生日界预测大约需要 1.18×256≈3001.18 \times 256 \approx 300 次。

依次哈希 msg0、msg1、msg2……并记录前缀,第 122 次尝试就撞上了:

输入 SHA-256 前 16 个十六进制字符
msg114 98978261783e81fd
msg121 9897dbca5f8d00ea

两者前 16 位都是 9897。(用 Python 实际跑出,这次运气偏好。)

作为对比,要找一个前缀指定为 9897 的输入(第二原像),平均要试约 6553665536 次。

3.3 不需要大内存的碰撞搜索

上面的方法要把所有见过的哈希存下来,2642^{64} 规模时内存吃不消。Floyd 判圈的思路可以解决:

把哈希函数 HH 看成有限集合上的映射,从 x0x_0 出发反复迭代 xi+1=H(xi)x_{i+1} = H(x_i)。因为集合有限,序列迟早进入循环,形状像希腊字母 ρ\rho。进入循环的那一刻,两个不同的点映射到了同一个点,这就是一次碰撞。

用"龟兔赛跑"(一个每次走一步,一个每次走两步)就能在 O(N)O(\sqrt{N}) 时间、O(1)O(1) 内存内找到它。这和Diffie–Hellman与离散对数里解离散对数的 Pollard rho 是同一个思想。van Oorschot–Wiener 的并行版本还能让成千上万台机器同时搜索。

3.4 为什么生日界不能被"设计"掉

生日攻击是通用的:它不看哈希函数的内部结构,对任何 nn 位输出的函数都有效。所以抗碰撞强度的上限永远是 n/2n/2 位。密码学家能做的只有一件事:把输出加长。


总结

层次 要点
直觉 23 人就有一半概率撞生日,因为"对"的数量是平方级
数学 k≈1.18Nk \approx 1.18\sqrt{N} 次就有一半概率碰撞
安全 nn 位哈希抗碰撞最多 n/2n/2 位;所以用 SHA-256
历史 MD5(2004)、SHA-1(2017)相继被攻破,都是结构弱点 + 生日思想