了解PHP password_hash使用的bcrypt salt

afdcj2ne  于 2023-02-15  发布在  PHP
关注(0)|答案(2)|浏览(127)

我有一些麻烦,以了解如何bcrypt使用盐。我知道什么盐是好的,但我不明白如何盐的价值是确切的使用。

    • 问题1:**正确的盐长是多少?

我找到的所有来源都说,salt的长度为22,它与算法、成本和实际散列值一起存储在结果字符串中。

但是,我找到的所有实现都使用长度为32的salt。例如,Symfony使用的FOSUserBundle使用以下代码创建salt:

$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)

由于sha1散列的长度为32个字符,因此生成的salt的长度也为32。这只是一个懒惰的实现,跳过代码将字符串的长度调整为22,因为这是由bcrypt自己完成的吗?还是出于某种原因需要32个字符?

    • 问题2:**盐长度为22真的正确吗?

在下面的示例中,似乎只有salt的前21个字符保存在结果字符串中。将这21个字符作为salt传递给password_hash将导致错误,但填充0将起作用:

$s = 'password';
$salt        = 'salt5678901234567890123456789012';
$salt_prefix = 'salt567890123456789010'; // first 21 chars of salt + 0

$h1 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt));
$h2 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt_prefix));

echo $h1 . PHP_EOL;
echo $h2 . PHP_EOL;

//Result
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu

所以,我们需要传递一个至少有22个字符的salt给算法,但是第22个字符似乎是无用的,对吗?如果第22个字符根本没有被使用,那么它有什么意义呢?

    • 问题3:**为什么不手动指定盐?

在PHP函数password_hash中使用手动哈希是不受欢迎的。相反,我们鼓励使用password_hash自动哈希,因为这样会更安全。
我知道使用"弱"盐或相同的盐为所有密码可能会导致风险,由于彩虹表。但为什么使用自动生成的盐一般更安全?
为什么使用自动生成的salt比手动生成的salt更安全,生成的salt如下所示:

$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
    • 问题4:**是否有password_hash的替代品仍然允许使用自定义salt?

由于我正在进行的项目的实现,我需要控制用于生成密码散列的盐值。这可以在将来更改,但要知道,有必要手动设置盐值。由于password_hash中不推荐使用此功能,我需要一些替代方法来生成散列。如何做到这一点?

    • 编辑:**

简单解释一下为什么我需要控制盐:密码不仅用于直接登录Web应用,还用于通过REST API连接应用。客户端从服务器请求salt,并使用它(算法和成本已知)对用户在客户端输入的密码进行哈希。
散列密码然后发送回服务器进行身份验证。目的是不以纯文本形式发送密码。为了能够在客户端生成与服务器相同的散列,客户端需要知道服务器使用了哪种salt。
我知道散列密码不会增加任何真正的安全性,因为通信已经只使用HTTPS。然而,这是客户端当前的操作方式:如果客户端发送回正确的密码哈希,则授权进行身份验证。
我不能改变服务器端而不破坏数以千计的现有客户端。客户端可以在未来的某个时候更新,但这将是一个漫长的过程。
既然这样做了,我需要遵循旧的流程,这意味着我需要能够告诉客户盐。
但是我需要自己生成盐。如果PHP知道最安全的方法来做这件事,我完全可以接受。但是我确实需要以某种方式获取/提取盐,将其发送给客户端。

    • 如果我理解正确的话,我可以让password_hash来完成这个工作,然后从结果字符串中提取字符7 - 29。
bnl4lu3b

bnl4lu3b1#

    • 问题1:**正确的盐长是多少?

我找到的所有来源都说,salt的长度为22,它与算法、成本和实际散列值一起存储在结果字符串中。
如果所有的消息来源都这么说,你就没有理由质疑...
没有通用的salt大小,它取决于算法,对于bcrypt,它是22 ......虽然有一个陷阱,必要的大小实际上是16字节,但实际上是Base64编码的()。
当你对16字节的数据进行Base64编码时,会得到一个24字符长的ASCII字符串,
最后两个字符是不相关的 *-当你修剪掉这两个不相关的字符时,它就变成了22。
为什么它们是不相关的?你的问题已经足够广泛了...阅读维基百科的Base64页面。

    • 实际上有一些Base64 "方言",bcrypt使用的方言与PHP的base64_encode()并不完全相同。*

但是,我找到的所有实现都使用长度为32的salt。例如,Symfony使用的FOSUserBundle使用以下代码来创建salt:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
由于sha1散列的长度为32个字符,因此生成的salt的长度也为32。这只是一个懒惰的实现,跳过代码将字符串的长度修剪为22,因为这是由bcrypt自己完成的吗?还是出于某种原因需要32个字符?
该行将产生一个31个字符的字符串,而不是32个字符,但这实际上并不相关。如果您提供一个更长的字符串,则只会使用其中的 * necessary * 部分-最后的字符将被忽略。
您可以自己测试:

