了解如何在bcrypt password_hash中生成/使用salt

时间:2016-12-06 08:34:59

标签: php symfony hash bcrypt php-password-hash

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

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

直到最新FOSUserBundle之一更新bcrypt密码哈希和存储在数据库中的已使用salt

使用REST API进行连接时,会将salt传输到客户端,以使用相同的salt对密码进行本地哈希处理。散列密码将发送回Web应用程序进行身份验证。

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

问题: 他们的方式FOSUserBundle哈希密码已经改变:因为不考虑手动指定盐而是让PHP自动生成盐(在PHP 7中甚至不可能手动设置盐),不再支持手动盐。

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

有没有办法合并这两种方法?让PHP自动创建盐,提取并将此盐发送给客户端?

据我所知,salt与哈希存储在同一个字符串中:

enter image description here

但是,只需从哈希字符串中复制21个char盐并将这些发送到客户端就不起作用了。 似乎这21个字符足以测试/验证密码,但不能重新创建哈希。这是正确的吗?

那么,是否有任何解决方案可以在不设置盐的情况下使用PHP password_hash,并且同时了解使用过的盐?

编辑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;

结果:

$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
如果未指定salt,

password_hash将为相同的密码创建新哈希。这是因为,盐将随机创建,而不是每次调用时产生不同的盐。

编辑2:

正如编辑1中所见,使用具有32个字符的盐将导致字符串仅包含盐的前21个字符。但是,这个salt-prefix不能用于重新创建相同的哈希,因为它太短而无法被接受。

但是,如果前缀填充为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;

所以解决方案可能是:

  • FOSUserBundle使用password_hash创建哈希,无需手动指定盐。
  • 从结果字符串中提取salt并将其填充为0到32个字符的长度
  • 将此盐传递给客户

任何人都可以确认,这是一个真正的解决方案而不仅仅是巧合吗?

3 个答案:

答案 0 :(得分:0)

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

reloadData()

salt5678901234567890123456789012盐中的最后2个盐变成了你,只是隐窝河豚的一些魔力。

答案 1 :(得分:0)

让客户提供任何盐。或者服务器提供从未使用过的随机盐。将哈希视为原始密码。重新哈希哈希密码和其他内容。

答案 2 :(得分:0)

您似乎误解了密码哈希和盐应该如何工作。

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

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

也许你所参与的项目盐的实施不好。事实上,不鼓励使用手工盐的原因之一是防止这样的事情。

  

所以解决方案可能是:

     

让FOSUserBundle使用password_hash来创建没有的哈希   手动指定盐。

     

从结果字符串中提取salt并将其填充为0到一个长度   32个字符

     

将此盐传递给客户

     

任何人都可以确认,这是一个真正的解决方案而不仅仅是一些   巧合?

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