去年冬天我给 NAS 做了次冷备份,两块 4TB 盘,rsync 跑了将近十一个小时,跑完只看了退出码是 0 就收工。两个月后要恢复一个记账用的压缩包,解压时报 unexpected end of archive——那个包本来 2.1GB,盘里只剩 1.4GB,中间某次写入悄悄断了。我没有任何办法证明其它三百多个包是好的。从那以后我的备份流程里多了一步:算完哈希、清单存到另一台机器上。哈希到底能证明什么、对哪一类问题无能为力,是那之后才弄清的。
一、哈希是内容的指纹,不是加密
MD5 把任意长度的数据压成 128 位、通常写成 32 个十六进制字符的一串。三个特性决定了它能做什么:输出定长,一个 3 字节文本和一个 4GB 镜像算出来的结果一样长;单向不可逆,拿到 5d41402abc4b2a76b9719d911017c592 也推不回原文(它是 hello 的 MD5,空字符串的是 d41d8cd98f00b204e9800998ecf8427e);雪崩效应,改一个比特,结果面目全非。
所以"MD5 加密"这个说法本身是错的,它只能回答两个问题:这两份数据是不是同一份?这份数据还是不是我拿到的那一份?想保密要靠加密,想存口令要靠带盐的慢哈希,两者都不该拿 MD5 顶替。
二、校验的关键其实是"期望值从哪来"
很多人把流程记成"下载—算哈希—比对",但决定结论价值的只有第三步里参照物的来源。分开看:
| 期望值来源 | 校验通过说明什么 | 校验失败说明什么 |
|---|---|---|
| 官网 HTTPS 页面单独公布 | 文件与官网那份一致,没在传输中坏掉 | 传输截断、被替换或版本拿错,重下 |
| 和文件同一个第三方下载站 | 只能说明这两样东西配套,几乎不能说明安全 | 连站家的说明都抄错了 |
| GPG/代码签名,另附公钥 | 来源可信且未被篡改,这才是防篡改 | 签名无效,别装 |
| 自己上次备份时记录的清单 | 这段时间里文件没变、没坏 | 介质或写入出问题了,立刻找另一份副本 |
中间那一行是我觉得最该写进说明书的:攻击者替换安装包时,顺手改掉页面上的 MD5 只需要几秒。哈希保证的是"完整性",不是"真实性",后者要靠签名和渠道信任。
三、我实际怎么算,用哪些命令
系统自带的命令先记住,不依赖网络也能用:
| 平台 | 命令 | 输出 |
|---|---|---|
| Windows(cmd) | certutil -hashfile app.iso MD5 | 32 位十六进制 |
| Windows(PowerShell) | Get-FileHash -Algorithm SHA256 app.iso | 64 位十六进制 |
| macOS | md5 app.iso / shasum -a 256 app.iso | MD5 / SHA-256 |
| Linux、Git Bash | sha256sum app.iso;整批 sha256sum -c SHA256SUMS | 逐行 OK / FAILED |
手边只有一两个文件、不想敲路径时,我把它们直接拖进文件 MD5 计算:批量拖入后一次得到 MD5、SHA-1、SHA-256 三种值,同时列出文件名、大小、类型和修改时间,哈希相同的会标红——上次清重复安装包就是靠这个标红挑出来的。计算用浏览器自带的接口完成,文件不上传;几十 GB 的镜像我还是用命令行更稳。
备份这边我的固定动作是三步:拷完 → 在源端生成一份 SHA256SUMS.txt → 把清单复制到另一台机器。恢复前 -c 跑一遍,两百多个包哪几个坏了直接看得到。文本类的短字符串(对接口签名的原文)我另开一页算,MD5 工具支持 16 位与 32 位、大小写切换和多行批量。
四、MD5 现在还能不能信
答案取决于对手是谁。MD5 的碰撞已经被实际构造出来,2008 年研究者利用它伪造过 CA 证书,所以对抗性场合都不该再用:软件签名、证书链、口令存储,一律换 SHA-256 起步。反过来,"我这份文件在拷贝时有没有断"这种非对抗场景它照样够用,因为坏档不是有人专门针对你的文件构造出来的。
| 用途 | 合适的算法 | 原因 |
|---|---|---|
| 下载完整性、备份自检 | MD5 或 SHA-256 | 只需发现无意损坏 |
| 安装包、镜像对外公布 | SHA-256 + 签名 | MD5 可被碰撞,挡不住蓄意替换 |
| 口令存储 | bcrypt / scrypt / Argon2 | MD5 太快,加盐也救不了 |
| 接口签名、防重放 | HMAC-SHA256 | 需要带密钥的认证码,哈希计算器一次出 MD5 到 SHA-512 六种结果并支持 HMAC |
| 文件去重 | MD5 起步 | 速度快;重要数据去重后再抽查一次内容 |
五、几个会让人白排查的坑
- 16 位和 32 位不是两种算法:16 位只是取 32 位结果的第 9 到 24 个字符。比对前先确认长度,不然会以为"完全对不上"。
- 大小写:
a-f与A-F是同一个值,只是展示格式。脚本比对前统一转小写。 - 换行符改变文本哈希:同一份
README.md在 Windows 下是 CRLF、Linux 下是 LF,MD5 必然不同;Git 的core.autocrlf也会造成同样结果。比文本哈希前先统一行尾。 - 改名、改时间不影响哈希:指纹只算内容,所以"我改了文件名哈希应该变"的直觉是错的,也正因如此它才适合用来判断两份内容是否相同。
- 外层包和内层文件是两回事:zip 的哈希一致只保证包没坏,包内有没有被替换要看逐文件清单。
- 目录没有单一哈希:文件顺序、权限、时间戳都会改变打包结果。核对整个目录要用逐文件清单或 rsync 的校验模式。
六、常见疑问
Q:两个文件 MD5 相同就一定完全一样吗?
日常场景可以这么判断,随机损坏导致相同的概率低到可以忽略。只有当你面对一个会主动构造碰撞的对手时才需要升级算法。
Q:官网只给 SHA-256,我机器上只有 md5sum 怎么办?
换算法而不是换文件:PowerShell 的 Get-FileHash、macOS 的 shasum -a 256、Linux 的 sha256sum 都能直接算,不必重新下载。
Q:校验不一致该怎么处理?
先重下一次再算,仍不一致就当它被改动过:不要运行,不要用其中的文件,同时确认自己是不是拿错了版本或者从镜像站下的。
Q:备份一定要留哈希清单吗,直接定期重拷不行?
重拷不能告诉你盘里那份是不是已经坏了,只能赌。清单的成本是几十秒计算,收益是恢复前就知道哪些包可用。
Q:算大文件要多久?
主要卡在磁盘读取而不是算法,同一块盘上 SHA-256 和 MD5 的耗时差别往往小于读写速度本身。机械盘上等几分钟正常,浏览器里过大的文件还会受内存限制。
那次坏档最后没造成实质损失——同批数据在另一台机器上还有一份旧副本,只缺了两个月。真正改变的是流程:现在备份一结束我就把哈希清单写到第二份存储上,因为"有一份备份"和"知道备份是好的"是两回事。