一谈到密码哈希,就必然会出现「salt(盐)」。在做哈希之前加上一个随机值,安全性就会大不相同。本文讲解其机制与要点(不会写出攻击步骤)。
为什么 salt 能让密码更安全
没有 salt 时,密码相同的人保存的哈希也相同。这给了攻击者两种手段:在预先计算好的对照表(彩虹表)里直接查出密码,以及把一个人的破解结果套用到所有密码相同的人身上。
加上 salt 后,即便同样是 "password123",每个用户的保存值也会完全不同。
出于同样的道理,salt 必须每个用户(最好是每个密码)都重新创建。如果所有人共用同一个 salt,相同密码的人之间保存值又会一致,大部分效果就丢失了。生成时要使用难以猜测的密码学随机数,创建好的 salt 要和哈希值一起保存(无需保密)。
另外,salt 守护的范围仅限于「预先计算好的表」和「针对重复使用密码的批量破解」。针对单个密码进行穷举的攻击,让它变慢并不是 salt 的工作,而是由 bcrypt/Argon2 这类故意做得很慢的哈希来承担。两者应对的威胁不同,所以必须搭配使用。
salt 的长度与生成方法
有两份文件确定了基准:
- NIST SP 800-63B-4(美国的认证指南):salt 至少 32 位,并且选取方式要让保存的 salt 很少发生重复。每个密码都要同时保存 salt 和哈希
- RFC 9106(Argon2 的规范):在密码哈希中推荐 16 字节(128 位),空间紧张时可减少到 64 位
生成时的三条规则:
- 使用密码学随机数生成器。使用所用语言的安全随机函数,而不是
Math.random()或 PHP 的mt_rand()这类可预测的函数 - 不要用可猜测的值:用户名、邮箱地址、用户 ID 或注册时间都不能代替 salt
- 修改密码时生成新的 salt。沿用旧的 salt,会在新旧哈希之间留下关联
实务上,标准的 bcrypt、Argon2、scrypt 实现会自动满足以上全部要求。如果你在写自己生成 salt 的代码,就把它当作需要复查的信号。
salt、pepper 与密钥拉伸的区别
密码保存涉及三种名称相似的措施。它们防御的对象不同,任何一种都不能替代另一种。
| salt | pepper | 密钥拉伸(慢速哈希) | |
|---|---|---|---|
| 是什么 | 每个用户的随机值 | 全体用户共用的秘密值 | 故意让哈希计算变得代价高昂 |
| 是否保密 | 否(与哈希一起保存) | 是(放在数据库之外) | 设置无需保密 |
| 防御对象 | 预先计算的表、对相同密码的批量破解 | 仅数据库泄露时的破解 | 对每个哈希的穷举 |
| 实现方式 | bcrypt/Argon2 自动添加 | 需要时自己添加 | bcrypt 的 cost、Argon2 的内存与迭代次数 |
NIST SP 800-63B-4 建议,把「只有验证方知道的秘密密钥」(通常称为 pepper)与密码哈希分开保存,最好放在 HSM 等专用硬件中。
真实案例:LinkedIn 未加 salt 的保存(2012 年)
2012 年 6 月,据报道约 650 万条据称属于 LinkedIn 用户的密码哈希被发布到网上。LinkedIn 确认其中一部分对应 LinkedIn 账户,并使受影响会员的密码失效。
该公司在官方博客中表示,它一直在从只对密码做哈希的系统,迁移到既做哈希又加 salt 的系统。当时,媒体引用的专家批评该公司只用 SHA-1 哈希来保护密码。
两点教训:
- 没有加 salt 的哈希,可以通过预先计算好的表成批还原出常见密码
- 即使正在迁移,只要还残留旧格式的哈希,泄露的影响就由那个旧格式决定
如何检查自己的环境
查看保存的字符串的形态
在你管理的开发数据库中,查看几条密码值。bcrypt 的话,$2b$12$ 之后的 22 个字符就是 salt。Argon2 的话,$argon2id$v=19$m=…,t=…,p=…$ 之后是以 $ 分隔的 salt 和哈希。如果值不符合任何一种形态,也没有单独的 salt 列,密码可能是在没有 salt 的情况下保存的。
用相同密码创建两个测试账户
在开发环境中,用相同的密码注册两个测试账户,比较保存的值。如果两者一致,说明 salt 没有起作用。同时确认修改密码时 salt 部分会改变。
查找手工拼装 salt 的代码
在代码中搜索 salt 一词,以及手工组合随机函数或哈希函数的地方。如果发现可预测的随机函数、复用用户名或 ID、或固定字符串,就换成所用语言或框架的标准密码函数。
迁移时参考实践指南
如何从没有 salt 的保存方式迁移(例如在登录时重新哈希),以及各算法的推荐设置,见密码的安全保存方法。
常见误解与正解
误解
- salt 必须保密
- 所有用户用共同的 salt 就行
- 只要加上 salt,弱密码也安全
正解
- salt 可以公开(和哈希一起保存)
- 每个用户(最好是每个密码)用不同的随机数
- salt 是针对「预先计算与重复使用密码破解」的对策。穷举对策由慢速哈希承担
本站的观点:先把 salt 和慢速哈希做对
pepper 是针对只有数据库被盗这种情况的附加措施。它增加了在别处管理秘密的工作,而一旦丢失这个秘密,就再也无法验证任何人的密码。
所以实务上,首要任务是用 bcrypt/Argon2 正确地使用salt+慢速哈希。等你已经有了密钥管理的体制,再考虑 pepper。
出处(一手资料)
- NIST,“SP 800-63B-4 Digital Identity Guidelines: Authentication and Authenticator Management”(2025 年 8 月) — pages.nist.gov(salt 至少 32 位、保存 salt 和哈希、分开保存的秘密密钥)
- RFC 9106,“Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications” — rfc-editor.org(密码哈希推荐 16 字节 salt)
- OWASP,“Password Storage Cheat Sheet” — cheatsheetseries.owasp.org(加 salt、加 pepper、多数库会自动处理 salt)
- PHC string format(Argon2 等使用的哈希字符串格式) — github.com/P-H-C
- TechCrunch,“LinkedIn Confirms Hack And Leak Of 'Some' User Passwords”(2012 年 6 月 6 日) — techcrunch.com(约 650 万条、该公司关于迁移到哈希加 salt 的说明)
- Computerworld,“LinkedIn provides breach update -- sort of”(2012 年 6 月 8 日) — computerworld.com(对只用 SHA-1 哈希的批评、密码失效)
接下来阅读
- 术语:密码哈希是什么(与 salt 成对理解)
- 对策:密码的安全保存方法(哈希+salt 实践)
- 入门:密码的正确保管