必须使用 password_hash + password_verify,因其自动处理盐值、算法选择与迭代轮数,而手动拼接盐值用 md5/sha1 会放弃安全基线,导致易被批量字典攻击。

为什么不能自己拼接盐值再用 md5 或 sha1
因为手写加盐逻辑等于放弃密码哈希的安全基线。ThinkPHP 5.1+ 默认用 password_hash(PHP 原生),它自动处理盐值生成、算法选择(bcrypt)、轮数迭代,且盐值直接嵌入哈希结果中——你根本不需要、也不该碰盐值字符串本身。
常见错误现象:
• 用户注册时用 md5($password . $salt) 存库,登录时再同样拼接验证
• 把盐值单独存一列,还用固定字符串(如 'thinkphp2024')
• 盐值用 uniqid() 或 rand() 生成,没做 base64 编码或长度校验
- 这些做法让攻击者能批量跑字典(无迭代、无随机盐、算法过时)
-
password_hash生成的哈希值自带算法标识、成本因子和盐,例如$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi,验证时只需原样传给password_verify - ThinkPHP 的
think\facade\Auth或自定义登录逻辑里,必须用password_hash+password_verify,别绕开
ThinkPHP 中注册时怎么安全调用 password_hash
不是“加盐”,是直接哈希。ThinkPHP 不提供封装函数,你得自己调用 PHP 原生函数,但要注意参数和上下文。
使用场景:用户注册、管理员重置密码、API 接口创建账号
- 用
PASSWORD_DEFAULT(当前是bcrypt),别硬写PASSWORD_BCRYPT—— 后续 PHP 升级可能切换算法,DEFAULT会自动适配 - 显式指定成本因子(
cost)更可控,比如['cost' => 12],避免服务器负载突增;默认是 10,太低(易被爆破),太高(登录卡顿) - 别对空密码或超长密码不做拦截:
strlen($password) 或 <code>> 72时直接拒绝,password_hash对 >72 字节只取前 72 字节,容易引发误判
示例:
use think\facade\Db;
$password = $this->request->param('password');
if (strlen($password) 72) {
throw new \Exception('密码长度必须在 8–72 字符之间');
}
$hash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
Db::name('user')->insert([
'username' => $this->request->param('username'),
'password' => $hash, // 直接存完整哈希串,长度约 60 字符
]);
登录验证为什么必须用 password_verify 而不是 == 或 hash_equals
因为 password_hash 输出的哈希值含盐、算法、成本等元信息,password_verify 才知道怎么正确解构并重算比对。自己用 == 等于把哈希当普通字符串比,完全失效。
常见错误现象:
• 登录时写 if ($user['password'] === md5($input))
• 用 hash_equals($user['password'], $hash) —— 这是防时序攻击的,但前提是两边都是合法哈希,而你输入的是明文
-
password_verify内部自动提取盐和参数,用相同算法重哈希明文,再安全比对 - 它天然防御时序攻击,无需额外套
hash_equals - 如果数据库里存的是旧 MD5 哈希,
password_verify会直接返回false,不会报错,你要靠返回值判断是否需迁移密码
示例:
$user = Db::name('user')->where('username', $username)->find();
if ($user && password_verify($inputPassword, $user['password'])) {
// 登录成功
} else {
// 失败:可能是密码错,也可能是老哈希格式不支持,可记录日志区分
}
升级老项目时怎么平滑迁移 MD5/SHA1 密码到 password_hash
不能强制所有用户改密码,也不能停服批量重哈希——要登录时渐进升级。
使用场景:TP3.x 升 TP6、或历史系统接入新认证模块
- 首次用旧密码登录成功后,立即用
password_hash生成新哈希,更新数据库password字段 - 判断是否需要升级:若
$user['password']长度是 32(MD5)或 40(SHA1),且password_verify($input, $user['password']) === false,就说明是旧格式,但明文对得上(你得自己用md5()验证一次) - 注意:迁移期间,验证逻辑要分支处理,但代码路径必须清晰,避免漏掉某类哈希格式
- 别存双份哈希字段(如
password_v1和password_v2)——增加维护负担,也暴露旧密码存储方式
关键点在于:迁移只发生在用户主动登录那一刻,没人登录的账号永远保持旧格式,直到下次触发。没有“一刀切”的时机。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










