MD5 的失败故事很多人熟悉,但 SHA-1 的经历更能说明一件事:一个算法的「退役」往往要等很久,即使理论上的警告已经出现了十几年。从 2005 年的理论警告到 2017 年的实际碰撞,中间隔了整整十二年,而这十二年里大量系统仍在签发 SHA-1 证书。
从理论复杂度到实际碰撞
2005 年,王小云团队给出了 SHA-1 碰撞攻击的理论复杂度,大约是 2^69 次运算。作为对比,SHA-1 的输出是 160 位,理想情况下找到碰撞应该需要 2^80 次运算。2^69 虽然仍然是一个天文数字,但已经比理想值低了两千多倍,说明算法的结构存在可利用的弱点。
理论上的弱点不等于可以实际利用,因为 2^69 次运算在当时的算力下仍然不现实。所以在接下来的十几年里,业界的态度是「知道有问题,但暂时还能用」。
2017 年 2 月,这个缓冲期结束了。Google 与荷兰 CWI 研究所联合发布了 SHAttered 攻击,实际构造出两个内容不同但 SHA-1 值完全相同的 PDF 文件,并公开了碰撞样例。这是第一次公开的、可验证的 SHA-1 碰撞。
行业为什么反应得这么慢
从 2005 年知道有问题,到 2016 年主流 CA 停止签发 SHA-1 证书,中间隔了十一年。这不是因为业界不重视,而是因为更换哈希算法牵涉的范围极广。
证书要换,意味着所有网站的证书都要重新签发;代码签名要换,意味着所有历史发布流程都要调整;协议要换,意味着客户端和服务端都要升级。任何一个环节没跟上,就会出现兼容性问题。这种「整个生态一起迁移」的成本,是安全决策里最容易被低估的部分。
SHA-1 的经历因此成了一个教训:算法迁移应该在还没被攻破的时候就启动,因为从决定迁移到迁移完成,可能要好几年。
SHA-1 现在还在哪些地方
即使已经被攻破,SHA-1 依然广泛存在于一些难以改动的场景里。
最典型的是 Git 的对象标识。Git 用 SHA-1 给每个 blob、tree、commit 生成 ID,git rev-parse HEAD 返回的那串 40 位十六进制就是它。Git 社区已经在推进迁移到 SHA-256,新版本支持 SHA-256 仓库格式,但由于涉及所有历史对象,迁移是渐进式的。
BitTorrent v1 的 info hash 也是 SHA-1,这是磁力链接的核心标识。协议已经运行多年,改动意味着全生态升级。
历史代码签名与旧版 TLS 中还有大量 SHA-1 痕迹,出于兼容性暂时无法立即替换。
文件分发校验中,很多开源项目至今只发布 MD5 与 SHA-1 校验值,这更多是历史习惯的延续。
HMAC-SHA1 为什么处境不同
有一个细节容易被忽略:SHA-1 被攻破的是碰撞抗性,而 HMAC 结构并不依赖碰撞抗性。
HMAC 引入了密钥,攻击者不知道密钥就无法计算中间状态,因此针对裸 SHA-1 的碰撞构造方法不能直接套用。这就是为什么 HMAC-SHA1 在很多协议里比裸 SHA-1 存活得更久。
但这不代表 HMAC-SHA1 应该继续用于新系统。它的输出只有 160 位,长期来看余量不足,而且迁移成本会随着时间增加。新项目直接用 HMAC-SHA256 是更省事的选择。
实际处理的建议
如果系统里还在用 SHA-1,按场景决定优先级。
涉及数字签名、证书、防篡改校验的,属于对抗性场景,应当尽快迁移到 SHA-256 或 SHA-512。
用于旧系统对接的,如果对方只支持 SHA-1,可以继续使用,但要在代码注释和文档里明确标注这是兼容性措施,不是安全选择,并记录迁移计划。
用于文件校验的,建议同时发布 SHA-256 值,逐步引导用户改用新值。
用于口令存储的,无论 SHA-1 是否被攻破都不该用——它太快,没有盐,没有可调代价。这是另一个维度的问题。
检查清单
- 2005 年出现理论警告,2017 年 SHAttered 实现实际碰撞,中间隔了十二年
- SHA-1 的抗碰撞性已被攻破,不能用于签名、证书、防篡改校验
- Git、BitTorrent、历史代码签名仍在用 SHA-1,迁移是渐进过程
- HMAC-SHA1 不直接受碰撞攻击影响,但新系统仍应使用 HMAC-SHA256
- 迁移要提前启动,从决定到完成可能需要数年
- 口令存储无论算法是否被攻破都要用慢哈希,与碰撞问题无关