php > var_dump(password_hash('foo', PASSWORD_DEFAULT, ['salt' => str_repeat('a', 22).'b']));
string(60) "$2y$10$aaaaaaaaaaaaaaaaaaaaaO8Q0BjhyjLkn5wwHyGGWhEnrex6ji3Qm"
php > var_dump(password_hash('foo', PASSWORD_DEFAULT, ['salt' => str_repeat('a', 22).'c']));
string(60) "$2y$10$aaaaaaaaaaaaaaaaaaaaaO8Q0BjhyjLkn5wwHyGGWhEnrex6ji3Qm"
php > var_dump(password_hash('foo', PASSWORD_DEFAULT, ['salt' => str_repeat('a', 22).'d']));
string(60) "$2y$10$aaaaaaaaaaaaaaaaaaaaaO8Q0BjhyjLkn5wwHyGGWhEnrex6ji3Qm"

(if使用了额外的字符,则生成的哈希值将有所不同)
我不熟悉FOSUserBundle,但是是的--它看起来确实像是在做一些懒惰的、不正确的事情。

    • 问题2:**盐长度为22真的正确吗?

在下面的例子中,似乎只有salt的前21个字符保存在结果字符串中。将这21个字符作为salt传递给password_hash将导致错误,但填充0将起作用:

$s = 'password';
$salt        = 'salt5678901234567890123456789012';
$salt_prefix = 'salt567890123456789010'; // first 21 chars of salt + 0

$h1 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt));
$h2 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt_prefix));

echo $h1 . PHP_EOL;
echo $h2 . PHP_EOL;

//Result
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu

所以,我们需要传递一个至少有22个字符的salt给算法,但是第22个字符似乎是无用的,对吗?如果第22个字符根本没有被使用,那么它有什么意义呢?
这并不是真的无关紧要......用"A"填充它,你会看到一个不同的结果。
老实说,我无法正确解释这一点,但这也是由Base64的工作方式引起的,因为在生成的哈希中,您实际上看到了类似于以下内容的内容(伪代码):

base64_encode(  base64_decode($salt) . $actualHashInBinary  )

也就是说,(假设)Base64编码的盐首先被解码为原始二进制,用于创建实际的哈希(同样是原始二进制),这两个被连接起来,然后整个东西都是Base64编码的。
由于输入的salt实际上是24个长度的全长中的22个相关值,所以我们实际上在末尾有一个 * uncomplete * 块,它在原始哈希的开头完成(填充?)...
连接两个单独的Base64编码值和在对原始值进行Base64编码之前连接原始值是不同的。

    • 问题3:**为什么不手动指定盐?

在PHP函数password_hash中,不推荐使用手动哈希。相反,我们鼓励使用password_hash自动哈希,因为这样会更节省。
我知道使用"弱"盐或相同的盐为所有的密码可能会导致风险,由于彩虹表。但为什么它是节省使用自动生成的盐一般?
简单地说-盐需要是 * 加密安全的 (即不可预测的),PHP已经知道如何做到这一点,而你可能(压倒性地)不知道。
除非你有一个实际的硬件CSPRNG(PHP还没有配置好使用它),否则你能做的最好的事情就是让PHP自动生成salt。然而,在这里,你显然想做相反的事情(不管出于什么原因),并在这个过程中降低它的安全性--
很多人 * 都这样做。
这就是为什么salt选项被弃用的原因--为了保护你不受自己的伤害。:)
为什么使用自动生成的盐而不是手动生成的盐会更省钱,生成的盐如下所示:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
正如我所说的,salt需要是不可预测的,在这个特定的例子中--使用的函数没有一个是不可预测的,即使是mt_rand()
是的,mt_rand()实际上并不是随机的,尽管它的名字暗示了这一点。

    • 问题4:**是否存在仍然允许使用自定义salt的password_hash替代品?

由于我正在进行的项目的实现,我需要控制用于生成密码散列的盐值。这可以在将来更改,但要知道,有必要手动设置盐值。由于password_hash中不推荐使用此功能,我需要一些替代方法来生成散列。如何做到这一点?
你不知道。
你的项目完全没有理由去规定password_hash()盐是如何生成的,我不知道你为什么认为这是必要的,但是100%没有必要--这没有意义。

尽管如此,最终-这就是为什么在删除某些东西之前要先放置弃用选项的原因。现在您知道salt选项将来会被删除,并且您有足够的时间重构您的应用程序。
明智地使用它,不要试图复制过时的功能。你应该在相反的方向工作-问如何在不破坏你的应用程序的情况下将两者分开。

nvbavucw

nvbavucw2#

你可以在blowfish中使用crypt。在2023年它仍然接受自定义salt。不建议在密码中使用相同的salt,但是对于标识符,例如电子邮件地址,它总比什么都没有或者使用校验和算法要好。

相关问题