password_hash 算法选型 Argon2id
解读
国内一线、二线厂的后端面试里,「如何安全地存密码」是高频考点。面试官问“为什么选 Argon2id”并不是想听“官方推荐”四个字,而是想确认你对:
- 国内合规(等保 2.0、国密条例、数据安全法)的敏感度;
- 算法本身在 TIME、MEMORY、THREAD 三维的抗破解成本;
- PHP 7.2+ 的落地细节(常量、选项数组、部署差异);
- 与 bcrypt 的性能/安全权衡;
- 高并发场景下的参数调优思路。
答不到“内存硬”和“侧信道”这两个关键词,基本会被追问到挂。
知识点
- PHP 原生支持
- PASSWORD_DEFAULT 当前仍是 PASSWORD_BCRYPT;
- PASSWORD_ARGON2I、PASSWORD_ARGON2ID 需要 PHP 7.2+ 且编译时带 libargon2;
- 7.4 起 Windows 官方包已内置,国内阿里云、腾讯云标准镜像均打开。
- Argon2 三变种
- Argon2d:数据依赖,抗 GPU,侧信道风险;
- Argon2i:数据独立,抗侧信道,GPU 友好;
- Argon2id:前 2 趟 i,后续 d,折中,CRYPTO-PR 推荐。
- 选项数组
memory_cost 单位 KiB,time_cost 单位趟数,threads 并发度;
等保场景下建议 memory_cost≥65536(64 MiB)、time_cost=4、threads=2,验证耗时 200~300 ms。 - 升级与兼容
password_needs_rehash() 可在登录时无痛升级旧 bcrypt 哈希;
存量用户迁移策略:懒升级 + 双写,避免全量跑脚本。 - 国密合规
密码学算法层面国密无强制,但等保要求“不可逆+盐值+抗暴力”;
Argon2id 满足,SM3-HMAC 可做 pepper(应用层密钥),存 HSM/ KMS。 - 性能调优
php-fpm + opcache 下,调高 memory_cost 比 time_cost 更能抬升破解成本;
容器场景注意 cgroup memory limit,防止 OOM;
可配合 preload 把 sodium 扩展常驻,减少冷启动 5~10 ms。
答案
“我选择 Argon2id 作为 password_hash 的算法,核心原因是它在侧信道抗性与GPU破解成本之间给出了最优折中,并且 PHP 7.4 以后已默认编译进主流镜像,落地零成本。具体做法:
- 使用 password_hash($plain, PASSWORD_ARGON2ID, ['memory_cost'=>65536,'time_cost'=>4,'threads'=>2]),单次哈希耗时控制在 250 ms 左右,既满足等保对‘计算强度’的要求,又不会在登录高峰把 CPU 打满。
- 验证环节先用 password_verify 做常量时间比较,再用 password_needs_rehash 判断是否需要升级参数,实现平滑演进。
- 为了符合数据安全法‘可审计’要求,我把算法标识、参数版本、用户 ID 一起写审计日志,一旦政策调整可快速回滚或调高参数。
- 最后在应用层加 32 字节 pepper(存在 KMS),即使数据库拖库,离线爆破也需要同时拿到代码密钥,提高攻击门槛。”
拓展思考
- 如果公司存量 3000 万 bcrypt 哈希,如何设计“零停机”迁移到 Argon2id?
答:登录链路上埋点,password_verify 成功后立即 password_needs_rehash,满足条件则重新哈希并双写,灰度 30 天完成全量;期间旧字段保留,新字段加 argon2_ 前缀,回滚只需切换字段。 - 容器云场景下,memory_cost 设 128 MiB 导致 php-fpm 频繁 OOM,怎么调?
答:把 threads 降到 1,memory_cost 降到 64 MiB,time_cost 提到 6,维持总耗时不变;同时把 fpm 进程数按 128 MiB*1.2 做内存上限估算,防止超卖。 - 国密改造若强制使用 SM3,如何与 Argon2id 结合?
答:SM3 仅做 pepper 的 HMAC 密钥派生,不改变 Argon2id 内部结构;对外仍宣称 Argon2id,满足国际审计,对内符合国密“密钥管理”条款。