php - 了解盐是如何在 bcrypt password_hash 中生成/使用的

标签 php symfony hash bcrypt php-password-hash

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

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

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

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

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

问题: 他们以 FOSUserBundle 哈希的方式更改了密码:因为不手动指定 salt 而是让 PHP 自动生成 salt 被认为更省事(在 PHP 7 中,甚至无法手动设置盐),不再支持手动加盐。

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

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

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

enter image description here

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

那么,有没有什么解决方案可以在不设置salt的情况下使用PHP password_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;

结果:

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

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

编辑 2:

正如在编辑 1 中看到的那样,使用 32 个字符的盐将导致字符串仅包含盐的前 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;

所以解决方案可能是:

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

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

最佳答案

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

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

当用户使用他的密码登录时,密码从客户端发送到服务器,未经哈希处理(但通常通过 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 ?

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

关于php - 了解盐是如何在 bcrypt password_hash 中生成/使用的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40991000/

相关文章:

JavaScript:语法错误:参数列表后缺少 ) - 如何在 php 中正确转义 js?

php - 让玩家进入下一场比赛 - 锦标赛分组(需要一些逻辑)

php 函数..是否可以替代 msqli_fetch_assocs() 来使该函数输出 bool 值?

symfony - 通过 symfony2 提供文件 - 上传目录的路径

symfony - 如何转发请求到路线名称

perl - 在 Perl 中,if (%hash) 和 if (defined %hash) 有什么区别?

php - 无法更改 laravel 应用名称

php - Symfony 功能测试 - 如何使用请求模拟 Controller 注入(inject)的服务(提交)

ruby - 按键对散列进行分组并对值求和

algorithm - 需要帮助提出哈希函数算法