password_hash 算法选型 Argon2id

解读

国内一线、二线厂的后端面试里,「如何安全地存密码」是高频考点。面试官问“为什么选 Argon2id”并不是想听“官方推荐”四个字,而是想确认你对:

  1. 国内合规(等保 2.0、国密条例、数据安全法)的敏感度;
  2. 算法本身在 TIME、MEMORY、THREAD 三维的抗破解成本;
  3. PHP 7.2+ 的落地细节(常量、选项数组、部署差异);
  4. 与 bcrypt 的性能/安全权衡;
  5. 高并发场景下的参数调优思路。

答不到“内存硬”和“侧信道”这两个关键词,基本会被追问到挂。

知识点

  1. PHP 原生支持
    • PASSWORD_DEFAULT 当前仍是 PASSWORD_BCRYPT;
    • PASSWORD_ARGON2I、PASSWORD_ARGON2ID 需要 PHP 7.2+ 且编译时带 libargon2;
    • 7.4 起 Windows 官方包已内置,国内阿里云、腾讯云标准镜像均打开。
  2. Argon2 三变种
    • Argon2d:数据依赖,抗 GPU,侧信道风险;
    • Argon2i:数据独立,抗侧信道,GPU 友好;
    • Argon2id:前 2 趟 i,后续 d,折中,CRYPTO-PR 推荐。
  3. 选项数组
    memory_cost 单位 KiB,time_cost 单位趟数,threads 并发度;
    等保场景下建议 memory_cost≥65536(64 MiB)、time_cost=4、threads=2,验证耗时 200~300 ms。
  4. 升级与兼容
    password_needs_rehash() 可在登录时无痛升级旧 bcrypt 哈希;
    存量用户迁移策略:懒升级 + 双写,避免全量跑脚本。
  5. 国密合规
    密码学算法层面国密无强制,但等保要求“不可逆+盐值+抗暴力”;
    Argon2id 满足,SM3-HMAC 可做 pepper(应用层密钥),存 HSM/ KMS。
  6. 性能调优
    php-fpm + opcache 下,调高 memory_cost 比 time_cost 更能抬升破解成本;
    容器场景注意 cgroup memory limit,防止 OOM;
    可配合 preload 把 sodium 扩展常驻,减少冷启动 5~10 ms。

答案

“我选择 Argon2id 作为 password_hash 的算法,核心原因是它在侧信道抗性与GPU破解成本之间给出了最优折中,并且 PHP 7.4 以后已默认编译进主流镜像,落地零成本。具体做法:

  1. 使用 password_hash($plain, PASSWORD_ARGON2ID, ['memory_cost'=>65536,'time_cost'=>4,'threads'=>2]),单次哈希耗时控制在 250 ms 左右,既满足等保对‘计算强度’的要求,又不会在登录高峰把 CPU 打满。
  2. 验证环节先用 password_verify 做常量时间比较,再用 password_needs_rehash 判断是否需要升级参数,实现平滑演进。
  3. 为了符合数据安全法‘可审计’要求,我把算法标识、参数版本、用户 ID 一起写审计日志,一旦政策调整可快速回滚或调高参数。
  4. 最后在应用层加 32 字节 pepper(存在 KMS),即使数据库拖库,离线爆破也需要同时拿到代码密钥,提高攻击门槛。”

拓展思考

  1. 如果公司存量 3000 万 bcrypt 哈希,如何设计“零停机”迁移到 Argon2id?
    答:登录链路上埋点,password_verify 成功后立即 password_needs_rehash,满足条件则重新哈希并双写,灰度 30 天完成全量;期间旧字段保留,新字段加 argon2_ 前缀,回滚只需切换字段。
  2. 容器云场景下,memory_cost 设 128 MiB 导致 php-fpm 频繁 OOM,怎么调?
    答:把 threads 降到 1,memory_cost 降到 64 MiB,time_cost 提到 6,维持总耗时不变;同时把 fpm 进程数按 128 MiB*1.2 做内存上限估算,防止超卖。
  3. 国密改造若强制使用 SM3,如何与 Argon2id 结合?
    答:SM3 仅做 pepper 的 HMAC 密钥派生,不改变 Argon2id 内部结构;对外仍宣称 Argon2id,满足国际审计,对内符合国密“密钥管理”条款。