在 PHP 中,用户认证是构建安全应用的基础环节。比较密码或令牌时,如果采用不安全的字符串比较方法,可能会导致时间攻击(Timing Attack),从而让攻击者推断出敏感信息。PHP 提供了一个专门用于防止这类攻击的函数 hash_equals(),能够以恒定时间的方式比较两个字符串,确保安全性。
本文将介绍如何利用 hash_equals() 函数实现安全的用户认证流程,避免常见的安全隐患。
时间攻击是指攻击者通过测量程序对不同输入字符串进行比较所需时间的差异,逐步猜测出敏感信息,比如密码哈希或身份验证令牌。
传统的字符串比较函数(如 == 或 strcmp)在匹配失败时会尽早返回,导致比较时间依赖于字符串的相似度,从而暴露信息。
hash_equals() 是 PHP 5.6 及以上版本提供的函数,设计用于对两个字符串进行恒定时间的比较,避免因返回时间差异导致的信息泄露。
函数原型:
bool hash_equals(string $known_string, string $user_string)
$known_string:已知的字符串(通常是存储的哈希值)。
$user_string:用户输入的字符串(需要验证的值)。
如果两个字符串相等,返回 true,否则返回 false。
假设用户注册时,我们将密码使用安全算法哈希后存储,登录时我们需要验证用户输入的密码。
示例代码:
<?php
// 用户注册时,将密码哈希存储到数据库
$password = 'user_password123';
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// 用户登录时,验证密码
$input_password = $_POST['password'] ?? '';
// 验证密码
if (password_verify($input_password, $hashed_password)) {
// 取出数据库中保存的哈希字符串
$stored_hash = $hashed_password;
// 将用户输入密码重新哈希用于比较(这里示范场景为验证令牌等情况)
$input_hash = hash('sha256', $input_password);
// 使用 hash_equals 进行恒定时间比较
if (hash_equals($stored_hash, $input_hash)) {
echo "认证成功";
} else {
echo "认证失败";
}
} else {
echo "认证失败";
}
?>
注意:上述示例中 password_verify() 本身已安全验证密码哈希,hash_equals() 适用于验证类似 API 令牌或自定义哈希值的场景。
在许多场景中,我们需要验证用户传递的 API 令牌是否匹配服务器端存储的令牌,若直接用 == 比较存在时间攻击风险。
示例:
<?php
// 服务器端存储的API令牌
$stored_token = 'abc123def456';
// 用户提交的令牌
$provided_token = $_GET['token'] ?? '';
// 进行安全比较
if (hash_equals($stored_token, $provided_token)) {
echo "令牌验证通过";
} else {
echo "令牌验证失败";
}
?>
这样即使令牌不匹配,攻击者也无法通过比较耗时推断出正确的令牌。
hash_equals() 提供了防止时间攻击的恒定时间字符串比较。
在密码验证中,推荐使用 password_hash() 与 password_verify(),hash_equals() 适用于令牌或哈希值的安全比较。
通过合理使用 hash_equals(),可以显著提升认证过程的安全性,减少敏感信息泄露的风险。
如果你想了解更多关于 PHP 安全认证的最佳实践,可以访问 https://gitbox.net/php-security。