"我该用 MD5 还是 SHA-256?" 这个问题的答案,完全取决于你要保护的是什么。把 MD5 用在错误的地方,会留下安全漏洞;把 SHA-256 用在 MD5 就够用的地方,会徒增复杂度(不过老实说,性能差异现在很少是真正的问题)。
这篇指南直接切入正题:两种算法各自做什么、为什么 MD5 在安全场景下已经不可用、它在哪些场景仍然完全没问题,以及一个简单的选择决策框架。
MD5 和 SHA-256 的工作原理(简述)
两者都是密码学哈希函数——它们接收任意大小的输入,输出固定长度的"摘要"(digest)或"哈希"。相同的输入总是产生相同的哈希。输入哪怕只改变一个比特,输出也会变成完全不同的哈希。
MD5(Message Digest 5):
- 1992 年由 Ronald Rivest 发布
- 产生 128 位(16 字节)哈希,显示为 32 个十六进制字符
- 示例:
echo -n "hello" | md5→5d41402abc4b2a76b9719d911017c592
SHA-256(Secure Hash Algorithm 256-bit):
- 2001 年由 NIST 作为 SHA-2 家族的一部分发布
- 产生 256 位(32 字节)哈希,显示为 64 个十六进制字符
- 示例:
echo -n "hello" | shasum -a 256→2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
两者都是单向函数:无法通过哈希反推出原始输入。两者都是确定性的:相同输入永远得到相同输出。
关键差异在于它们抵抗碰撞攻击的能力。
为什么 MD5 在安全场景下已经"坏了"
碰撞(collision)是指两个不同的输入产生相同的哈希。一个安全的哈希函数,找到碰撞在计算上应当是不可行的。
MD5 没能守住这条底线。2004 年,研究人员演示了对 MD5 的实际碰撞攻击。到 2008 年,攻击者利用证书签名流程中的 MD5 碰撞伪造了一张恶意 SSL 证书。2012 年,Flame 蠕虫利用 MD5 碰撞伪造了微软的代码签名证书。
实践中的含义:
攻击者可以构造两个具有相同 MD5 哈希的不同文件。如果你的系统用 MD5 哈希来校验完整性或真实性,攻击者就能用一个产生相同哈希的恶意文件替换掉合法文件。
# 两份不同的 PDF,MD5 哈希相同(研究人员已演示)
# 文件 A:合法文档
# 文件 B:恶意文档,但 MD5 相同
md5(file_a) == md5(file_b) # True —— 但内容不同
截至 2026 年,SHA-256 还没有已知的实际碰撞攻击。理论上最优的攻击大约需要 2^128 次运算,远超任何可预见的计算能力。
MD5 在哪些场景仍然可以接受
MD5 并非一无是处。当不需要抗碰撞——即攻击者没有动机或能力针对你的具体场景构造碰撞时,使用 MD5 是没问题的。
文件完整性校验(非对抗场景):
校验下载文件是否完整传输(而非是否被篡改):
# 用发布方提供的校验和验证下载文件
md5sum ubuntu-26.04-desktop-amd64.iso
# 与下载页面给出的哈希对比
如果你只是检查意外损坏(比特翻转、传输不完整),MD5 完全够用。但如果需要确认文件没有被攻击者"故意修改",请使用 SHA-256。
缓存键和去重:
把哈希用作快速查找键,并且即使发生碰撞也能优雅处理:
import hashlib
def get_cache_key(content):
# 这里用 MD5 没问题 —— 最坏情况只是缓存未命中
return hashlib.md5(content).hexdigest()
如果发生碰撞,无非就是缓存未命中或多存了一份冗余副本,没有任何安全影响。
非安全场景的文件去重:
备份工具跳过未修改的文件:
# rsync 风格:跳过内容哈希未变化的文件
find . -type f -exec md5sum {} \; | sort > manifest.md5
遗留系统兼容性:
一些老的 API、包管理器或文件格式只支持 MD5 校验和。这种情况下你只能继续用——但在任何可能的地方加上 SHA-256 校验。
哪些场景必须使用 SHA-256(或更强算法)
数字签名和证书:
任何对数据签名并验证签名的系统,都必须使用抗碰撞的哈希。SHA-256 是最低标准:
# 对发布产物签名
openssl dgst -sha256 -sign private_key.pem -out release.sig release.tar.gz
openssl dgst -sha256 -verify public_key.pem -signature release.sig release.tar.gz
密码存储(但不能直接用):
千万不要用裸 MD5 或 裸 SHA-256 来哈希密码。两者都太快,在现代 GPU 面前暴力破解毫无门槛。请使用专门设计的密码哈希函数:
# 错误 —— 永远不要这样做
import hashlib
password_hash = hashlib.sha256(password.encode()).hexdigest()
# 正确 —— 使用 bcrypt、scrypt 或 argon2
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
Argon2id 是目前密码哈希的黄金标准,bcrypt 仍然被广泛使用且经过充分验证。这些函数的关键特性是"慢"——它们在计算上故意很昂贵,使暴力破解变得不可行。
区块链和加密货币:
比特币和大多数区块链系统使用 SHA-256 进行区块哈希和工作量证明。抗碰撞特性对链的完整性至关重要。
API 请求签名和 HMAC:
在为 API 请求签名或生成 HMAC token 时:
import hmac
import hashlib
# 用于 API 认证的 HMAC-SHA256
signature = hmac.new(
secret_key.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
Git 和内容寻址存储:
Git 已经从 SHA-1 迁移到 SHA-256 作为对象哈希。任何攻击者可能注入恶意内容的内容寻址系统,都应使用 SHA-256。
合规与监管要求:
PCI-DSS、HIPAA、SOC 2 以及大多数合规框架都明确禁止将 MD5 用于密码学用途,并要求使用 SHA-256 或更强算法。
性能对比
随着硬件 SHA 扩展指令(Intel SHA-NI、ARM Crypto Extensions)的普及,MD5 和 SHA-256 的速度差距已经显著缩小:
| 算法 | 吞吐量(带硬件加速的现代 CPU) | 输出大小 | |-----------|--------------------------------------|-------------| | MD5 | ~1.5 GB/s | 128 位 | | SHA-256 | ~1.2 GB/s(带 SHA-NI) | 256 位 | | SHA-256 | ~500 MB/s(纯软件实现) | 256 位 |
在支持 SHA-NI 的现代硬件上(2017 年以来大多数 CPU 都支持),SHA-256 的速度已经快到几乎所有应用都无需在意性能差距。你需要达到每秒几十 GB 的哈希吞吐,MD5 的速度优势才有意义——而到了那个量级,你需要回答的是更大的架构问题。
对于常见场景——哈希 API 负载、校验下载文件、生成缓存键——差异只有微秒级。请根据安全需求选择,而不是速度。
决策框架
用这个流程来判断:
1. 是否用于安全场景?(认证、签名、证书、对抗性完整性校验)
- 是 → 至少使用 SHA-256,绝不用 MD5。
- 否 → 进入问题 2。
2. 是否用于密码存储?
- 是 → 两者都不要用,请用 Argon2id 或 bcrypt。
- 否 → 进入问题 3。
3. 攻击者是否能从构造碰撞中获益?(例如用匹配合法哈希的恶意文件进行替换)
- 是 → 使用 SHA-256。
- 否 → MD5 可以接受(缓存键、非对抗性校验和、去重)。
4. 是否有合规或监管要求?
- 是 → 按标准来(几乎都是 SHA-256+)。
- 否 → 基于问题 1-3 自己判断。
拿不准时,就用 SHA-256。 性能代价可以忽略,输出仍然紧凑(64 个十六进制字符),而且你永远不用担心某个"非安全"用例日后是否会变成安全相关。
在本地生成哈希
无论你需要为缓存键生成 MD5 校验和,还是为文件校验生成 SHA-256 哈希,都可以在不把数据发送到服务器的情况下完成。
SHA-256 哈希生成器 和 MD5 哈希生成器 都完全使用 Web Crypto API 在你的浏览器中处理输入。你的文本、文件或 API 负载永远不会离开你的机器——这在哈希 API 密钥、个人数据或专有代码等敏感内容后再发给第三方时尤为重要。
命令行快速哈希:
# SHA-256
echo -n "your input" | shasum -a 256
# MD5
echo -n "your input" | md5
# 文件哈希
shasum -a 256 important-file.pdf
md5sum important-file.pdf # Linux
总结一下:MD5 是适合非对抗场景的快速校验和;SHA-256 是任何安全相关场景的基线;至于密码,两者都不要用——请选择 Argon2id 或 bcrypt。拿不准时,SHA-256 是没有副作用的安全默认选择。