MD5 还是 SHA-256?校验和到底该用哪个
「MD5 不安全,别用」是你会听到的建议,它对了一半——而这恰恰让它危险。MD5 对一个校验和来说是够用还是鲁莽,完全取决于你得先回答的一个问题:有没有人想骗你?这篇把校验和干的两件事拆开,指出每件各自需要哪个哈希,并讲清为什么「换个更强的哈希」往往是错的解法。
你正看着一个列了 MD5 的下载页面。或者,你正在决定自己的发布包旁边该放什么。不管哪种,同一个问题都会冒出来:MD5 还能用吗,还是得上 SHA-256?一搜,你会得到一个很响亮的答案——「MD5 已经不安全了,别用」。这话对了一半。错的那一半,恰恰是把人误导的地方。盲目照做,会有两种结局。一种是你在 MD5 本来够用的地方白费功夫。另一种更糟:你换上 SHA-256、以为安全了,却没发现你真正需要的根本不是一个更强的哈希。
诚实的说法是这样。「用哪个哈希」不是「哪个算法更好」的问题,而是「你在防谁」的问题。人们发布或核对一个哈希,通常是为了两个很不一样的目标,而 MD5 对其中一个完全够用,对另一个则毫无用处。在你搞清楚自己追求的是哪个目标之前,任何答案都谈不上对。
先交代一句范围:本文讨论的是文件校验和——确认数据没有被改动。存储密码是另一个问题,答案也不同。那种场景应当使用专门的密码哈希方案,比如 Argon2id 或 bcrypt,绝不能用 MD5,也不能用普通的 SHA-256。
一个核心:两件任务,一个问题
你核对一个已发布的哈希,是为了达成下面两个目标之一。这两个目标不一样,而裸哈希对它们的作用也很不同。
- 查出意外损坏。连接断开把文件截断。磁盘不稳翻转了一个比特。镜像发来一个过期副本。没有谁「设计」出这个差异——它是随机的破坏,而且你信任给你参考值的那个来源。这是防意外的完整性,裸哈希自己就能搞定。
- 抵御蓄意篡改。有人想让你接受一个恶意文件,并正主动地设法让它的哈希与你期望的那个一致。这关乎真实性,而这里裸哈希只是答案的一部分:它是必要的一层,但不是防护的全部。要完成这件事,还得在上面加一个签名(或 HMAC)。
整个 MD5 与 SHA-256 的抉择,都取决于一个问题:有没有对手?对第一个目标,没有——MD5 就能胜任。对第二个目标,有——MD5 会直接失守,而 SHA-256 成为哈希这一层的最低基线。但把第二个目标的要点记牢:哈希是这道防护的一层,永远不是它的全部。把威胁模型判对,算法自然就定了。判错,你要么在一个不需要的问题上小题大做,要么指望一个哈希去做任何裸哈希都做不到的事。
「MD5 不安全」到底是什么意思
这个「破」有个精确的名字,而这份精确正是关键。一个密码学哈希本该具备几个性质。MD5 恰好丢了其中一个,保住了其余的。
- 抗碰撞——没人能找到两个不同的输入、产出同一个哈希。MD5 丢的就是这个。现成的工具如今就能在一台笔记本上造出 MD5 碰撞。(不过要让碰撞的两个文件同时具有特定、可用的内容——也就是选定前缀碰撞——难度大得多,具体多难取决于攻击场景。)SHA-1 也倒了:2017 年的 SHAttered 攻击造出了一次真实的 SHA-1 碰撞。
- 抗原像与抗第二原像——给定一个哈希,没人能反推出一个能产出它的输入(原像);给定一个文件,没人能再找到另一个同哈希的文件(第二原像)。这两条 MD5 实质上都还守着。没有可行的攻击能拿着一个已存在的具体 MD5 值、或一个给定文件,去构造出一个匹配。
这个区分,正是 MD5 至今在第一件任务上还管用的原因。意外损坏不是一个精心构造的碰撞。一个被截断的下载、一个翻转的比特,是一个变了的文件——而不是两个刻意凑到一起的文件。输入一变,MD5 的输出就可靠地随之改变。拿一个可信发布方早已算好的哈希去验下载,这是一个替换问题——它依赖的是抗原像与抗第二原像,而这两条 MD5 并没丢。但这并不说明 MD5 适合做认证,因为它的抗碰撞已经被攻破,而真实攻击正是从那道门进来的。
那么,这个碰撞的破口到底让攻击者能做什么?他需要两个文件,而且两个都由他掌控。设想一个会给你提交的任何文件盖章签名的服务。攻击者构造一个看似无害的文件,再构造一个与它 MD5 相同的恶意孪生文件。他提交无害的那个、拿到签名,再把孪生的那个发出去——签名照样通过,因为哈希一致。这不是理论。Flame 恶意软件就用一次 MD5 碰撞,伪造了微软的代码签名证书。研究者用同样手法造出过伪造的 CA 证书。套路始终一样:攻击者必须能影响那个「好」文件,而不只是坏文件。只要你的流水线留下这个口子,一个抗碰撞已破的哈希就是一个敞开的缺口——而堵上它不花任何成本,所以没有理由留着。
先给一张全局小地图
细节之前,先用一张表把这些名字理清楚:
| 算法 | 输出 | 抗碰撞情况 | 什么时候用它 |
|---|---|---|---|
| CRC32 | 32 位 | 无(本非其职) | 只求速度;查意外错误;完全不涉及安全 |
| MD5 | 128 位 | 已攻破 | 遗留的非对抗校验、去重、缓存键 |
| SHA-1 | 160 位 | 已攻破 | 新项目别用;只为兼容老系统 |
| SHA-256 | 256 位 | 暂无实用攻击 | 公开或需签名的工作流的安全哈希默认 |
| SHA-512 | 512 位 | 暂无实用攻击 | 安全性与 SHA-256 相同,64 位上有时更快 |
| BLAKE3 | 256 位起 | 暂无实用攻击 | 追求极速的新系统 |
这篇剩下的内容,其实都在教你怎么把第三、第四列读对。
MD5 什么时候是真的够用
撇开潮流看。MD5 在「你有充分理由相信没有对手,而又看重速度、或手里本就有 MD5 值」时,是个合理选择。
- 查出来自可信来源的意外损坏。你跨一条不稳的网络拷了个文件,或从自己内部镜像拉了下来,想确认它完整到达。
- 去重与缓存键。你用哈希判断「这两块数据是否相同」,或者给缓存做键。这个系统里没人在蓄意制造碰撞。
- 核对别人给你的 MD5。一个你信任的项目,通过一条你信任的通道,只给了 MD5。那么核对它,仍能查出它本该查出的意外损坏。它不是毫无价值。只是它不构成对「被篡改文件」的防护。
有一句得说清楚。这里的「够用」是指没错,不是指新项目推荐。而且如果你要的只是快速查出意外损坏,一个非密码学校验和,比如 CRC32 或 xxHash,往往更合适。它为这一件事量身打造、更快,也不带一层日后会被误读的「安全」标签。
什么时候必须用 SHA-256(或更强)
只要攻击者可能影响输入或文件,规矩就很简单:哈希这一层至少用 SHA-256。你碰上它的次数,比你以为的多——它就藏在你日常使用的多数系统底下。
- 可能被篡改的公开下载。任何用户从开放互联网获取的东西,只要一个镜像或中间人可能替换文件。
- 软件供应链。一个 Docker 镜像靠它的 SHA-256 摘要(
sha256:…)锁定。一个 npm 包会记录一个 Subresource Integrity 哈希,通常是以 base64 书写的 SHA-512。它们的存在,就是为了让被替换的产物无法通过校验。 - 与安全相关的哈希标识符。TLS 证书指纹、被签名的内容,以及任何必须抵御蓄意碰撞的哈希派生 ID。(密钥令牌是另一回事——那应当来自密码学安全的随机数生成器,而不是靠挑选某个哈希算法。)
- 内容寻址,那里的碰撞是一个安全漏洞、而不只是一个麻烦。这正是 Git 迁离 SHA-1 的原因:两个不同的提交撞上同一个 ID,会击穿这个模型最核心的假设。
对新项目,哪怕今天看着不像有对手,也默认 SHA-256。威胁模型会随时间变化。一个内部工具被暴露出去。一条「可信」的流水线接进了不可信的输入。而在支持 SHA 指令的处理器上,SHA-256 相对 MD5 的成本几乎为零。全新项目里,几乎没有理由再选 MD5。
「更长的哈希不是更安全吗」——位数与速度
两种直觉会把人推向 SHA-512,或者「最强的那个」。两种都值得纠正。
第一,更长不自动等于更安全。MD5 和 SHA-1 的问题,是抗碰撞出现了结构性的攻破——不是位数太少。更多的输出位,确实会抬高一次通用的、暴力碰撞搜索的成本,但 SHA-256 在这方面本就有巨大的安全余量,你用不完。更长的输出做不到的,是修复结构性弱点,也无法提供真实性。SHA-512 修不了 SHA-256 缺的东西,因为 SHA-256 并不缺。
第二,最强的哈希不是最慢的,而 MD5 也早已不是最快的。现代 CPU 内建了哈希指令——x86 上的 SHA-NI、ARM 上的加密扩展。有了它们,SHA-256 每秒能跑好几个 GB,常常追平甚至超过 MD5。BLAKE3 更进一步,利用 SIMD 和多核并行,通常是你能选的最快的密码学哈希。如果你真的只要吞吐、完全不需要安全,一个硬件 CRC32 比它们都快。「MD5 更快」在十年前是个真实的论点。在今天的硬件上,它很少还成立。
所以选 SHA-512,要为一个真实的理由——更长的摘要,或者 64 位服务器上的速度优势。而不是为一个你并不会得到的安全升级。
陷阱:更强的哈希不等于防篡改
这是「把 MD5 换成 SHA-256」会悄悄鼓励的误解,所以得直说。单是升级算法,本身并不防篡改。这是两根不同的轴,把它们混为一谈,正是「我们用了 SHA-256」变成一种虚假安全感的原因。
- MD5 → SHA-256 提升的是抗碰撞。它只在攻击者构造输入时才有意义。
- 裸哈希 → 数字签名 才提供真实性。只要攻击者能同时改掉文件和它公布的哈希,它就派上用场。
设想一个下载和它的校验和放在同一个页面。攻击者控制了那个页面。他替换文件,并把哈希改成与之一致。这时那个哈希是 MD5 还是 SHA-512,毫无区别。检查会通过。你被骗了。解法不是一个更长的摘要。对公开分发而言,是一个数字签名——GPG,或 Ed25519、minisign:发布方用私钥签名,你用他们广为公布的公钥验证,这下攻击者没有密钥就伪造不出那个参考值。HMAC 解决的是同一个问题,但只适用于双方已经共享秘密密钥的封闭场景——比如 webhook,或两个内部服务之间——因此它不适合公开下载。这正是 Linux 发行版发布一个带签名的校验和文件、而不只是一个更强的哈希的原因。(如果两个值压根就对不上,那是另一个问题——校验和为什么对不上里走了一遍。)
一趟走完的抉择
按顺序回答,算法自然就定了。
- 对手能构造输入、或替换文件吗?如果能 → 哈希这一层用 SHA-256 或更强。没有例外。MD5 和 SHA-1 出局。
- 这是公开下载、或任何沾安全的东西吗?把它当成第 1 问的「能」——发布 SHA-256,并给它签名。击败被篡改参考值的是签名,不是哈希长度。
- 纯粹查意外损坏、没有对手、而且你掌控或信任来源?那 MD5 就够用。尽管一个非密码学校验和(CRC32、xxHash)通常更合适也更快——而任何新东西反正默认 SHA-256 更稳妥。
- 在验别人发布的文件?用他们发布时所用的那个算法。如果只有 MD5、而你信任来源和通道,它仍能确认意外完整性。但优先选那些同时给你 SHA-256 和签名的发布方。
自己算一个哈希,命令行就够了。Linux 上:md5sum file、sha256sum file。macOS 上工具名不同:md5 -r file、shasum -a 256 file。Windows PowerShell 上:Get-FileHash file -Algorithm SHA256。想在浏览器里快速核对,把文件或文本放进 哈希生成器。它一次计算一个算法,所以选中参考值点名的那个(MD5、SHA-256、SHA-512、SHA-3、BLAKE2b/BLAKE2s/BLAKE3 等等),再切换算法逐一对比。注意它输出的是十六进制,而不是 SRI 需要的 base64——所以遇到 integrity="sha256-…" 这样的值,你得把一边转换一下。
四个问题底下,是开头那个核心。这是一个威胁模型的判断,不是一份排名。MD5 不是普遍地「不安全」,SHA-256 也不是普遍地「答案」。MD5 只在一件事上被攻破——抵御一次蓄意碰撞——而这一件,恰恰是安全校验和需要、意外损坏检查不需要的。先判断有没有对手。而一旦有,记住:哈希是基线,从来不是防护的全部。