用户密码必须用 password_hash() 做不可逆哈希,禁用 md5()/sha1() 等通用哈希及 openssl_encrypt() 等可逆加密;验证须用 password_verify(),数据库字段设为 varchar(255),迁移应借登录过程渐进升级。

别学“密码加密”,先搞清哈希和加密不是一回事——用户密码必须用 password_hash() 做不可逆哈希,而不是用 openssl_encrypt() 这类可逆加密。
为什么不能用 md5() 或 sha1() 存密码
它们是通用哈希函数,没盐、没成本因子、计算太快,彩虹表一扫就垮。你看到的 md5("123456") === "e10adc3949ba59abbe56e057f20f883e",攻击者本地 1 秒能跑几亿次。
- 数据库里存
md5($pass)的老系统,现在基本等于明文暴露 - 哪怕加了静态盐(比如
md5($pass . "my_salt")),也挡不住 GPU 暴力穷举 -
hash('sha256', $pass)同样危险:无盐、无迭代、不抗时序攻击
password_hash() 怎么调用才不算错
它不是“选个算法填进去”那么简单,关键在默认行为和错误处理。
- 始终用
PASSWORD_DEFAULT,别硬写PASSWORD_BCRYPT——PHP 未来升级默认算法时,你的代码自动受益 - 如果返回
false,不是密码太长,很可能是内存不足或系统禁用了算法(比如某些容器没开 libsodium) - 数据库字段必须是
VARCHAR(255),因为PASSWORD_ARGON2ID输出可能达 240+ 字符,VARCHAR(60)会截断 - 别把哈希值再 base64 编码一遍存库——
password_hash()输出本身就是安全字符串,直接存
验证必须用 password_verify(),不能手比对
手动写 $input_hash === $db_hash 会引入时序攻击面,攻击者靠响应时间差异就能逐步猜出哈希值。
-
password_verify()内部是恒定时间比对,PHP 已帮你封死这个漏洞 - 它能自动识别哈希前缀(
$2y$、$argon2id$),无需你解析算法或提取盐 - 传入空字符串、
null或超长字符串时,它只返回false,不会报错或崩溃
旧系统迁移时最易踩的坑
直接全量跑 password_hash() 替换所有旧 MD5 密码?服务器 CPU 会瞬间拉满,用户登录卡死。
- 正确做法是在用户每次成功登录后,用新哈希覆盖旧值——一次只处理一个账号
- 用
password_needs_rehash($hash, ['cost' => 12])判断是否该升级(比如从 bcrypt cost=10 升到 12),别靠字符串长度或前缀硬判断 - 如果旧哈希是
sha1($pass.$salt)这种自定义格式,password_verify()肯定失败,得先按原逻辑验一次,再重哈希
真正麻烦的从来不是函数怎么写,而是怎么让老数据在不动用户的情况下,悄无声息地变安全——这需要在登录链路里埋点,而不是写个迁移脚本就完事。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











