什么是哈希碰撞?MD5 和 SHA-256 安全性深度解析
下载一个安装包时,发布方通常会附上一串哈希值。你算一遍本地文件的哈希,对比一下,相同就认为文件没被篡改。这套机制的前提是:两个不同文件几乎不可能算出同一个哈希。但「几乎不可能」取决于算法。MD5 碰撞成本早已落到秒级;SHA-1 在 2017 年被实际攻破,2020 年连 chosen-prefix 也进入可负担区间;SHA-256 至今没有公开碰撞实例。这篇拆清鸽巢原理、生日攻击、真实案例,以及 HMAC 语境下为什么「哈希已破」不等于「带密钥的 MAC 立刻垮掉」。想亲手算哈希?用 MD5 哈希生成器 或 SHA-256 哈希生成器;要和官方公布值比对,用 哈希校验器。
什么是哈希碰撞
哈希函数把任意长度的输入压缩成固定长度的输出。MD5 永远输出 128 位,SHA-256 永远输出 256 位。输入空间无限、输出空间有限,根据鸽巢原理,碰撞必然存在。
碰撞的定义很直接:两个不同的输入,产生相同的哈希值。
hash("文件A") = 9e107d9c...
hash("文件B") = 9e107d9c... ← 与上面相同,但 文件A ≠ 文件B
存在本身不可怕。真正的问题是找到碰撞的计算成本。若平均需要约 2^128 次运算,按当前公开算力不可达,那函数在工程意义上就可视为抗碰撞。
密码学里还要分清三种「找哈希」问题:
| 问题 | 攻击者目标 | 理想 N 位哈希的量级 |
|---|---|---|
| 碰撞(collision) | 任意两个不同输入同哈希 | ~2^(N/2)(生日界) |
| 第二原像(second preimage) | 给定消息 m,找 m'≠m 且哈希相同 | ~2^N |
| 原像(preimage) | 给定哈希,反推任一输入 | ~2^N |
下载校验、证书签名更怕碰撞与第二原像;「MD5 解密」类需求其实是原像/彩虹表问题——见 MD5「解密」与碰撞。
生日悖论与生日攻击
直觉说:128 位空间里,碰撞概率是 2 的负 128 次方。这忽略了多次尝试的累积效应。
经典生日悖论:23 人的房间里,有两人生日相同的概率超过 50%。不是 365/2,而是远更小。原因是「任意两人配对」的数量随人数平方级增长。
对输出空间大小为 N(例如 MD5 的 N=2^128)的哈希,随机取样约 n 次时,至少出现一对碰撞的概率近似为:
P ≈ 1 − exp( − n² / (2N) )
要让 P≈50%,需要 n ≈ 1.2 √N,也就是常说的生日界:约 2^(N/2) 次哈希,而不是 2^N。
- 128 位(MD5):生日攻击约 2^64
- 160 位(SHA-1):约 2^80
- 256 位(SHA-256):约 2^128
所以 SHA-256 的碰撞抗性按生日界计是 128 位,不是 256 位;256 位指的是原像抗性量级。
生日攻击只是「随机撞库」的下界。真正危险的是结构化碰撞算法:利用压缩函数差分,把成本压到远低于 2^(N/2)。MD5、SHA-1 正是这样倒下的。
identical-prefix 与 chosen-prefix
工程上常见两类碰撞构造:
- Identical-prefix:两个文件共享同一前缀,攻击者只在后面插入精心构造的差分块。SHAttered 的两个 PDF 属于这一类。
- Chosen-prefix:攻击者可分别为「合法内容」和「恶意内容」选定不同前缀,再补碰撞后缀。这对伪造证书、并立两份合同尤其致命——2008 年 rogue CA、2012 年 Flame,以及 SHA-1 的 Shambles(2020) 都依赖 chosen-prefix 能力。
校验下载时若仍用 MD5:攻击者不必「撞出和官网一模一样的恶意包」,而可以构造「恶意包与某个看似合法的包同哈希」,再诱使你信任那条哈希通道。
MD5:从破裂到日常可利用
MD5 于 1992 年前后进入广泛使用。关键时间线:
| 年份 | 事件 |
|---|---|
| 2004 | 王小云团队公布实用 MD5 碰撞 |
| 2007 | chosen-prefix 碰撞演示,可控制双文件前缀 |
| 2008 | 利用 MD5 碰撞伪造 CA 证书 |
| 2012 | Flame 用 MD5 碰撞伪造微软代码签名证书 |
| 约 2010 年代中后期至今 | 普通 CPU 上数秒级即可产出 identical-prefix 碰撞对 |
2008 年攻击尤其严重:两份内容不同的证书同 MD5,CA 签了「合法」那份,签名对「恶意」那份同样有效。
2026 年现状:MD5 在任何安全敏感场景都不应再承担「抗伪造指纹」的角色。非对抗场景(去重、缓存键、自有系统内快速变更检测)仍可使用,但下载完整性、证书、密码、API 防伪不要再用。更完整的用途边界见 MD5 指南。
SHA-1:SHAttered 与 Shambles
SHA-1 输出 160 位,理想生日界约 2^80。实际更弱。
- 2017 年 SHAttered(Google + CWI):两个不同 PDF 同 SHA-1,消耗约 6500 CPU 年 + 110 GPU 年,成本约十余万美元量级。主流浏览器与证书生态随即淘汰 SHA-1 签名。
- 2020 年 Shambles:chosen-prefix 碰撞成本降到可被资金充足的攻击者承担的量级;Git 等依赖「内容寻址 = SHA-1」的系统加速向 SHA-256 迁移。
shattered-1.pdf → 38762cf7f55934b34d179ae6a4c80cadccbb7f0a
shattered-2.pdf → 38762cf7f55934b34d179ae6a4c80cadccbb7f0a
2026 年现状:新系统不要用裸 SHA-1 做完整性或签名摘要。遗留 git 对象、旧支付接口仍可能要求 SHA-1——那是兼容,不是安全背书。站内 SHA-1 工具 也按「存量对接」定位。
SHA-256:至今无公开碰撞
SHA-256 输出 256 位,生日界碰撞抗性 2^128。截至 2026 年,没有任何公开的 SHA-256 碰撞实例。
暴力搜索碰撞按当前全球算力量级遥不可及;这不表示「数学上不存在碰撞」,只表示「找出来」在可见未来不现实。量子侧主要讨论 Grover 对原像的平方加速,碰撞故事仍与对称原语寿命绑定——延伸阅读:哈希算法怕量子计算吗。
算法对比与选型细节见 SHA-256 指南、SHA-256 vs MD5。
HMAC 语境:碰撞破了,MAC 是否立刻报废?
HMAC(Hash-based Message Authentication Code)是「带密钥的哈希」:
HMAC(K, m) = H( (K' ⊕ opad) ‖ H( (K' ⊕ ipad) ‖ m ) )
它解决的是另一类问题:在共享密钥的前提下,证明消息未被篡改且来自持钥方。裸哈希做下载校验时,任何人都能重算;HMAC 则不能。
和碰撞相关的几条工程判断:
- 威胁模型不同:公开的 MD5/SHA-1 碰撞攻击针对的是无密钥的摘要。HMAC 的伪造通常需要未知密钥下的相关密钥攻击或密钥泄露;「能撞 MD5」≠「能伪造 HMAC-MD5」。
- 长度扩展 ≠ 碰撞:Merkle–Damgård 结构的裸哈希(MD5/SHA-1/SHA-256)存在长度扩展问题;HMAC 的双层填充正是为挡住这类滥用。需要 MAC 时用 HMAC,不要手写
H(key ‖ message)。 - 算法选型仍要前进:HMAC-MD5 / HMAC-SHA1 在公开文献里没有像裸碰撞那样「几秒打穿」的实用破法,但标准与合规已淘汰它们。新项目用 HMAC-SHA256(或更强)。工具与原理见 HMAC 生成器 与 HMAC 指南。
- 别把场景弄反:Webhook/API 签名走 HMAC;ISO/安装包完整性走公布的 SHA-256 +(更好)签名。用 哈希校验器 比对下载哈希,不要拿它当「带密钥的 API 验签台」。
一句话:碰撞摧毁的是「摘要可当防伪指纹」的信任;HMAC 额外要求密钥。密钥丢了两者都完;密钥还在时,仍应把底层哈希升级到 SHA-256 一族。
Python 演示:概念与文件校验
下面用 MD5 只演示 API,不表示你能在本机随机撞出碰撞:
import hashlib
def md5_hex(s: str) -> str:
return hashlib.md5(s.encode()).hexdigest()
a, b = "hello", "world"
print(md5_hex(a)) # 5d41402abc4b2a76b9719d911017c592
print(md5_hex(b)) # 7d793037a0760186574b0282f2f435e7
def verify_collision(x: str, y: str) -> bool:
if x == y:
return False
return md5_hex(x) == md5_hex(y)
# 真实碰撞对是差分构造的字节序列,不是随机字符串
# 可检索公开的 MD5 collision example(Marc Stevens 等)自行验证
工程里校验文件更常见的是流式 SHA-256:
import hashlib
def file_sha256(path: str) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()
expected = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
actual = file_sha256("./download.bin")
print("校验通过" if actual == expected else "文件已损坏或被篡改")
浏览器里不想开终端时:把文件拖进 哈希校验器,算法选 SHA-256,粘贴发布方期望值即可。
真实世界的后果
文件完整性:MD5 校验下载时,攻击者可构造与「某个可信哈希」碰撞的恶意安装包。哈希一致,跑的却是木马。
代码签名与证书:CA、驱动签名、公证体系依赖哈希。SHA-1 被弃用,正是因为碰撞成本已进入攻击者预算。
Git 对象:历史用 SHA-1 标识 commit/tree/blob;SHAttered / Shambles 之后,官方路线转向 SHA-256。
区块链:比特币等重度依赖 SHA-256 的抗碰撞与原像假设;这也是生态迟迟不回到短摘要的原因之一。
按场景选算法
| 场景 | 算法选择 | 理由 |
|---|---|---|
| 文件完整性校验 | SHA-256(或更强) | MD5/SHA-1 可被构造碰撞 |
| 密码存储 | bcrypt / Argon2 | 快哈希利于爆破,需要慢 KDF + 盐 |
| 数字签名摘要 | SHA-256 或 SHA-3 | 主流 CA / 库默认路径 |
| 消息认证(API/Webhook) | HMAC-SHA256 | 共享密钥 + 抗长度扩展结构 |
| 非安全去重 / 缓存键 | MD5 或 xxHash | 无对抗伪造动机时可换速度 |
| 内容寻址(类 Git) | SHA-256 | SHA-1 不再适合当唯一信任根 |
密码存储是常见误区:SHA-256 太快。需要慢哈希时用站内 bcrypt 工具。哈希与加密的分工见 哈希 vs 加密。
用 ToolVault 计算和校验
以下工具均在浏览器本地运行,不上传原文:
- MD5 哈希生成器:非安全去重、缓存键、对接旧系统
- SHA-256 哈希生成器:SHA-1/256/384/512 文本摘要(完整性与签名场景用 256+)
- 哈希校验器:文件或文本 + 期望哈希,自动比对(支持 MD5 与 SHA 家族;安全下载请选 SHA-256)
- HMAC 生成器:带密钥的消息认证,不是裸哈希比对
操作步骤也可对照 如何校验哈希。
FAQ
SHA-256 碰撞概率有多高?
随机搜索量级约 2^128(生日界)。对当前现实攻击者不可达。理论上碰撞存在;难的是构造出来。
MD5 还能用吗?
非安全场景可以:缓存键、去重、无对抗动机的快速指纹。证书、密码、对抗性下载完整性校验不要再用。
下载校验该用 MD5 还是 SHA-256?
发布并校验 SHA-256(或更强)。MD5 的 chosen-prefix 碰撞可伪造「哈希一致但内容不同」;一致的 MD5 不能证明文件就是你想要的那份。
HMAC-MD5 是不是也彻底不能用了?
与裸 MD5 不同:没有密钥就无法直接套用公开碰撞去伪造 HMAC。但新项目没有理由继续用 HMAC-MD5/SHA1——标准与库都在推向 HMAC-SHA256。遗留验证用 HMAC 生成器 即可,设计新协议时直接上 SHA-256。
怎么快速核对发布方给的哈希?
把文件或文本与期望摘要贴进 哈希校验器,或用 SHA-256 哈希生成器 现算后人工对比。两者都在本地浏览器运行。
总结
哈希碰撞问的不是「会不会发生」,而是「找到它要花多少钱」。MD5 已便宜到可批量利用;SHA-1 在 SHAttered / Shambles 之后离开安全默认;SHA-256 的碰撞成本仍在 2^128 量级。做完整性与签名摘要时用 SHA-256 或更强;做 API 防伪用 HMAC-SHA256;把 MD5 留在去重和缓存这类不依赖抗伪造的场景。
ToolVault 工具匣 出品的开发者指南。相关工具:MD5 哈希、SHA-256 哈希、哈希校验器、HMAC 生成器。相关阅读:MD5 指南、MD5「解密」与碰撞、SHA-256 指南、HMAC 指南、哈希 vs 加密、哈希算法怕量子计算吗。更多工具见首页。
实现注记
把哈希工具搬到浏览器时测过:SubtleCrypto 算 SHA-256 的吞吐大约是纯 JS 的 8–15 倍,100MB 输入从分钟级降到秒级。浏览器不把 MD5 放进 SubtleCrypto,主要是避免给已废弃算法官方快车道——所以 MD5 路径用 SparkMD5,SHA 家族走 crypto.subtle.digest。哈希校验器 一次只跑你选中的一种算法(不是并行打出五份摘要),比对前会做大小写与首尾空白归一化;选 MD5/SHA-1 时界面会提示碰撞风险,避免「匹配 = 安全」的误读。
相关工具
相关文章
后量子迁移自查清单:从算法盘点到 hybrid TLS,2026 年该做完的 12 件事
后量子密码(PQC)迁移的实操自查框架:三张监管时刻表为什么让 2026 成为起点、算法盘点(CBOM)怎么做、按 HNDL 暴露度排优先级、X25519+ML-KEM 混合 TLS 怎么开,以及 crypto-agility 的工程落地。附 12 项可勾选 checklist。
Passkeys 完全指南:为什么服务器再也存不了你的密码副本
Passkey(WebAuthn/FIDO2)加深:注册与断言仪式、平台认证器与漫游密钥、同步通行密钥取舍、与密码/TOTP/JWT 的关系,以及 origin 绑定如何结构性抗钓鱼。服务器只存公钥;站内无独立 Passkey 工具页的说明。
ML-KEM 入门:Kyber 是怎么进到你的 TLS 握手里的
后量子密钥封装 ML-KEM(FIPS 203,前身 Kyber)的工程向解读:Kyber 到标准名的变迁、封装/解封装语义与 IND-CCA、三档参数尺寸、X25519+ML-KEM 与 TLS 1.3 HKDF 密钥调度的混合方式、ClientHello 膨胀中间盒坑、OpenSSL 3.5 / nginx 开法,以及浏览器侧实现边界。