了解 bcrypt password_hash 中的盐是如何 generated/used

Understanding how salt is generated/used in bcrypt password_hash

我正在开发一个现有的 Symfony 2.8 网络应用程序项目,该项目使用 FOSUserBundle 进行用户身份验证。

除了 Web 前端,用户还可以使用不同的智能手机客户端通过 REST API 连接到 Web 应用程序。因此,在直接登录 Web 应用程序和连接时都需要对用户进行身份验证 为什么 REST API.

直到最近的一个 FOSUserBundle 更新了一个 bcrypt 密码散列和使用过的 salt 存储在数据库中的位置。

当使用 REST API 连接时,salt 被传输到客户端以使用相同的 salt 在本地散列密码。哈希密码然后发送回网络应用程序进行身份验证。

我知道发送散列密码而不是纯文本不会增加(很多)额外的安全性,因为只能使用 HTTPS 进行通信。然而,这是客户端的工作方式:他们需要盐来生成散列密码。我可以在将来更新客户端,但现在这就是工作方式。

问题: 他们以 FOSUserBundle 散列密码已更改的方式:因为不手动指定盐但让 PHP 自动生成盐被认为是更安全的(在 PHP 7 中它甚至不是可以手动设置盐),不再支持手动盐。

直接登录 Web 应用程序时没有问题,但由于 REST 客户端仍需要盐,因此此更新会中断 REST 连接。

有什么方法可以结合这两种方法吗?让PHP自动创建salt,提取并发送这个salt给客户端?

据我了解,盐与散列存储在同一个字符串中:

但是,简单地从散列字符串中复制 21 个字符的盐并将其发送给客户端是行不通的。 这 21 个字符似乎足以 test/verify 密码,但不足以重新创建哈希。这是正确的吗?

那么,有没有什么解决方案可以在不设置salt的情况下使用PHPpassword_hash,同时了解所用的salt?

编辑 1:

回答@RiggsFolly 的问题:MD5 一直未被使用。 正确,bcryp/password_hash 不会两次创建相同的散列。如果密码和盐相同,它将这样做:

$s = 'password';
$salt = 'salt5678901234567890123456789012';

$options['salt'] = $salt;
$h1 = password_hash($s,PASSWORD_BCRYPT,$options);
$h2 = password_hash($s,PASSWORD_BCRYPT,$options);

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

结果:

y$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
y$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
如果未指定盐,

password_hash 将为相同的密码创建一个新的哈希值。这是因为,salt 将随机创建,这在每次调用时都会有所不同。

编辑 2:

正如在编辑 1 中看到的那样,使用 32 个字符的 salt 将导致字符串仅包含 salt 的前 21 个字符。但是,此盐前缀不能用于重新创建相同的哈希,因为它太短而无法被接受。

不过,如果前缀补0的话,好像还行:

$s = 'password';
$salt        = 'salt5678901234567890123456789012';
$salt_prefix = 'salt5678901234567890100000000000';

$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;

所以解决方案可能是:

谁能证实这是一个真正的解决方案,而不是巧合?

http://us2.php.net/crypt 中所述,盐是 22 个字符,而不是 21 个。

<?php
$key = 'password';
$hash = password_hash($key, PASSWORD_BCRYPT);
$salt = substr($hash, 7, 22);
$rehash = password_hash($key, PASSWORD_BCRYPT, ['salt' => $salt]);
if ($hash == $rehash) {
  echo 'ok', PHP_EOL;
}

salt5678901234567890123456789012 salt 中的最后 2 个更改为 u 只是 crypt blowfish 中的一些魔法。

让客户提供任何盐。或者服务器给出了一个它从未使用过的随机盐。将散列视为原始密码。重新散列散列密码和其他内容。

您似乎误解了密码散列和盐的工作原理。

盐永远不会发送给客户端。它仅在创建密码时生成(或手动指定)一次。它的目的是随机化哈希函数的输出,这样当数据库落入坏人之手时,就不可能通过将输出与彩虹表进行比较来获取用户密码。

当用户使用他的密码登录时,密码从客户端发送到服务器,未经哈希处理(但通常通过 https)。密码比较函数然后获取存储的散列+密码,从中获取盐,将盐附加到用户输入,计算散列,然后将其与数据库中的散列进行比较。

也许您正在进行的项目对盐的实现不佳。事实上,不鼓励使用手动盐的原因之一是为了防止这样的事情发生。

So a solution could be:

let FOSUserBundle use password_hash to create the hash without manually specifying a salt.

extract the salt from the result string and pad it with 0 to a length of 32 chars

pass this salt to the client

Can anyone confirm, that this a real solution and not just some coincidence ?

这不是一个好的解决方案。唯一的好方法是确保以正确的方式实施密码散列,这样您就不必多次生成盐。