「密码不能『原样』保存到数据库里」——那该怎么保存呢?答案就是哈希。本文讲解其机制,以及保证安全的要点(不会写出攻击步骤)。
哈希 ≠ 加密
两者常被混淆,但目的恰好相反。**加密是「为了以后读回,可用密钥还原」**的变换。哈希是「无法还原(单向)」的变换。密码这种信息,连运营方本来都不需要读回,所以特意采用无法还原的哈希更合适。
加密(可逆)
明文 ⇄ 密文。有密钥就能还原。适用于以后要读回的数据
哈希(单向)
密码 → 哈希值。无法还原。适用于保存密码
登录时,要看的是「把输入的密码用同样的步骤做哈希,再和已保存的哈希是否一致」。无需取出原始密码。
为什么「裸用 MD5/SHA-256」还不够
MD5 和 SHA-256 是快速哈希。这本是有用的特性,但在密码保存上却成了弱点。因为攻击者可以针对泄露的哈希,每秒计算海量候选来逐一比对。
- 彩虹表:把「常见密码→其哈希」预先计算好的巨大对照表。如果是裸哈希,只要查表就能找到匹配。
- 暴力破解(brute force):哈希越快,单位时间内能尝试的候选越多=越容易被攻破。
让它安全的两个机制
加上 salt(每个用户不同的值)
保存前,先为每个用户混入一个不同的随机值(salt),再做哈希。这样即使密码相同,每个人的保存值也各不相同,彩虹表被废掉,对重复使用密码的批量破解也无从下手。
使用故意做得很慢的专用哈希
bcrypt / Argon2 / scrypt 可以把计算有意做得很重(成本参数)。对正常登录的单次而言慢到无所谓,但能把攻击者的暴力破解拖到现实中不可行的速度。salt 的添加也已内置其中。
本站观点:不要自己拼凑
「给 MD5 加个 salt 不就够了吗?」——这正是典型的陷阱。安全的密码保存,要把 salt 的生成与保管、计算成本的调节、对时序差异的考量都包含进来,才算成立。比起自己东拼西凑,交给语言、框架的官方密码函数(很多内部都用 bcrypt/Argon2)来处理,在本站看来才是最安全、最可靠的。新项目请把 Argon2id 作为首选。
继续阅读
- 术语:salt 是什么(让重复使用密码的攻击失效的「调味」)
- 对策:密码的安全保存方法(哈希+salt 实践)
- 入门:密码的正确保管(别再明文保存) / 如何选择密码管理器
FAQ
Q哈希和加密有什么区别?
加密是『有密钥就能还原(可解密)』的变换,用于以后需要读回数据的场景。哈希是『无法还原(单向)』的变换。密码本来就不需要运营方读回,所以特意采用无法还原的哈希更合适。即便数据库被盗,也无法从哈希中直接取出原始密码。
Q用 MD5 或 SHA-256 做哈希就安全吗?
仅此还不够。MD5/SHA-256 是『快速』哈希,攻击者每秒能尝试海量候选,因此常见密码会被暴力破解或彩虹表(预先计算好的对照表)攻破。要做到安全,需要为每个用户加上不同的『salt』,并使用『故意做得很慢』的专用哈希(bcrypt·Argon2·scrypt)。
Q到底该用哪一个?
新项目首选 Argon2(尤其是 Argon2id),其次是 bcrypt 或 scrypt。它们的机制中都内置了 salt 的添加与计算成本的调节。比起自己拼凑 MD5+salt,使用这些标准实现(语言/框架的官方函数)更安全、更可靠。