直接用 password_hash(),别用 hash() 或 md5();它自动加盐、选算法(默认 bcrypt)、可调迭代轮数,生成的哈希字符串已内置算法、盐和参数,验证时 password_verify() 可自动解析。

PHP 里该用 password_hash() 还是自己调 hash()?
直接用 password_hash(),别碰 hash() 或 md5() 等通用哈希函数。前者自动加盐、选算法(默认 bcrypt)、控制迭代轮数;后者不加盐、不可调参、不抗暴力破解。
常见错误现象:hash('sha256', $password) 存库后被彩虹表秒破;md5($password . $salt) 手动拼接盐值但盐未唯一 per-user、轮数固定、无延时防护。
-
password_hash()生成的字符串自带算法标识、盐和轮数,后续password_verify()能自动识别解析 - 不要存盐到单独字段——它已编码在哈希结果里(如
$2y$10$...开头) - 避免显式指定
PASSWORD_ARGON2I或PASSWORD_ARGON2ID:需确认 PHP 版本 ≥ 7.2 且编译时启用了 libsodium
Composer 引入 paragonie/random_compat 是必须的吗?
不是必须,但对老项目(PHP random_bytes() 和 random_int(),password_hash() 内部依赖它们生成安全盐值。
如果你还在用 PHP 5.6,composer require paragonie/random_compat 确实能补上 CSPRNG 缺口;但更建议升级 PHP,因为 random_compat 在 PHP 7.0+ 下会静默退化,且无法修复 password_hash() 在旧版中默认使用弱轮数(如 bcrypt 的 cost=10 实际可能被降为 4)的问题。
- 检查 PHP 版本:
php -v;若低于 7.2,优先升级而非堆组件 -
paragonie/random_compat不解决哈希逻辑问题,只管随机数生成——而密码哈希的安全核心在算法选择与参数可控性
验证时为什么 password_verify() 总返回 false?
最常见原因是数据库字段长度不够,截断了哈希字符串。默认 bcrypt 输出 60 字符,argon2 可达 255 字符以上。字段设成 VARCHAR(255) 是稳妥下限。
其他高频原因:
- 从表单读取密码时被
trim()或urldecode()意外修改(比如末尾空格、+ 号被转为空格) - 数据库连接字符集非
utf8mb4,导致某些盐值中的特殊字节存储异常 - 调用
password_verify()前对用户输入做了额外 hash(如先sha256($_POST['pwd'])再验证),而数据库存的是原始密码的哈希
调试建议:用 var_dump(strlen($hash_from_db), strlen($input_hash)) 快速确认是否截断。
要不要用第三方库比如 ircmaxell/password-compat?
不用。这个库是 PHP 5.3–5.4 时代的临时兼容层,早已废弃(last update: 2014)。它只是把 password_hash() 的 polyfill 包了一层,但无法解决底层 RNG 不安全、算法不可配、无 Argon2 支持等根本问题。
现代项目应直接依赖语言原生能力,而不是用 Composer 引一堆过时封装来“假装安全”。真正需要关注的是:
- 确保 PHP ≥ 7.2(支持 Argon2,且
password_hash()的cost参数可调范围更合理) - 验证流程中没做多余转换(比如 base64_encode 密码再哈希)
- 重置密码时仍走
password_hash(),而非沿用旧逻辑生成明文或弱哈希
Argon2 的内存与线程参数容易调错,稍不注意就拖垮服务器响应——真要用,务必在 staging 环境压测并发下的 CPU/内存表现。


















