正在加载,请稍候…

MD5 碰撞:为什么不应再使用 MD5 进行安全保护

了解 MD5 碰撞漏洞、真实世界攻击案例,以及为什么现代应用必须使用 SHA-256 或更强的哈希算法。

MD5 曾经是文件完整性校验和数字签名的首选哈希函数。但自 2004 年以来,研究人员已经展示了能够打破其安全保证的实用碰撞攻击。本文解释 MD5 碰撞的工作原理、为何重要,以及你应该使用什么替代方案。

服务器机架警示灯

什么是哈希碰撞?

哈希碰撞是指两个不同的输入产生相同的哈希输出。对于安全的哈希函数,找到碰撞在计算上应该是不可行的。MD5 产生 128 位(16 字节)的哈希,因此生日攻击的界限是 2^64 次运算——但实际攻击要快得多。

MD5 碰撞如何工作

MD5 使用 Merkle-Damgård 结构:消息被填充到 512 位的倍数,分成块,并通过压缩函数迭代处理。压缩函数对 128 位内部状态(四个 32 位寄存器 A、B、C、D)执行 64 轮非线性运算。

王小云 2004 年的差分攻击利用了压缩函数中的弱点。通过精心选择两个消息块之间的差异,攻击者可以使内部状态差异在 64 轮后抵消,产生相同的最终哈希。这比暴力破解高效得多——攻击复杂度约为 2^39 次 MD5 运算,在现代 PC 上不到一小时即可完成。

现实世界的碰撞工具

fastcollHashClash 这样的工具实现了这些攻击。例如,使用 fastcoll:

fastcoll_v1.0.0.5.exe -p original.pdf -o collision1.pdf collision2.pdf

这会生成两个具有相同 MD5 哈希但内容不同的 PDF 文件。

为什么 MD5 在安全上被破解

MD5 未能满足安全所需的抗碰撞性。后果是严重的:

性质 用于 MD5 状态
抗碰撞性 数字签名、证书 ❌ 已破解(2^39 次运算)
抗第二原像 文件完整性(恶意) ❌ 已削弱
抗原像 密码哈希 ⚠️ 仍然较强(~2^123)但不推荐

MD5 消亡的时间线

  • 2004:王小云在 Crypto 2004 上展示实用的 MD5 碰撞。
  • 2008:Sotirov 等人利用 MD5 碰撞伪造 RapidSSL 中间 CA 证书,展示了现实世界的影响。
  • 2012:Flame 恶意软件使用 MD5 选择前缀碰撞伪造微软代码签名证书。
  • 2017:SHA-1(比 MD5 更强)也被 SHAttered 攻击破解;行业全面迁移到 SHA-2。

MD5 何时仍可接受(何时不可)

使用场景 安全? 理由
数字签名/证书 ❌ 否 碰撞允许伪造
密码存储 ❌ 否 使用 bcrypt、scrypt、Argon2
文件完整性(防恶意篡改) ❌ 否 攻击者可替换文件和哈希
文件完整性(意外损坏) ⚠️ 弱 为面向未来使用 SHA-256
非安全哈希表键 ✅ 是 碰撞导致性能问题而非安全漏洞

常见陷阱

  • 认为 MD5 对安全“足够好”。 碰撞攻击是实用的——坚定的攻击者可以伪造签名或证书。
  • 使用 MD5 进行密码哈希。 即使没有碰撞,MD5 也很快,容易受到暴力破解;始终使用慢速加盐的哈希如 Argon2。
  • 忽略生日界限。 对于 128 位哈希,抗碰撞性只有 64 位——现代硬件可行。
  • 认为“我只需要抗原像性。” 许多协议依赖抗碰撞性;如果攻击者可以创建两个具有相同哈希的文档,他们就可以用一个替换另一个。

应该使用什么替代方案

  • SHA-256(SHA-2 家族):当前标准,没有已知的实际攻击,256 位输出。用于签名、证书和文件完整性。
  • SHA-3:替代设计(Keccak),不易受长度扩展攻击。适合新协议。
  • BLAKE3:非常快,安全,支持流式处理。适合哈希大文件。
  • HMAC-SHA256:对于消息认证,始终使用 HMAC 而不是裸哈希,以避免长度扩展攻击。

工作示例:从 MD5 迁移到 SHA-256

假设你有一个文件上传系统,使用基于 MD5 的文件名和校验和。以下是迁移方法:

之前(不安全)

import hashlib

def store_file(content):
    md5 = hashlib.md5(content).hexdigest()
    filename = f"{md5}.bin"
    with open(filename, 'wb') as f:
        f.write(content)
    return md5

def verify_file(filename, expected_md5):
    with open(filename, 'rb') as f:
        content = f.read()
    actual_md5 = hashlib.md5(content).hexdigest()
    return actual_md5 == expected_md5

之后(安全)

import hashlib

def store_file(content):
    sha256 = hashlib.sha256(content).hexdigest()
    filename = f"{sha256}.bin"
    with open(filename, 'wb') as f:
        f.write(content)
    return sha256

def verify_file(filename, expected_sha256):
    with open(filename, 'rb') as f:
        content = f.read()
    actual_sha256 = hashlib.sha256(content).hexdigest()
    return actual_sha256 == expected_sha256

对于现有的 MD5 哈希文件,重新计算 SHA-256 哈希并更新引用。你也可以在过渡期间同时存储两个哈希。

在我们的哈希文本工具中尝试比较 MD5 和 SHA-256 对同一输入的输出。

常见问题

MD5 是否完全被破解,适用于所有目的?

不。MD5 仍然提供抗原像性(约 2^123 次运算),对于非安全用途如意外损坏的校验和或作为哈希表键是安全的。然而,SHA-256 同样快且消除了任何疑虑。

如果我加盐,可以使用 MD5 吗?

不能。盐不能防止碰撞攻击——它只影响原像攻击。仍然可以找到加盐 MD5 的碰撞。

今天找到 MD5 碰撞需要多长时间?

在现代消费级 GPU(例如 RTX 4090)上,使用 fastcoll 或 HashClash 等工具可以在不到一分钟内找到单个碰撞。云实例使其更便宜。

SHA-1 呢?也被破解了吗?

是的。SHA-1 碰撞在 2017 年被演示(SHAttered 攻击)。SHA-1 不应再用于任何安全目的。Git 仍然使用 SHA-1 作为对象 ID,但威胁模型不同;新项目应使用 SHA-256。

有没有抗量子计算的哈希函数?

SHA-256 和 SHA-3 被认为对 Grover 算法(将安全级别减半)具有量子抗性。对于 256 位哈希,量子攻击仍需要 2^128 次运算——不可行。BLAKE3 也提供 256 位安全性。

结论

MD5 的抗碰撞性已被破解,将其用于安全目的是不负责任的。始终为新应用选择 SHA-256 或更强的哈希。对于现有系统,尽快迁移——碰撞攻击的成本对于有动机的对手来说足够低。使用我们的哈希文本工具验证 MD5 和 SHA-256 输出之间的差异。