哈希与认证基础(一):哈希函数的安全目标:原像、第二原像与碰撞
-
- 前言
- 一、哈希函数的基本模型
-
- [1.1 从任意长度消息得到固定长度摘要](#1.1 从任意长度消息得到固定长度摘要)
- [1.2 碰撞为什么必然存在](#1.2 碰撞为什么必然存在)
- [1.3 哈希函数不是加密算法](#1.3 哈希函数不是加密算法)
- 二、原像抗性:给定摘要寻找消息
-
- [2.1 原像的概念](#2.1 原像的概念)
- [2.2 原像攻击和密码破解的区别](#2.2 原像攻击和密码破解的区别)
- [2.3 原像攻击的理论复杂度](#2.3 原像攻击的理论复杂度)
- 三、第二原像抗性:给定消息寻找替代品
- 四、碰撞抗性:任意寻找两条相同摘要的消息
-
- [4.1 碰撞攻击的定义](#4.1 碰撞攻击的定义)
- [4.2 碰撞和第二原像的核心差别](#4.2 碰撞和第二原像的核心差别)
- [4.3 碰撞安全性为什么只有一半](#4.3 碰撞安全性为什么只有一半)
- 五、三种安全目标的统一对比
-
- [5.1 攻击模型对比](#5.1 攻击模型对比)
- [5.2 复杂度对比](#5.2 复杂度对比)
- [5.3 用一句话记忆](#5.3 用一句话记忆)
- 六、哈希函数不等于认证
-
- [6.1 普通哈希只能进行公开校验](#6.1 普通哈希只能进行公开校验)
- [6.2 完整性和认证不是一回事](#6.2 完整性和认证不是一回事)
- [6.3 密码存储也不能直接使用普通哈希](#6.3 密码存储也不能直接使用普通哈希)
- [七、用 16 位实验观察碰撞](#七、用 16 位实验观察碰撞)
-
- [7.1 为什么不能直接实验 SHA-256 碰撞](#7.1 为什么不能直接实验 SHA-256 碰撞)
- [7.2 Python 实验代码](#7.2 Python 实验代码)
- 总结
前言
假设有一份合同文件:
text
contract.pdf
系统先计算它的哈希摘要:
h = H ( M ) h=H(M) h=H(M)
其中, M M M 表示合同内容, H H H 表示哈希函数, h h h 表示摘要。
之后,系统可能会对这个摘要进行数字签名:
σ = Sign s k ( H ( M ) ) \sigma=\operatorname{Sign}_{sk}(H(M)) σ=Signsk(H(M))
这套流程看起来很简单,但安全性取决于一个关键问题:
攻击者能不能找到另一份不同的消息,却让它产生相同的摘要?
如果攻击者能够构造:
M ′ ≠ M M'\neq M M′=M
同时满足:
H ( M ′ ) = H ( M ) H(M')=H(M) H(M′)=H(M)
那么原本针对 M M M 生成的签名,就可能被攻击者拿来证明 M ′ M' M′。
这只是哈希函数安全问题中的一种情况。
在不同攻击场景下,攻击者可能掌握:
- 一个已经公开的摘要;
- 一条已经确定的原始消息;
- 完全自由选择的两条消息。
对应的安全问题分别是:
- 原像抗性:给定摘要,难以找到能够产生该摘要的消息;
- 第二原像抗性:给定一条消息,难以找到另一条摘要相同的消息;
- 碰撞抗性:难以找到任意两条摘要相同的不同消息。
这三个概念名称相似,但攻击者的能力和攻击难度并不相同。
本文先不讨论 SHA-256 的具体轮函数,也不展开 Merkle-Damgård 结构、长度扩展攻击和 HMAC,而是先建立哈希函数安全性的基础框架。
Crypto 专栏:https://blog.csdn.net/r_feynman_/category_13190241.html
Crypto 密码解析实战靶场:https://blog.csdn.net/r_feynman_/category_13194584.html
一、哈希函数的基本模型
1.1 从任意长度消息得到固定长度摘要
密码学哈希函数可以表示为:
H : { 0 , 1 } ∗ → { 0 , 1 } n H:\{0,1\}^{*}\rightarrow\{0,1\}^{n} H:{0,1}∗→{0,1}n
其中:
- { 0 , 1 } ∗ \{0,1\}^{*} {0,1}∗ 表示任意长度的比特串;
- { 0 , 1 } n \{0,1\}^{n} {0,1}n 表示固定长度为 n n n 的比特串;
- n n n 表示哈希摘要长度。
例如,SHA-256 的输出长度为 256 位:
H ( M ) ∈ { 0 , 1 } 256 H(M)\in\{0,1\}^{256} H(M)∈{0,1}256
无论输入是:
text
hello
还是一个几 GB 大小的视频文件,最终得到的摘要长度都固定为 256 位。
例如:
text
输入:
hello
SHA-256:
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
哈希函数具有几个基本特征:
- 相同输入必须得到相同输出;
- 输入长度可以任意;
- 输出长度固定;
- 给定输入时,摘要可以高效计算;
- 输入发生微小变化时,摘要通常会发生明显变化。
但是,这些特征还不足以说明哈希函数是安全的。
密码学真正关心的是:
攻击者能否利用摘要值,构造满足特定条件的输入。
1.2 碰撞为什么必然存在
假设哈希函数输出长度为 n n n 位,那么摘要空间大小为:
2 n 2^n 2n
而输入消息可以是任意长度,数量远远超过 2 n 2^n 2n。
根据抽屉原理,必然存在两条不同消息:
M 1 ≠ M 2 M_1\neq M_2 M1=M2
使得:
H ( M 1 ) = H ( M 2 ) H(M_1)=H(M_2) H(M1)=H(M2)
这就是碰撞。
因此,哈希函数无法做到:
任意两条不同消息的摘要都不同。
因为输出空间有限,输入空间无限,碰撞在数学上必然存在。
哈希函数的安全目标是让碰撞满足下面的条件:
碰撞虽然存在,但攻击者无法在现实计算资源内找到。
这也是"碰撞存在"和"碰撞可利用"之间的区别。
1.3 哈希函数不是加密算法
哈希函数经常和加密算法一起使用,但两者的目标不同。
加密算法关注保密性:
C = E K ( M ) C=E_K(M) C=EK(M)
持有密钥的一方可以通过解密恢复明文:
M = D K ( C ) M=D_K(C) M=DK(C)
哈希函数则是:
h = H ( M ) h=H(M) h=H(M)
它通常没有对应的解密过程。
哈希函数也不能简单理解成"把数据压缩后保存"。普通压缩算法希望在压缩后恢复原始数据:
compressed data → original data \text{compressed data}\rightarrow\text{original data} compressed data→original data
而哈希函数只保留一个固定长度摘要:
M → H ( M ) M\rightarrow H(M) M→H(M)
摘要中并不包含足够的信息来唯一恢复原始消息。
不过,"哈希不可逆"并不是完整的安全定义。因为实际系统中,攻击者可能通过字典、枚举或已知候选集合,尝试找到一个能够产生相同摘要的输入。
二、原像抗性:给定摘要寻找消息
2.1 原像的概念
假设有一条消息 M M M:
h = H ( M ) h=H(M) h=H(M)
那么 M M M 就是摘要 h h h 的一个原像。
原像攻击的目标是:
已知摘要 h h h,寻找任意一条消息 M ′ M' M′,使 H ( M ′ ) = h H(M')=h H(M′)=h。
形式化表示为:
给定 h ,寻找 M ′ ,满足 H ( M ′ ) = h \text{给定 }h,寻找M'\text{,满足 }H(M')=h 给定 h,寻找M′,满足 H(M′)=h
注意,这里不要求找到原始消息 M M M。
只要找到任意一条消息 M ′ M' M′,满足:
H ( M ′ ) = h H(M')=h H(M′)=h
从原像攻击的定义来看,就已经成功。
2.2 原像攻击和密码破解的区别
假设数据库中保存了用户密码的哈希值:
text
hash = SHA256(password)
攻击者拿到这个哈希值后,可以尝试:
text
123456
password
admin123
qwerty
然后依次计算:
H ( 123456 ) H(\texttt{123456}) H(123456)
H ( password ) H(\texttt{password}) H(password)
H ( admin123 ) H(\texttt{admin123}) H(admin123)
只要某个结果和数据库中的摘要一致,就找到了一个可用输入。
这个过程在形式上与"根据摘要寻找输入"有关,但它并不等同于针对理想哈希函数进行 2 n 2^n 2n 级别的通用原像攻击。
原因是用户密码的有效搜索空间通常很小。
用户密码不是均匀随机的 256 位数据,而是经常来自:
- 常见单词;
- 手机号码;
- 生日;
- 键盘组合;
- 其他网站泄露密码;
- 用户名与年份组合。
因此,密码哈希被破解,通常是因为密码本身熵不足,而不是哈希函数被数学逆向。
更准确地说:
哈希函数的原像抗性不能阻止攻击者枚举低熵密码。
这也是密码存储必须使用专门 KDF,而不是直接保存 SHA-256 摘要的原因。密码存储将在第九篇单独讨论。
2.3 原像攻击的理论复杂度
对于输出长度为 n n n 位的理想哈希函数,通用原像攻击的计算复杂度约为:
2 n 2^n 2n
以 SHA-256 为例:
n = 256 n=256 n=256
因此,通用原像攻击的理论量级约为:
2 256 2^{256} 2256
这并不表示现实中任何输入都绝对无法找到,而是表示在没有额外结构、没有弱密码和没有实现漏洞的情况下,最直接的通用搜索成本极高。
原像抗性关注的是:
text
给你一个摘要,能不能找到一条消息匹配它?
它不关注攻击者能不能自己找两条消息碰撞,这属于后面的碰撞抗性。
三、第二原像抗性:给定消息寻找替代品
3.1 第二原像的定义
第二原像攻击的前提是:
攻击者已经知道一条消息 M M M,并且可以计算它的摘要:
h = H ( M ) h=H(M) h=H(M)
攻击者要寻找另一条不同消息 M ′ M' M′:
M ′ ≠ M M'\neq M M′=M
满足:
H ( M ′ ) = H ( M ) H(M')=H(M) H(M′)=H(M)
形式化表示为:
给定 M ,寻找 M ′ ≠ M ,使 H ( M ′ ) = H ( M ) \text{给定 }M,寻找M'\neq M\text{,使 }H(M')=H(M) 给定 M,寻找M′=M,使 H(M′)=H(M)
这条 M ′ M' M′ 就叫作 M M M 的第二原像。
3.2 第二原像和原像的区别
两者可以这样对比:
原像攻击
攻击者知道:
h h h
攻击者寻找:
M ′ M' M′
满足:
H ( M ′ ) = h H(M')=h H(M′)=h
第二原像攻击
攻击者知道:
M M M
攻击者寻找:
M ′ ≠ M M'\neq M M′=M
满足:
H ( M ′ ) = H ( M ) H(M')=H(M) H(M′)=H(M)
原像攻击只给出一个目标摘要,而第二原像攻击给出了一条具体的原始消息。
可以用一句话区分:
text
原像:给摘要找消息。
第二原像:给消息找替代品。
3.3 文件替换场景
假设审计系统已经归档了一份文件:
text
audit-report.pdf
系统保存其摘要:
h = H ( audit-report.pdf ) h=H(\texttt{audit\text{-}report.pdf}) h=H(audit-report.pdf)
攻击者希望替换文件内容,但又不想让摘要校验失败。
攻击者需要构造:
text
audit-report-fake.pdf
满足:
H ( audit-report-fake.pdf ) = H ( audit-report.pdf ) H(\texttt{audit\text{-}report\text{-}fake.pdf})= H(\texttt{audit\text{-}report.pdf}) H(audit-report-fake.pdf)=H(audit-report.pdf)
由于原文件已经确定,攻击者不能重新选择第一条消息。
这就不是普通的"随便找一组碰撞",而是针对指定消息寻找第二原像。
对于理想化的 n n n 位哈希函数,第二原像攻击通常需要约:
2 n 2^n 2n
量级的计算。
不过对于某些具体哈希结构和超长消息,可能存在低于理想复杂度的专门攻击。后续讲解 Merkle-Damgård 结构时,会进一步讨论消息长度和内部状态对攻击模型的影响。
3.4 第二原像与数字签名
数字签名通常不会直接对任意长度消息执行签名,而是先计算摘要:
h = H ( M ) h=H(M) h=H(M)
再对摘要进行签名:
σ = Sign s k ( h ) \sigma=\operatorname{Sign}_{sk}(h) σ=Signsk(h)
验证时,接收方重新计算:
h ′ = H ( M ) h'=H(M) h′=H(M)
然后验证:
Verify p k ( σ , h ′ ) = True \operatorname{Verify}_{pk}(\sigma,h')=\text{True} Verifypk(σ,h′)=True
如果攻击者针对已经签名的消息 M M M 找到了第二原像 M ′ M' M′:
M ′ ≠ M M'\neq M M′=M
且:
H ( M ′ ) = H ( M ) H(M')=H(M) H(M′)=H(M)
那么在某些签名流程中,原本对应 M M M 的签名就可能被拿去验证 M ′ M' M′。
但这里不要简单得出:
数字签名只依赖第二原像抗性。
实际数字签名同时涉及:
- 哈希函数的碰撞抗性;
- 哈希函数的第二原像抗性;
- 签名算法的不可伪造性;
- 消息编码和签名格式;
- 公钥和证书验证。
安全性是多个环节共同作用的结果。
四、碰撞抗性:任意寻找两条相同摘要的消息
4.1 碰撞攻击的定义
碰撞攻击的目标是寻找任意两条不同消息:
M 1 ≠ M 2 M_1\neq M_2 M1=M2
满足:
H ( M 1 ) = H ( M 2 ) H(M_1)=H(M_2) H(M1)=H(M2)
与第二原像攻击不同的是,碰撞攻击中两条消息都可以由攻击者自由选择。
攻击者不需要:
- 匹配某个指定摘要;
- 针对某个已知文件寻找替代品;
- 恢复任何真实原始消息。
只要找到一组满足条件的消息对即可。
4.2 碰撞和第二原像的核心差别
假设攻击者要攻击一份已经存在的合同 M M M。
情况一:合同已经固定
攻击者只能寻找:
M ′ ≠ M M'\neq M M′=M
满足:
H ( M ′ ) = H ( M ) H(M')=H(M) H(M′)=H(M)
这是第二原像攻击。
情况二:签名之前自由准备两份合同
攻击者可以提前准备:
M 1 M_1 M1
和:
M 2 M_2 M2
并努力构造:
H ( M 1 ) = H ( M 2 ) H(M_1)=H(M_2) H(M1)=H(M2)
这是碰撞攻击。
可以简单理解为:
text
第二原像:
第一条消息由别人决定。
碰撞:
两条消息都由攻击者决定。
正因为碰撞攻击可以自由选择两条消息,所以它通常比第二原像攻击更容易。
4.3 碰撞安全性为什么只有一半
假设哈希输出为 n n n 位,摘要空间大小为:
N = 2 n N=2^n N=2n
攻击者生成 q q q 条消息后,可以形成:
( q 2 ) = q ( q − 1 ) 2 \binom{q}{2}= \frac{q(q-1)}{2} (2q)=2q(q−1)
组消息对。
每一组消息对产生碰撞的概率约为:
1 N = 1 2 n \frac1N= \frac1{2^n} N1=2n1
因此,碰撞出现的机会与下面这个量有关:
q ( q − 1 ) 2 n + 1 \frac{q(q-1)}{2^{n+1}} 2n+1q(q−1)
当:
q ≈ 2 n / 2 q\approx2^{n/2} q≈2n/2
时:
q 2 ≈ 2 n q^2\approx2^n q2≈2n
此时消息对数量已经达到摘要空间的量级,出现碰撞的概率会明显增加。
因此,理想化情况下:
n 位哈希的碰撞攻击复杂度约为 2 n / 2 \boxed{ n\text{ 位哈希的碰撞攻击复杂度约为 }2^{n/2} } n 位哈希的碰撞攻击复杂度约为 2n/2
例如:
| 哈希输出长度 | 理想化碰撞复杂度 |
|---|---|
| 128 位 | 2 64 2^{64} 264 |
| 160 位 | 2 80 2^{80} 280 |
| 256 位 | 2 128 2^{128} 2128 |
| 512 位 | 2 256 2^{256} 2256 |
对于 SHA-256:
n = 256 n=256 n=256
所以它的通用碰撞安全强度约为:
2 128 2^{128} 2128
这并不表示 SHA-256 摘要只有 128 位,而是表示寻找任意碰撞时,生日攻击将有效搜索空间降低到了 128 位安全强度。
完整的碰撞概率推导和生日攻击实验将在下一篇展开。
五、三种安全目标的统一对比
5.1 攻击模型对比
| 安全目标 | 攻击者已知信息 | 攻击者能否自由选择消息 | 攻击目标 |
|---|---|---|---|
| 原像抗性 | 摘要 h h h | 需要寻找输入 | 找到 M M M,使 H ( M ) = h H(M)=h H(M)=h |
| 第二原像抗性 | 已知消息 M M M | 第一条消息不能选择 | 找到 M ′ ≠ M M'\neq M M′=M,使 H ( M ′ ) = H ( M ) H(M')=H(M) H(M′)=H(M) |
| 碰撞抗性 | 只知道哈希算法 | 两条消息都可以选择 | 找到 M 1 ≠ M 2 M_1\neq M_2 M1=M2,使摘要相同 |
5.2 复杂度对比
对于理想化的 n n n 位哈希函数:
原像攻击 ≈ 2 n 第二原像攻击 ≈ 2 n 碰撞攻击 ≈ 2 n / 2 \begin{aligned} \text{原像攻击}&\approx2^n\\ \text{第二原像攻击}&\approx2^n\\ \text{碰撞攻击}&\approx2^{n/2} \end{aligned} 原像攻击第二原像攻击碰撞攻击≈2n≈2n≈2n/2
三者不能简单地互相替代。
尤其需要注意:
找到一组碰撞,不代表攻击者可以指定目标摘要。
碰撞攻击只要求:
H ( M 1 ) = H ( M 2 ) H(M_1)=H(M_2) H(M1)=H(M2)
但原像攻击要求匹配一个已经指定的摘要:
H ( M ) = h H(M)=h H(M)=h
这两个问题的自由度不同,不能因为它们都出现了"相同摘要",就认为攻击难度相同。
5.3 用一句话记忆
text
原像:给摘要找消息。
第二原像:给消息找替代品。
碰撞:任意找两条摘要相同的消息。
再换一种角度:
text
原像看一个摘要;
第二原像看一条固定消息;
碰撞看两条可自由选择的消息。
六、哈希函数不等于认证
6.1 普通哈希只能进行公开校验
假设下载页面发布:
text
文件:tool.zip
摘要:H(tool.zip)
用户下载文件后重新计算:
H ( downloaded file ) H(\text{downloaded file}) H(downloaded file)
如果两个摘要相同,可以说明当前文件与当前摘要匹配。
但这有一个前提:
摘要本身必须来自可信渠道。
如果攻击者可以同时修改:
text
恶意文件
和:
text
网页上的摘要
那么攻击者只需要重新计算:
H ( malicious file ) H(\text{malicious file}) H(malicious file)
然后把新的摘要一起发布即可。
因此,普通哈希能够帮助发现文件变化,但不能单独证明文件来自官方。
6.2 完整性和认证不是一回事
可以把几个概念区分开:
| 机制 | 能否发现数据被修改 | 能否证明来源 | 是否需要秘密 |
|---|---|---|---|
| 普通哈希 | 有条件可以 | 不能单独证明 | 否 |
| HMAC | 可以 | 可以证明来自持钥方 | 是 |
| 数字签名 | 可以 | 可以验证签名者 | 私钥 |
| 加密 | 主要提供保密性 | 不自动提供认证 | 是 |
普通哈希是公开计算的:
H ( M ) H(M) H(M)
任何人都可以重新计算。
HMAC 则引入密钥:
HMAC K ( M ) \operatorname{HMAC}_K(M) HMACK(M)
不知道密钥 K K K 的攻击者,不能仅靠公开哈希函数生成正确的认证值。
HMAC 和 MAC 将在后续文章详细介绍,这里只需要先明确:
哈希函数是构造认证机制的基础组件,但普通哈希本身不是认证协议。
6.3 密码存储也不能直接使用普通哈希
下面这种做法看起来合理:
text
database_password = SHA256(password)
但它存在明显问题:
- SHA-256 计算速度很快;
- 攻击者可以使用 GPU 批量计算;
- 常见密码容易被字典枚举;
- 没有盐时,相同密码会产生相同摘要;
- 泄露一个密码库后,可以针对所有用户重复尝试。
密码存储需要专门的密码派生函数,并配置:
- 独立随机盐;
- 足够的计算成本;
- 必要的内存成本;
- 合适的并行参数。
这一部分将在第九篇"密码存储与密钥派生"中完整展开。
七、用 16 位实验观察碰撞
7.1 为什么不能直接实验 SHA-256 碰撞
SHA-256 的完整输出长度为 256 位。
根据生日攻击估算,寻找碰撞需要约:
2 128 2^{128} 2128
量级的计算。
普通电脑无法通过简单枚举完成这样的实验,因此我们把 SHA-256 的输出截短为 16 位:
H 16 ( M ) = Truncate 16 ( SHA256 ( M ) ) H_{16}(M)= \operatorname{Truncate}_{16}(\operatorname{SHA256}(M)) H16(M)=Truncate16(SHA256(M))
此时摘要空间只有:
2 16 = 65536 2^{16}=65536 216=65536
种结果。
对应的碰撞复杂度约为:
2 8 = 256 2^8=256 28=256
这已经适合进行课堂演示。
下面的 16 位摘要只用于观察生日碰撞,不具备任何实际安全性。
7.2 Python 实验代码
python
import hashlib
def hash16(message: bytes) -> bytes:
"""
截取 SHA-256 的前 16 位。
只用于演示生日碰撞。
"""
return hashlib.sha256(message).digest()[:2]
seen = {}
collision = None
for i in range(100000):
message = f"message-{i}".encode()
digest = hash16(message)
if digest in seen:
collision = (
seen[digest],
message,
digest
)
break
seen[digest] = message
if collision is None:
print("没有找到碰撞")
else:
message_1, message_2, digest = collision
print("发现碰撞:")
print("消息 1:", message_1)
print("消息 2:", message_2)
print("摘要:", digest.hex())
print("消息是否不同:", message_1 != message_2)
print(
"摘要是否相同:",
hash16(message_1) == hash16(message_2)
)
对于固定的枚举顺序,可能得到:
text
发现碰撞:
消息 1: b'message-126'
消息 2: b'message-133'
摘要: 6620
消息是否不同: True
摘要是否相同: True
验证过程为:
message-126 ≠ message-133 \texttt{message-126}\neq\texttt{message-133} message-126=message-133
但:
H 16 ( message-126 ) = H 16 ( message-133 ) = 6620 H_{16}(\texttt{message-126})= H_{16}(\texttt{message-133})= \texttt{6620} H16(message-126)=H16(message-133)=6620
这段实验真正说明的是:
输出空间缩短后,碰撞会迅速出现;哈希输出长度直接影响碰撞安全性。
它并不说明完整 SHA-256 可以在几百次计算内找到碰撞。
总结
哈希函数的核心安全性,不是"摘要不可逆",也不是"输入改变后输出变化很大",而是攻击者能否在现实成本内完成特定任务。
原像抗性
给定摘要:
h h h
寻找:
M , H ( M ) = h M,\quad H(M)=h M,H(M)=h
它回答的是:
能不能从摘要找到一条匹配消息?
第二原像抗性
给定消息:
M M M
寻找:
M ′ ≠ M M'\neq M M′=M
满足:
H ( M ′ ) = H ( M ) H(M')=H(M) H(M′)=H(M)
它回答的是:
能不能替换一条已经确定的消息?
碰撞抗性
寻找任意两条不同消息:
M 1 ≠ M 2 M_1\neq M_2 M1=M2
满足:
H ( M 1 ) = H ( M 2 ) H(M_1)=H(M_2) H(M1)=H(M2)
它回答的是:
能不能自由构造一对摘要相同的消息?
对于理想化的 n n n 位哈希函数:
原像攻击 ≈ 2 n 第二原像攻击 ≈ 2 n 碰撞攻击 ≈ 2 n / 2 \begin{aligned} \text{原像攻击}&\approx2^n\\ \text{第二原像攻击}&\approx2^n\\ \text{碰撞攻击}&\approx2^{n/2} \end{aligned} 原像攻击第二原像攻击碰撞攻击≈2n≈2n≈2n/2
因此, n n n 位哈希函数的碰撞安全性通常只有 n / 2 n/2 n/2 位。
这一篇最重要的不是记住三个英文术语,而是理解攻击模型:
text
原像:给摘要找消息。
第二原像:给消息找替代品。
碰撞:任意找两条摘要相同的消息。
下一篇将专门推导生日攻击:
text
哈希与认证基础(二):生日攻击:为什么 n-bit 哈希只有 n/2-bit 碰撞安全性
届时再从概率公式、生日悖论和实际实验三个角度,解释碰撞复杂度为什么会从 2 n 2^n 2n 降低到 2 n / 2 2^{n/2} 2n/2。