必须用 password_hash() 而非 md5()/sha1(),因其自动处理盐、算法升级和格式封装;数据库字段需 VARCHAR(255)+,验证须用 password_verify() 直接比对原哈希值。

直接用 password_hash(),别碰 md5()、sha1()、crypt() 这些老方法——它们不安全,且 PHP 官方已明确弃用。
为什么不能用 md5() 或 sha1() 存密码
这些是通用哈希函数,设计目标是快,不是抗暴力破解。现代 GPU 一秒能跑上亿次 md5(),加盐也挡不住彩虹表和爆破工具。更关键的是:md5("password") 永远等于同一个值,攻击者拿到数据库后可批量反查。
常见错误现象:
- 用户注册时存
md5($_POST['pwd']),登录时再比对md5($input)—— 这等于把密码明文“翻译”成固定字符串,毫无防御力 - 手动拼接盐(如
md5($pwd . $salt)),但盐是固定或可预测的(比如用用户名)——盐失去随机性就等于没盐 - 数据库字段只设 32 字符(刚好够存 MD5)——而
password_hash()输出最长超 255 字符,字段太小会截断,导致后续password_verify()必然失败
password_hash() 怎么调用才正确
它自动处理盐生成、算法选择和格式封装,你唯一要做的就是:传入密码,存下返回值,验证时原样喂给 password_verify()。
实操要点:
- 始终用
PASSWORD_DEFAULT(不是PASSWORD_BCRYPT)——它会随 PHP 升级自动切换更安全的算法(目前是 bcrypt,未来可能是 Argon2) - 数据库字段必须是
VARCHAR(255)或更长——bcrypt 输出约 60 字符,Argon2 可达 190+,留足余量 - 不要截断、base64 编码、urlencode 或任何二次处理返回值——
password_hash()输出是完整可解析的字符串,含算法标识、成本因子、盐和哈希,破坏任一节都会让password_verify()失效 - 示例:
password_hash("mypass123", PASSWORD_DEFAULT)返回类似$2y$10$9vXJZ.../QzA8w的字符串,直接入库即可
password_verify() 验证失败的典型原因
不是密码错了,而是存储或比对环节出了偏差。最常踩的坑集中在数据流转过程:
- 注册时用了
PASSWORD_DEFAULT,但登录验证时却用password_hash($input, PASSWORD_BCRYPT)去生成比对串——错!验证必须用password_verify($input, $hash_from_db),不能自己再哈希 - 从数据库读出的哈希值被 trim()、stripslashes() 或其他过滤函数处理过——尤其注意 MySQL 的
SELECT结果若字段类型为CHAR且长度不足,会自动右补空格,PHP 的==比较会忽略末尾空格,但password_verify()严格校验字节流,空格会导致失败 - 使用了旧版 PHP(password_hash() 在 PHP 5.5+ 才原生支持,低版本需额外加载
- 哈希值被写入日志或调试输出时被截断(比如日志系统限制单行长度)——看似存进去了,实际入库的是残缺字符串
别忽略「密码重哈希」这个隐性需求
今天用 PASSWORD_DEFAULT 存的密码,几年后 PHP 升级可能默认算法变成 Argon2。这时用户下次登录,password_verify() 仍能成功,但你可以顺手用新算法重哈希一次再更新到数据库——这样存量密码也会逐步升级。
判断是否需要重哈希很简单:password_needs_rehash($hash_from_db, PASSWORD_DEFAULT) 返回 true 就该更新。这个逻辑容易被跳过,但它是让老系统持续保持安全的关键动作。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











