PHP password_hash() 函数(详细教程)

PHP password_hash() 函数:安全存储用户密码的正确姿势

在 Web 开发中,用户登录功能几乎是每个系统都必须实现的核心模块。而其中最敏感、最关键的环节,就是用户密码的存储方式。曾经,很多人习惯把密码明文存进数据库,一旦数据库泄露,所有用户的账号信息就彻底暴露。这种做法不仅不安全,更是对用户信任的辜负。

今天,我们就来深入聊聊 PHP 中专门用于处理密码加密的函数 —— password_hash()。它不是简单的加密工具,而是一套经过安全验证的、专为密码设计的哈希算法。掌握它,是你构建安全应用的第一步。


为什么不能直接存储明文密码?

想象一下,你去银行开户,银行柜员把你的密码写在一张纸上,贴在墙上,还说“反正没人会看”。这听起来是不是很荒谬?但现实中,很多早期网站就是这样做的。

明文存储密码的问题在于:

  • 数据库一旦被黑客攻破,所有用户密码直接暴露;
  • 用户习惯重复使用密码,一个平台泄露,可能导致多个平台被入侵;
  • 一旦泄露,用户需要立即修改所有关联账户,体验极差。

所以,我们绝不能把密码原封不动地存进数据库。我们需要一种“单向”的处理方式:可以验证密码是否正确,但无法从存储结果反推出原始密码。

这就是哈希函数的作用。而 password_hash() 就是 PHP 为解决这个问题而提供的标准方案。


password_hash() 函数的基本用法

password_hash() 是 PHP 5.5.0 起引入的内置函数,它使用 bcrypt 算法(默认)对密码进行哈希处理。它的语法非常简单:

string password_hash ( string $password , int $algo [, array $options ] )
  • $password:要加密的原始密码;
  • $algo:指定哈希算法,最常用的是 PASSWORD_BCRYPT
  • $options:可选参数,比如成本因子(cost)。

下面是一个最基础的使用示例:

<?php
// 原始密码
$plain_password = "MySecurePass123!";

// 使用 password_hash() 生成哈希值
$hashed_password = password_hash($plain_password, PASSWORD_BCRYPT);

// 输出结果(保存到数据库)
echo $hashed_password;
// 示例输出:$2y$10$K9uGqXzZvR1p0lMwQmXkYOFr5o5qZ9vY9vY9vY9vY9vY9vY9vY9vY9v
?>

注释说明

  • PASSWORD_BCRYPT 是默认算法,使用 bcrypt 哈希;
  • 输出结果以 $2y$ 开头,这是 bcrypt 格式的标识;
  • 后面的 10 表示成本因子(cost),决定了计算的强度;
  • 整个字符串包含了盐值(salt)、成本和哈希值,无需单独管理盐。

什么是“盐值”?为什么它很重要?

盐值(salt)是密码哈希中的一个关键概念。你可以把它想象成“密码的调味料”——即使两个用户输入了相同的密码,只要盐值不同,最终生成的哈希值也完全不同。

如果没有盐值,黑客可以使用“彩虹表”(rainbow table)——一种预先计算好的密码哈希对照表,快速破解大量常见密码。

password_hash() 会自动生成一个唯一的盐值,确保即使两个用户都用了“123456”,他们的哈希结果也完全不同。这个过程完全由 PHP 内部处理,开发者无需手动管理。


控制哈希强度:成本因子(cost)

password_hash() 的默认成本因子是 10。成本因子越高,计算哈希所需的时间越长,安全性也越高,但对服务器性能的影响也越大。

如果你的系统用户量大、登录频繁,可以适当降低成本;如果安全性要求极高(如金融系统),建议提高成本。

<?php
$plain_password = "MySuperSecretPassword";

// 设置成本因子为 12,更安全
$hashed_password = password_hash($plain_password, PASSWORD_BCRYPT, [
    'cost' => 12
]);

echo $hashed_password;
// 输出:$2y$12$A1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
?>

注释说明

  • cost 参数控制 bcrypt 的工作量;
  • 每增加 1,计算时间大约翻倍;
  • 推荐值:10 ~ 12,平衡安全与性能;
  • 不要设得过低(如 4),否则容易被暴力破解。

如何验证用户输入的密码?

