在选哈希算法时,一个常见的推理是:MD5 输出 128 位,SHA-1 输出 160 位,SHA-256 输出 256 位,SHA-512 输出 512 位,所以数字越大越安全,选最大的那个总没错。这个推理在碰撞抗性上有一定道理,但用它来指导口令安全就完全跑偏了。
长度对抗的是碰撞
摘要长度决定的第一件事是碰撞抗性。根据生日问题,n 位的摘要,找到碰撞大约需要 2^(n/2) 次运算。
按这个公式算:MD5 是 128 位,理论抗碰撞强度是 2^64 次运算;SHA-1 是 160 位,理论上是 2^80;SHA-256 是 256 位,理论上是 2^128。
2^64 次运算在今天已经不难做到,这就是 MD5 碰撞能被实际构造出来的数学基础。2^80 曾经被认为足够安全,但 SHA-1 的结构弱点让实际复杂度降到了约 2^69,所以也被攻破了。而 SHA-256 的 2^128 远超现实算力,目前没有可行路径。
所以长度确实重要,但它保护的是「两个不同输入撞到同一个摘要」这件事。
它保护的从来不是口令
口令场景面对的威胁不是碰撞,而是穷举。
攻击者不需要找到两个不同口令产生同一哈希,他只需要不断尝试口令,直到算出的哈希与目标一致。这个过程只依赖哈希的计算速度,与输出长度关系不大——算一次 SHA-256 和算一次 SHA-512,在现代 CPU 上的差距是常数倍,不构成数量级上的障碍。
换句话说,把口令哈希从 MD5 换成 SHA-512,攻击者每秒能尝试的次数可能只下降几倍,而他本来需要提升的是几十个数量级。长度带来的那点开销远远不够。
口令安全靠的是「贵」
真正有效的思路是让每一次尝试都变得昂贵。这需要算法提供可调的代价参数。
Bcrypt 用 cost 因子控制迭代轮数,cost 每加 1,计算量翻倍。把 cost 从 10 提到 12,单次验证的时间变为四倍,攻击者的尝试速度就降到四分之一。
Argon2 除了迭代次数,还能要求占用指定大小的内存。这一点很关键:显卡的优势来自大量并行核心,而每个核心都需要独立的内存空间。当算法要求每个并行实例占用几十 MB 内存时,显卡的并行度立刻受限于显存容量,攻击成本大幅上升。
PBKDF2 用迭代次数控制,虽然没有内存硬特性,但把迭代次数设到几十万次,同样能把单次验证时间拉到可接受的上限。
这三种算法的共同点是:参数可以随硬件进步调整。今天设的 cost 值五年后可能不够用,但至少有一个旋钮可以往上拧。而 SHA-256 和 SHA-512 没有任何旋钮,它们的速度由算法决定,永远那么快。
加长输出的边际收益
回到长度这个话题,还有一个容易被忽略的点:超过某个阈值之后,继续加长输出几乎没有收益。
SHA-256 的 128 位抗碰撞强度已经远超任何现实攻击能力。即使算力提升一千倍,2^128 仍然不可达。在这种情况下,把输出加到 512 位带来的额外安全余量,对实际防护没有意义——因为攻击者根本不会走碰撞这条路,他会走穷举或实现漏洞。
安全投入应该放在有实际风险的地方:算法是否提供可调代价、实现是否用了安全的随机源、是否有速率限制、是否防范了时序攻击。这些才是决定口令系统强度的因素。
检查清单
- 摘要长度决定碰撞抗性,n 位摘要的碰撞复杂度约为 2^(n/2)
- 口令面临的威胁是穷举,不是碰撞,长度帮不上主要忙
- 口令安全的关键是「每次尝试都很贵」,需要可调代价参数
- Bcrypt 用 cost 因子,Argon2 还能要求占用大量内存
- 参数应随硬件升级定期上调,这是通用哈希不具备的能力
- 超过 SHA-256 的强度余量对实际防护意义有限,投入应转向实现细节