Password Storage & Verification
密码验证系统应把用户口令经过独立随机 salt 和自适应 password-hashing scheme 存成带算法/参数版本的 verifier,并结合可选 pepper、登录速率限制和 rehash-on-login 抵抗数据库泄漏后的离线猜测;它与可逆加密和普通 SHA-256 不同。
[!info] related notes
- 所属 MOC: 安全 MOC
- KDF: KDF
- Salt/Pepper: Salt vs Pepper
- Algorithms: Argon2id, PBKDF2, bcrypt
- Hash 边界: 密码学哈希函数
- Password Policy: Password Policy, Breached Password Blocklist
- 在线攻击/防御: Credential Stuffing / Spraying / Brute Force, Authentication Throttling & Account Lockout
- MFA: Password 与 MFA 的安全边界
- 登录流程: Token 认证实现流程
Password Storage & Verification
一句话模型
服务端不应该保存“可恢复原密码”的值。
典型模型:
password + unique salt + password-hashing parameters → slow/adaptive password hash → stored verifier
登录时:
candidate password → same scheme using stored parameters → constant-time/algorithm verify → match / mismatch
Threat Model
Password storage 主要防的是:
attacker 拿到了 user database 之后进行 offline password guessing。
此时 attacker 已经拥有:
- stored hash;
- salt;
- algorithm name;
- work factor;
- 可能的用户 metadata。
系统无法阻止 attacker 无限离线尝试,只能让每一次 guess 尽可能昂贵。
因此 password hashing 需要:
- unique salt;
- tunable cost;
- ideally memory hardness;
- algorithm/version metadata;
- periodic parameter upgrade。
为什么不能直接 SHA-256
普通 SHA-256 设计目标是高吞吐。
对于低熵人类口令:
password → SHA-256 → hash
attacker 可以用 GPU/ASIC 每秒尝试巨量候选。
所以:
cryptographic hash 安全 ≠ password storage 安全。
详见 密码学哈希函数。
Password Hashing Scheme
现代优先:
兼容/合规场景:
已有 legacy 系统常见:
选择不应只看算法名字,还要看当前参数和平台支持。
Salt
每个 password verifier 使用独立、随机 salt。
目标:
- 相同 password 不产生相同 stored verifier;
- 阻止跨账号一次计算复用;
- 破坏通用 rainbow table。
Salt:
- 可以和 hash 一起存;
- 不要求保密。
详见 Salt vs Pepper。
Pepper
Pepper 是额外的 server-side secret。
它不和 password hash 放在同一数据库中,理想上存于:
- HSM;
- KMS;
- TEE;
- 独立 secret store。
它提供 defense in depth:
database dump leaked but pepper still secret → offline cracking becomes much harder
Pepper 不是 salt 的替代品。
存储格式
不要只存一列裸 digest。
至少需要能够恢复:
- scheme/version;
- parameters;
- salt;
- output/hash。
许多算法采用自描述字符串,例如 PHC string。
这样才能支持:
登录成功时检测旧参数 → rehash → 升级 verifier。
Rehash-on-Login
推荐迁移策略:
- 用户使用旧 verifier 登录;
- 先按旧 scheme 正常 verify;
- 登录成功;
- 判断参数是否低于当前 policy;
- 用当前 scheme/参数重新 hash;
- 原子更新 verifier。
这样不需要知道用户明文密码,也能渐进升级整个数据库。
在线攻击与离线攻击不同
Password hashing 主要提高 offline guessing 成本。
在线 login attack 还必须依赖:
- rate limiting;
- account throttling;
- suspicious-login detection;
- MFA;
- breached-password screening。
不要通过把 Argon2 参数调到极高来替代 online rate limiting。
DoS 边界
Password hashing 故意昂贵,也会消耗真实服务器资源。
如果:
- memory cost 太高;
- parallelism 太高;
- 无登录 rate limit;
攻击者可能利用登录 endpoint 制造资源耗尽。
因此参数应按:
security target
- hardware benchmark
- expected concurrency
- latency budget
共同调优。
Password Verification 不等于 Password-Derived Encryption
服务端登录:
password → verifier → yes/no
密码保险库/本地加密:
password → KDF → encryption key → decrypt vault
两者都可能用 Argon2/PBKDF2,但产物和系统目标不同。
不要把“password hash”与“由 password 派生出的长期 encryption key”混成同一对象。
当前标准边界
NIST SP 800-63B-4 要求密码 verifier:
- 使用适合 password hashing 的 salted scheme;
- cost factor 尽可能高但不破坏 verifier 性能;
- 随计算能力增长提高 cost;
- 存储 scheme / cost metadata 以支持迁移;
- 可额外使用单独保存的 keyed secret 作为额外防线。
OWASP 当前优先推荐 Argon2id;FIPS-140 合规需要时可使用 PBKDF2-HMAC-SHA256。
参数数字应被视为当前基线,不是永久常数。
常见错误
hash(password + salt)
自己拼普通快速 hash。
→ 不等于标准 password hashing scheme。
全站共用一个 salt
→ 失去 per-account salt 的关键价值。
Salt 当 secret
→ 没必要,反而使设计复杂。
Pepper 和数据库一起存
→ 数据库泄漏时 pepper 同时泄漏,防御价值大幅下降。
永远固定 cost
→ hardware 变快后离线攻击成本持续下降。
登录失败就自动升级 hash
→ 没有正确 password 时不能安全 rehash。
学习检查
- Password storage 的核心 threat model 为什么是 database breach 后的 offline guessing?
- 为什么 SHA-256 本身安全却不适合直接存 password?
- Salt 为什么需要 unique/random,却不需要保密?
- Pepper 为什么必须和 hash database 分离?
- 为什么 verifier 应存 scheme 与参数 metadata?
- rehash-on-login 怎样在不知道长期明文密码的情况下升级算法?
- password hashing 与 online rate limiting 分别解决什么攻击面?
- password verifier 与 password-derived encryption key 有什么不同?