当你需要验证用户登录时,不能直接比较原始密码,而应该使用 password_verify() 函数。

<?php
// 从数据库读取存储的哈希值
$stored_hash = "$2y$10$K9uGqXzZvR1p0lMwQmXkYOFr5o5qZ9vY9vY9vY9vY9vY9vY9vY9vY9v";

// 用户输入的密码
$user_input = "MySecurePass123!";

// 使用 password_verify 验证
if (password_verify($user_input, $stored_hash)) {
    echo "登录成功!";
} else {
    echo "密码错误,请重试。";
}
?>

注释说明

  • password_verify() 会自动提取存储哈希中的盐值和算法;
  • 它会用相同的算法对用户输入重新计算哈希,再与原值比对;
  • 无需手动拼接盐值或指定算法,安全又方便;
  • 返回布尔值,适合用于 if 判断。

与旧方法的对比:为什么 password_hash() 更优?

password_hash() 出现之前,开发者常用 md5()sha1() 或自定义加密方法。这些方式存在明显缺陷:

方法 是否推荐 原因
md5($password) ❌ 不推荐 速度极快,容易被暴力破解;无盐值;可逆彩虹表攻击
sha1($password) ❌ 不推荐 同上,已被证明不安全
crypt($password, $salt) ⚠️ 低推荐 需手动管理盐值,容易出错
password_hash() ✅ 强烈推荐 自动盐值、安全算法、易于使用、持续更新

总结password_hash() 是目前 PHP 中唯一推荐用于密码存储的方式。它封装了所有安全细节,让开发者专注于业务逻辑。


实际应用场景:用户注册与登录流程

下面我们模拟一个完整的用户注册与登录流程,展示如何在项目中使用 password_hash()

注册流程

<?php
// 1. 接收用户提交的注册信息
$raw_password = $_POST['password']; // 假设已通过表单提交

// 2. 使用 password_hash 生成哈希值
$hashed_password = password_hash($raw_password, PASSWORD_BCRYPT, [
    'cost' => 10
]);

// 3. 将 $hashed_password 存入数据库
// 示例 SQL:
// INSERT INTO users (username, password_hash) VALUES ('alice', '$2y$10$...');

echo "注册成功,密码已安全存储。";
?>

登录流程

<?php
// 1. 从数据库查询用户信息
$username = $_POST['username'];
$query = "SELECT password_hash FROM users WHERE username = ?";
$stmt = $pdo->prepare($query);
$stmt->execute([$username]);
$user = $stmt->fetch();

if (!$user) {
    echo "用户不存在";
    exit;
}

// 2. 获取数据库中的哈希值
$stored_hash = $user['password_hash'];

// 3. 验证用户输入的密码
$input_password = $_POST['password'];

if (password_verify($input_password, $stored_hash)) {
    echo "登录成功,欢迎回来!";
    // 进入用户会话(如 $_SESSION['user'] = $username)
} else {
    echo "密码错误,请重试。";
}
?>

注释说明

  • 注册时只保存哈希值,不保存明文密码;
  • 登录时通过 password_verify 验证,不涉及明文比较;
  • 整个过程完全符合安全规范;
  • 适合用于任何基于 PHP 的 Web 应用。

常见误区与注意事项

  1. 不要对已哈希的密码再次哈希
    例如:password_hash(password_hash(...)) 是错误的,会导致哈希值无法验证。

  2. 不要在哈希中加入用户信息
    password_hash($password . $username) 会破坏算法设计,降低安全性。

  3. 不要手动管理盐值
    password_hash() 已经自动处理,无需关心盐值生成与存储。

  4. 确保 PHP 版本 ≥ 5.5.0
    低版本不支持该函数,需升级 PHP。

  5. 定期检查安全策略
    随着硬件发展,成本因子可能需要调整。建议每 1-2 年评估一次。


结语

password_hash() 函数是 PHP 安全开发的基石。它将复杂的密码安全问题,封装成一行简单的调用。作为开发者,我们不需要成为密码学专家,但必须具备正确的安全意识。

记住:永远不要把明文密码存进数据库。使用 password_hash(),就是你对用户数据最基本的尊重。

无论你是初学者还是有经验的开发者,掌握这个函数,都是迈向安全开发的重要一步。从今天起,让你的每一个登录系统都更安全、更可靠。