You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免将空字节传入Bcrypt(password_hash)函数?

问题:生成“记住我”安全令牌时,空字符导致password_hash抛出ValueError

我有一个用于生成浏览器登录“记住我”安全令牌的脚本,代码如下:

$token = random_bytes(32);

随后将该令牌存入数据库,用于后续比对,相关代码如下:

$sql = " INSERT INTO auth_tokens (user, user_email, auth_type, selector, token, expires_at) VALUES (?, ?, 'remember_me', ?, ?, ?) ";
$stmt = mysqli_stmt_init($conn);
$hashedToken = password_hash($token, PASSWORD_DEFAULT);
// ... more param definitions
mysqli_stmt_bind_param($stmt, $s, $username, $email, $selector, $hashedToken, $date);
mysqli_stmt_execute($stmt);

问题在于,random_bytes()有时会生成空字符,进而导致password_hash()抛出错误。该致命ValueError是近期添加到Bcrypt中的:

PHP Fatal error:  Uncaught ValueError: Bcrypt password must not contain null character in *** Stack trace:***: password_hash() {main} thrown in ***

请问预防random_bytes()生成的空字符传入的正确方法是什么?直接使用str_replace('\0', '', $token)是否合理?


解决方案

为什么直接用str_replace('\0', '', $token)不合理

直接替换空字符会破坏令牌的安全性:

  • random_bytes(32)生成的是固定32字节的二进制数据,替换空字符后长度会缩短,导致令牌的随机性(熵)下降,降低抗破解能力。
  • 空字节的数量和位置不可控,可能导致不同的原始令牌被处理成相同的字符串,增加碰撞风险。

正确的处理方式:二进制转文本编码

random_bytes()生成的是二进制字符串,要彻底避免空字节问题,最安全的做法是将其转换为Base64或Hex编码的纯文本字符串——这种转换是无损的,既保留了原始数据的全部熵,又不会产生空字节。

示例1:使用Base64编码

$token = random_bytes(32);
// 将二进制令牌转换为Base64编码的文本字符串
$tokenText = base64_encode($token);
// 安全传入password_hash
$hashedToken = password_hash($tokenText, PASSWORD_DEFAULT);

// 存入数据库的逻辑保持不变
$sql = " INSERT INTO auth_tokens (user, user_email, auth_type, selector, token, expires_at) VALUES (?, ?, 'remember_me', ?, ?, ?) ";
$stmt = mysqli_stmt_init($conn);
mysqli_stmt_bind_param($stmt, 'ssssss', $username, $email, $selector, $hashedToken, $date);
mysqli_stmt_execute($stmt);

示例2:使用Hex编码

$token = random_bytes(32);
// 将二进制令牌转换为十六进制文本字符串
$tokenText = bin2hex($token);
$hashedToken = password_hash($tokenText, PASSWORD_DEFAULT);

// 后续存储逻辑同上

后续比对注意事项

当浏览器返回“记住我”令牌时,需要确保传入password_verify的是相同编码格式的文本字符串,比如:

// 假设从Cookie中获取的是Base64编码的令牌
$receivedToken = $_COOKIE['remember_token'];
// 从数据库取出对应的哈希值
$storedHashedToken = /* 查询数据库的结果 */;

if (password_verify($receivedToken, $storedHashedToken)) {
    // 令牌验证通过
}

内容的提问来源于stack exchange,提问作者Fid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 03:12:41