密码存储原理:哈希、加密与盐值
聊到密码迁移,我们首先得把「密码是怎么存的」这件事彻底搞明白。
说来惭愧,我刚入行那会儿,有个项目直接把用户密码明文塞进了数据库。后来被安全审计抓个正着,差点被拉去写检讨。那之后我才真正开始认真研究密码存储的门道。
说白了,密码存储就三个核心:哈希、加密、盐值。搞懂它们,你才能理解什么叫真正的「安全存储」。
密码存储的演进:从裸奔到全副武装
我们来看看密码存储的几个「时代」。你想想看,你的系统现在处在哪个阶段?
| 阶段 | 存储方式 | 风险等级 | 典型特征 |
|---|---|---|---|
| 石器时代 | 明文存储 | 极高 | 数据库一泄露,所有账号全完蛋 |
| 青铜时代 | 简单哈希(MD5/SHA-1) | 高 | 抵抗不了彩虹表,也扛不住暴力破解 |
| 铁器时代 | 哈希 + 盐值 | 中 | 相同密码,存储结果不同,防彩虹表 |
| 现代标准 | 慢哈希 + 盐值(bcrypt/Argon2) | 低 | 计算慢,破解成本高,是目前的最佳实践 |
我在一个遗留系统里就见过「青铜时代」的产物。几万用户,密码全是用 MD5 存的。迁移的时候,我第一件事就是让用户全部强制重置密码——因为原来的密码根本不能叫「安全」。
核心概念:哈希 vs 加密
很多新手搞混这两个概念。我习惯用一个比喻来解释:加密是双开门,哈希是碎纸机。
- 加密(Encryption):有密钥就能打开。加密后的数据可以通过解密还原出原文。
- 哈希(Hashing):单向过程。你永远没办法从哈希值反推出原文。
为什么密码存储不能用加密?因为加密就意味着有人拿着密钥能把你所有用户的密码恢复成明文。说白了,这个密钥就是系统最大的单点故障。一旦密钥泄露,全线崩溃。
哈希不一样。即便数据库被拖库了,攻击者拿到也只是一堆看起来毫无规律的字符串。他没办法直接知道你的密码是「123456」。
盐值:为什么不能少?
好,现在你已经知道要用哈希了。但问题来了:如果两个用户密码相同,他们的哈希值会不会一样?
会! 这就是「彩虹表」攻击能生效的原因。攻击者可以先算好常见密码的哈希值,做成一张表,然后直接查。
盐值就是用来解决这个问题的。它是一个随机生成的字符串,在哈希之前拼在密码后面。这样即使密码相同,因为盐值不同,最终的哈希值也完全不一样。
我曾经维护过一个系统,所有用户共用一个固定的「盐值」——说白了这就是个心理安慰,根本起不到真正的防护作用。真正的盐值必须满足三个条件:
- 每个用户独立:甚至每次密码变更都要重新生成
- 足够长:至少 16 字节,我习惯用 32 字节
- 密码学安全随机:不能用
random(),要用secrets._hex()这类安全随机函数
慢哈希:给攻击者「戴上镣铐」
光有哈希和盐值还不够。我们还得控制哈希的计算速度。
你想想看,如果哈希函数算得太快,攻击者一秒能试几百万个密码,那你的盐值保护很快就会失效。所以我们需要「慢哈希」——故意让计算变慢。
我目前团队使用的标准是 bcrypt,它的 cost factor 参数控制了哈希的运算轮数。在我个人的项目里,我一般设成 12,这样一次哈希大约需要 250 毫秒。对用户来说毫无感觉,但对攻击者来说,破解成本直接翻了几千倍。
常见的慢哈希算法对比:
| 算法 | 特点 | 建议场景 |
|---|---|---|
| bcrypt | 内置盐值,自适应,行业标准 | 通用推荐,新项目首选 |
| Argon2id | 抗 GPU 和 ASIC,新一代标准 | 追求极致安全,对性能有要求 |
| PBKDF2 | 可调迭代次数,传统方案 | 兼容旧系统,不建议新项目 |
| scrypt | 内存密集型,抗硬件破解 | 资源充足,硬件攻击防护优先 |
// Python 示例:普通哈希 vs 慢哈希
import hashlib
import bcrypt
# ❌ 错误:普通 SHA-256 + 盐值
password = "my_secure_password"
salt = "固定盐值"
wrong_hash = hashlib.sha256((password + salt).encode()).hexdigest()
# 计算速度:纳秒级
# ✅ 正确:bcrypt 慢哈希 + 内置盐值
password_bytes = password.encode('utf-8')
salt_bcrypt = bcrypt.gensalt(rounds=12)
correct_hash = bcrypt.hashpw(password_bytes, salt_bcrypt)
# 计算速度:约 250ms,慢了上亿倍
嗯,这里要注意:rounds 也不是越高越好。太高会导致正常的用户登录也变慢,影响体验。我一般建议从 10 开始,在开发环境测试,然后逐步提高到 12 或 13。
最佳实践与避坑指南
总结一下密码存储的黄金法则:
- 使用慢哈希算法(bcrypt 或 Argon2id)
- 每个用户使用独立的密码学安全随机盐值
- 不要在客户端做哈希,服务器端接收原始密码后立即哈希
- 不要自己实现密码学算法,用成熟的库
- 定期评估 cost factor,跟上硬件发展
知识体系总览
下面这张图把本章的核心脉络串起来了。从用户注册到密码存储,每一步都不能省略。
以上就是密码管理最核心的三大块。搞明白了这些,你就能理解为什么迁移密码时需要特别小心——因为哈希过的密码已经不是「密码」了,它是一种不可逆的凭证。
接下来的章节,我们就要动手解决「怎么安全地把这些哈希值搬过去」这个实际操作问题了。