跳到正文
>_ITDITDWeb 安全平台

安全指南

如何安全存储密码 —— 哈希与加盐的正确做法

用户的密码该如何存进数据库?明文为什么危险、为什么只做哈希还不够、为什么需要为每个用户加盐,以及真正的关键——慢哈希(bcrypt/Argon2/scrypt)的选型、配置与既有系统的迁移,统统按顺序讲给开发者听,且不涉及攻击手法。还会点出自行实现的那些坑。

发布于 2026-06-27 更新于 2026-06-27 2 分钟阅读

「用户的密码,到底该怎么存进数据库才对?」——这是每个做服务的人都必然会撞上一次的问题。答案很明确。我们不涉及攻击手法,只按顺序讲清楚安全的存储方法

为什么明文、加密、裸哈希都不行

我们按数据库迟早会泄露这个前提来考虑。一旦泄露,不同的存储方式造成的损失天差地别。

  • 明文:泄露的瞬间所有密码一览无余。再加上重复使用同一密码,用户在其他服务上的账号也会被连锁攻陷。最糟。
  • 加密(可逆):有密钥就能还原=密钥一起泄露就等同于明文。密码本就没有读回来的必要,因此可逆这件事本身毫无意义。
  • 裸哈希(MD5/SHA-256):快速哈希让攻击者能高速尝试海量候选。常见密码会被彩虹表或暴力破解还原出来。

走到安全为止的“四个阶段”

把它理解成给弱方式一步步叠加对策的过程,会很快上手。

① 明文:一泄露就全完
↓ 改为单向
② 裸哈希:扛不住彩虹表
↓ 为每个用户加盐
③ 加盐哈希:彩虹表失效,但暴力破解依旧很快
↓ 故意放慢
④ 加盐+慢哈希(Argon2id / bcrypt):这才是正解
明文 → 裸哈希 → 加盐 → 慢哈希。只有走到最后一步,才真正算得上『安全的存储』。

要点在于,盐和慢哈希的职责是不同的负责让「预计算(彩虹表)和批量破解重复密码」失效。慢哈希负责把「暴力破解的速度压到不现实的水平」。两者齐备,才第一次称得上安全(→ 什么是哈希化)。

实践:现在就该做的事

1

使用 Argon2id(或 bcrypt)

新项目把 Argon2id 作为首选。它还能要求消耗内存,因此对 GPU 暴力破解很有抵抗力。如果更看重成熟稳定的实现,bcrypt 在实践中也完全够用。两者都直接使用标准库的实现

2

盐交给“自动”去做

bcrypt/Argon2 会为每个用户自动生成盐,并把它一并塞进哈希字符串里。你不需要自己另存一列盐。手动去拼 MD5(salt + password),正是不该犯的典型错误。

3

按运行环境设置成本(强度)

把 bcrypt 的成本系数、Argon2 的内存・迭代・并行度,设到正常登录在体感上察觉不到延迟的范围内的最大值。服务器越快,攻击者也越快,所以要以年为周期复核并上调

4

校验用标准的比对函数

登录时,把已存哈希与输入用库提供的比对函数进行匹配(很多实现已经做成了对时序差有所防范的常数时间比较)。不要自己写字符串相等判断。

5

弱哈希用“登录时重新哈希”来迁移

如果已经用 MD5/SHA-256 存了,就在用户登录成功的那一刻用新方式重新哈希并覆盖保存。对于尚未登录的部分,可以用 bcrypt 等把既有哈希再包一层,用这种双重哈希暂时提升整体强度。

常见的错误做法 vs 正确的实现

常见错误

  • 把密码加密后存储(密钥一泄露就全完)
  • 直接存 MD5(password)SHA-256(password)
  • 所有用户共用同一个盐
  • 自己手动拼 hash(salt + password)

正确的实现

  • 用 Argon2id / bcrypt 做单向哈希
  • 盐按每个用户分配(由库自动附加)
  • 成本设在体感可接受范围内的最大值,并定期上调
  • 直接使用标准的哈希/比对函数

本站的观点:密码存储是该踩在“成熟正解”上的地方

密码存储不是一个可以彰显独创性的领域。老老实实踩在经过全球验证的 Argon2/bcrypt 标准实现上,既最安全,运维也最省心。本站的立场是「在这里不要搞创意发挥」。真正该投入精力的,是从根本上减少对密码的依赖——把多因素认证(MFA)做到位,以及未来向通行密钥(无密码)迁移。再强的哈希,也兜不住弱密码和重复使用。

延伸阅读

FAQ

Q密码加密后再存储不就行了吗?
A

不行,加密并不合适。加密只要有密钥就能还原(解密),密钥一旦泄露,所有密码就会被还原成明文。密码对运营方来说也没有读回来的必要,因此无法还原的『哈希化』才是正解。不过裸哈希还不够,需要再配合为每个用户加盐以及慢哈希(bcrypt/Argon2)。

Qbcrypt 和 Argon2,到底该用哪个?
A

如果是新项目,Argon2(尤其是 Argon2id)是首选。因为它的内存占用和计算量都可调,能很好地抵御 GPU 暴力破解。如果更看重既有资产或成熟稳定的实现,bcrypt 在实践中也完全够用。关键在于:无论选哪个,都要『直接使用标准库的实现,绝不自己拼装』。

Q我已经用 MD5 或 SHA-256 存了密码,该怎么迁移?
A

没法把它们一次性还原成明文(这恰恰是它的优点),所以要在登录时迁移。当用户成功登录的那一刻,用新方式(如 Argon2id)对输入的正确密码重新哈希并覆盖保存。对于一直没登录的用户,还可以用 bcrypt 等把既有哈希『再包一层』,用这种双重哈希暂时提升一下整体强度。