如何使用PHP的password_hash函数进行密码哈希加密及验证过程详解?

2026-05-07 15:251阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计919个文字,预计阅读时间需要4分钟。

如何使用PHP的password_hash函数进行密码哈希加密及验证过程详解?

因为`password_hash()`默认使用bcrypt算法,并自动为每次调用生成唯一的盐值(salt),这是设计使然,不是bug。验证时,完全不需要也不应该去提取盐或比较原始的哈希字符串——内部会自动解析盐和参数。

  • 错误做法:把 password_hash() 结果存进数据库后,又试图用 md5($pwd.$salt) 手动重算比对
  • 正确做法:原样保存整个哈希字符串(如 $2y$10$9z8a...),验证时只传密码和这个完整字符串给 password_verify()
  • bcrypt 哈希长度固定为 60 字符,开头 $2y$ 表示算法版本,10$ 是 cost factor(迭代轮数),后面是 salt + hash 的 base64 编码

怎么选 cost factor?设太高会卡住用户登录

cost factor 控制 bcrypt 的计算强度,值每+1,耗时约翻倍。PHP 默认是 10,对应约 100ms 单次哈希,在现代服务器上安全且可用;设到 14 可能超过 1.5 秒,登录接口容易超时或被压垮。

  • 测试方法:microtime(true) 包裹 password_hash('test', PASSWORD_BCRYPT, ['cost' => 12]) 多次运行看平均耗时
  • 生产建议:从 10 开始,若服务器较新(如 CPU > 3GHz)、并发不高,可试 11 或 12;老旧机器或高并发网关层,别超过 10
  • PASSWORD_ARGON2ID 可替代 bcrypt,但需 PHP ≥ 7.3 且开启 libsodium 扩展,兼容性不如 bcrypt

password_verify() 返回 false 的常见原因

几乎全是输入或存储环节出错,跟算法本身无关。最常踩的坑是「哈希被截断」或「密码被 trim() 过」。

  • 数据库字段太短:VARCHAR(60) 刚好够 bcrypt,但若用 ARGON2ID 哈希可能达 90+ 字符,必须扩到 VARCHAR(255)
  • 表单提交时前端 JS 或后端 trim() 掉了密码首尾空格,而用户注册时输的是 "pass123 "(带空格),导致哈希不匹配
  • 从数据库读哈希时用了 mysql_fetch_row() 但没注意字段顺序,取错了列;或 PDO fetch mode 没设对,返回 null
  • password_verify() 第二个参数为 null 或空字符串时,永远返回 false,建议加 is_string($hash) && strlen($hash) >= 60 防御性判断

能不能用 password_hash() 加密其他数据?比如邮箱或 token?

不能。它专为密码设计:不可逆、抗彩虹表、慢速、带盐。拿它处理邮箱,既浪费 CPU,又破坏语义——邮箱需要查重、索引、模糊匹配,而哈希后完全失去这些能力。

立即学习“PHP免费学习笔记(深入)”;

  • 邮箱/用户名等标识字段:该用普通数据库唯一索引 + 小心脱敏,不是哈希
  • 短期 token(如重置链接):用 random_bytes() + base64_encode() 生成,配合过期时间与单次使用校验
  • 真要加密敏感字段(如身份证号):用 sodium_crypto_secretbox() 或 OpenSSL 的 AEAD 模式,而不是 password_hash()

bcrypt 哈希里藏不了额外信息,也解不开,硬套只会让后续维护的人在日志里疯狂搜 password_verify 却找不到问题根源。

标签:PHP

本文共计919个文字,预计阅读时间需要4分钟。

如何使用PHP的password_hash函数进行密码哈希加密及验证过程详解?

因为`password_hash()`默认使用bcrypt算法,并自动为每次调用生成唯一的盐值(salt),这是设计使然,不是bug。验证时,完全不需要也不应该去提取盐或比较原始的哈希字符串——内部会自动解析盐和参数。

  • 错误做法:把 password_hash() 结果存进数据库后,又试图用 md5($pwd.$salt) 手动重算比对
  • 正确做法:原样保存整个哈希字符串(如 $2y$10$9z8a...),验证时只传密码和这个完整字符串给 password_verify()
  • bcrypt 哈希长度固定为 60 字符,开头 $2y$ 表示算法版本,10$ 是 cost factor(迭代轮数),后面是 salt + hash 的 base64 编码

怎么选 cost factor?设太高会卡住用户登录

cost factor 控制 bcrypt 的计算强度,值每+1,耗时约翻倍。PHP 默认是 10,对应约 100ms 单次哈希,在现代服务器上安全且可用;设到 14 可能超过 1.5 秒,登录接口容易超时或被压垮。

  • 测试方法:microtime(true) 包裹 password_hash('test', PASSWORD_BCRYPT, ['cost' => 12]) 多次运行看平均耗时
  • 生产建议:从 10 开始,若服务器较新(如 CPU > 3GHz)、并发不高,可试 11 或 12;老旧机器或高并发网关层,别超过 10
  • PASSWORD_ARGON2ID 可替代 bcrypt,但需 PHP ≥ 7.3 且开启 libsodium 扩展,兼容性不如 bcrypt

password_verify() 返回 false 的常见原因

几乎全是输入或存储环节出错,跟算法本身无关。最常踩的坑是「哈希被截断」或「密码被 trim() 过」。

  • 数据库字段太短:VARCHAR(60) 刚好够 bcrypt,但若用 ARGON2ID 哈希可能达 90+ 字符,必须扩到 VARCHAR(255)
  • 表单提交时前端 JS 或后端 trim() 掉了密码首尾空格,而用户注册时输的是 "pass123 "(带空格),导致哈希不匹配
  • 从数据库读哈希时用了 mysql_fetch_row() 但没注意字段顺序,取错了列;或 PDO fetch mode 没设对,返回 null
  • password_verify() 第二个参数为 null 或空字符串时,永远返回 false,建议加 is_string($hash) && strlen($hash) >= 60 防御性判断

能不能用 password_hash() 加密其他数据?比如邮箱或 token?

不能。它专为密码设计:不可逆、抗彩虹表、慢速、带盐。拿它处理邮箱,既浪费 CPU,又破坏语义——邮箱需要查重、索引、模糊匹配,而哈希后完全失去这些能力。

立即学习“PHP免费学习笔记(深入)”;

  • 邮箱/用户名等标识字段:该用普通数据库唯一索引 + 小心脱敏,不是哈希
  • 短期 token(如重置链接):用 random_bytes() + base64_encode() 生成,配合过期时间与单次使用校验
  • 真要加密敏感字段(如身份证号):用 sodium_crypto_secretbox() 或 OpenSSL 的 AEAD 模式,而不是 password_hash()

bcrypt 哈希里藏不了额外信息,也解不开,硬套只会让后续维护的人在日志里疯狂搜 password_verify 却找不到问题根源。

标签:PHP