PHP crypt() 修剪盐,因为它太长了

信息安全 哈希 php 河豚
2021-08-23 02:34:07

我正在使用 Blowfish 和 PHP crypt() 进行密码散列,但我注意到一些奇怪的东西。引用 PHP 文档:

CRYPT_BLOWFISH - Blowfish 用盐散列如下:“$2a$”、“$2x$”或“$2y$”,两位数的成本参数,“$”和字母表中的 22 位数字“./0-9A” -ZZ”。

我注意到包含在最终哈希中的盐短 1 个字符(最后一个被切断),好像盐太长了,但事实并非如此。

我的脚本输出示例:

盐:97504ebb48c4619f820f83,长度为 22

河豚:$2a$13$97504ebb48c4619f820f8u4QTtlV5MoqHt9l7hmK4jEohUXrI.0PK

哈希匹配。

如您所见,随机盐恰好是 22 位数字,但最终散列中缺少“3”。如果我只将盐设为 21 个字符,我会得到一个损坏的哈希并且它不起作用。那么为什么它会修剪最后一个字符呢?

PHP 手册中的示例还向随机盐添加了最后一个 $。那美元是有原因的,还是他们只是随机地将它添加到 Blowfish、SHA-256 和 SHA-512 以混淆所有人?

最后,这是我的代码:

if (CRYPT_BLOWFISH == 1) {
    $salt = md5(uniqid(rand(), TRUE));
    $salt = substr($salt, 0, 22);

    echo "Salt: " . $salt . " with length " . strlen($salt) . "<br />";
    $pass = "rasmuslerdorf";

    $bsalt = "$2a$13$".$salt;
    $blowfish=crypt($pass, $bsalt);
    echo 'Blowfish:     ' . $blowfish . "<br />";

    if (crypt($pass, $blowfish) == $blowfish) {
        echo "Hash match.<br />";
    }
    else echo "no<br />";
}
else {
    exit("You need php 5.3 or newer");
}
1个回答

实际上'3'在那里,但它被称为'u'。

说明: bcrypt 需要一个 128 位的盐。你提供的盐应该是“修改过的base64”,即由字母、数字、'/'或'.'组成。符号(这是“修改的”,因为在真正的Base64中,使用了 '+' 符号而不是 '.',并且顺序不同)。由于这是一个 64 个元素的字母表,每个字符值 6 位,而您的 22 个字符编码 132 位。

Bcrypt 仅使用 132 位中的前 128 位,因此后四位完全被忽略。最后四位是最后一个盐字符的最后四位。在修改后的 base64 中,“u”的二进制值是 48、110000,而“3”的二进制值是 57、111001。如您所见,这两个值仅在最后(最右边)四位不同,这些位被忽略。

发生的情况是,您首先使用的 bcrypt 实现将您的 salt(22 个字符)转换为 128 位缓冲区(16 个字节),这就是 drop 发生的地方。然后代码会将其转换修改后的 base64,但这一次它将使用零作为“丢失的四位”,从而将您的“3”变成“u”。如果你使用 '97504ebb48c4619f820f8u' 而不是 '97504ebb48c4619f820f83' 作为 salt,你会发现你获得了完全相同的 bcrypt 输出,因为这两个 salt 仅在最后四位不同,被忽略。