php - 如何使用河豚散列长密码(> 72 个字符)

标签 php security hash passwords blowfish

上周我阅读了很多关于密码散列的文章,而 Blowfish 似乎是目前最好的散列算法(之一)——但这不是这个问题的主题!

72 个字符的限制

Blowfish 只考虑输入密码的前 72 个字符:

<?php
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$hash = password_hash($password, PASSWORD_BCRYPT);
var_dump($password);

$input = substr($password, 0, 72);
var_dump($input);

var_dump(password_verify($input, $hash));
?>

输出是:
string(119) "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)"
string(72) "Wow. This is a super secret and super, super long password. Let's add so"
bool(true)

正如您所看到的,只有前 72 个字符很重要。 Twitter 正在使用河豚又名 bcrypt 来存储他们的密码 (https://shouldichangemypassword.com/twitter-hacked.php) 并猜猜是什么:将您的 Twitter 密码更改为超过 72 个字符的长密码,您只需输入前 72 个字符即可登录您的帐户。

河豚和胡椒

关于“peppering”密码有很多不同的意见。有人说这是不必要的,因为您必须假设 secret 胡椒串​​也是已知/已发布的,因此它不会增强散列。我有一个单独的数据库服务器,所以很可能只有数据库被泄露,而不是持续的胡椒。

在这种情况下(胡椒没有泄漏),您会根据字典进行更困难的攻击(如果不对,请纠正我)。如果你的胡椒串也泄漏了:还不错 - 你仍然有盐,它和没有胡椒的哈希一样受到保护。

所以我认为添加密码至少不是一个糟糕的选择。

建议

我的建议是为超过 72 个字符(和胡椒)的密码获取 Blowfish 哈希:

<?php
$pepper = "foIwUVmkKGrGucNJMOkxkvcQ79iPNzP5OKlbIdGPCMTjJcDYnR";

// Generate Hash
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$password_peppered = hash_hmac('sha256', $password, $pepper);
$hash = password_hash($password_peppered, PASSWORD_BCRYPT);

// Check
$input = substr($password, 0, 72);
$input_peppered = hash_hmac('sha256', $input, $pepper);

var_dump(password_verify($input_peppered, $hash));
?>

这是基于 this question :password_verify返回 false .

问题

什么是更安全的方法?首先获取 SHA-256 哈希值(返回 64 个字符)还是仅考虑密码的前 72 个字符?

优点
  • 用户仅输入前 72 个字符无法登录
  • 您可以在不超过字符限制的情况下添加胡椒
  • hash_hmac 的输出可能比密码本身具有更多的熵
  • 密码由两个不同的函数散列

  • 缺点
  • 仅使用 64 个字符来构建河豚哈希


  • 编辑 1:这个问题只针对blowfish/bcrypt 的PHP 集成。感谢您的评论!

    最佳答案

    这里的问题基本上是熵的问题。所以让我们开始看那里:

    每个字符的熵

    每字节熵的位数为:

  • 十六进制字符
  • 位:4
  • 值:16
  • 72 个字符的熵:288 位
  • 字母数字
  • 位数:6
  • 值:62
  • 72 个字符中的熵:432 位
  • “常见”符号
  • 位:6.5
  • 值:94
  • 72 个字符的熵:468 位
  • 完整字节
  • 位数:8
  • 值:255
  • 72 个字符的熵:576 位

  • 因此,我们的行为方式取决于我们期望的角色类型。

    第一个问题

    您的代码的第一个问题是您的“胡椒”哈希步骤输出了十六进制字符(因为未设置 hash_hmac() 的第四个参数)。

    因此,通过散列您的胡椒,您可以有效地将密码可用的最大熵减少 2 倍(从 576 到 288 位可能的位)。

    第二个问题

    然而,sha256只提供256首先是一些熵。所以你有效地将可能的 576 位减少到 256 位。您的哈希步骤 * 立即 *,根据定义丢失
    至少 密码中可能熵的 50%。

    您可以通过切换到 SHA512 来部分解决此问题,您只会将可用熵减少约 12%。但这仍然是一个不显着的差异。这 12% 将排列数量减少了 1.8e19 倍.这是一个很大的数字......那就是系数 它减少了...

    潜在问题

    潜在的问题是存在超过 72 个字符的三种类型的密码。这种风格系统对他们的影响将大不相同:

    注意:从现在开始,我假设我们正在与使用 SHA512 的胡椒系统进行比较。带有原始输出(不是十六进制)。
  • 高熵随 secret 码

    这些是使用密码生成器的用户,这些生成器会生成大量的密码 key 。它们是随机的(生成的,不是人为选择的),并且每个角色的熵都很高。这些类型使用高字节(字符 > 127)和一些控制字符。

    对于该组,您的散列函数将为 显着将他们的可用熵减少到 bcrypt .

    让我再说一遍。对于使用高熵、长密码的用户,您的解决方案可显着降低其密码的强度。 (72 个字符的密码丢失 62 位熵,更长的密码丢失更多)
  • 中熵随 secret 码

    该组使用的密码包含常用符号,但没有高位字节或控制字符。这些是您的可输入密码。

    对于这个组,您将稍微解锁更多的熵(不是创建它,而是允许更多的熵适合 bcrypt 密码)。当我说轻微时,我的意思是轻微。当您最大化 SHA512 的 512 位时,就会发生盈亏平衡。因此,峰值在 78 个字符处。

    让我再说一遍。对于此类密码,在用完熵之前,您只能存储额外的 6 个字符。
  • 低熵非随 secret 码

    这是使用可能不是随机生成的字母数字字符的组。像圣经引用之类的东西。这些短语每个字符大约有 2.3 位的熵。

    对于这一组,您可以通过散列显着解锁更多的熵(不是创建它,而是允许更多的熵适合 bcrypt 密码输入)。在您用完熵之前,盈亏平衡点约为 223 个字符。

    让我们再说一遍。对于此类密码,预散列肯定会显着提高安全性。

  • 回到现实世界

    这些类型的熵计算在现实世界中并不重要。重要的是猜测熵。这就是直接影响攻击者可以做什么的原因。这就是你想要最大化的。

    虽然很少有研究涉及猜测熵,但我想指出一些要点。

    连续随机猜出72个正确字符的几率是低。你更有可能赢得强力球彩票 21 次,而不是发生这种碰撞......这就是我们谈论的一个数字。

    但我们可能不会在统计上偶然发现它。在短语的情况下,前 72 个字符相同的可能性比随 secret 码高得多。但它仍然很低(基于每个字符 2.3 位,您更有可能中 5 次强力球彩票)。

    实际上

    实际上,这并不重要。有人猜对了前 72 个字符,而后者产生显着差异的可能性非常低,不值得担心。为什么?

    好吧,假设你正在接受一个短语。如果这个人能猜对前 72 个字符,他们要么是 真的幸运(不太可能),或者这是一个常见的短语。如果这是一个常见的短语,唯一的变量是制作它的时间。

    让我们举个例子。让我们从圣经中引用一段话(仅仅因为它是长文本的常见来源,而不是出于任何其他原因):

    You shall not covet your neighbor's house. You shall not covet your neighbor's wife, or his manservant or maidservant, his ox or donkey, or anything that belongs to your neighbor.



    那是 180 个字符。第 73 个字符是 g在第二个 neighbor's .如果您猜到了这么多,您可能不会停留在 nei ,但继续本节的其余部分(因为这可能是密码的使用方式)。因此,您的“哈希”并没有增加太多。

    顺便说一句:我绝对不提倡使用圣经引用。事实上,恰恰相反。

    结论

    您不会真正通过先散列来帮助那些使用长密码的人。有些团体你绝对可以提供帮助。有些你肯定会受伤。

    但最终,没有什么是过分重要的。我们正在处理的数字只是 方式太高了。熵的差异不会太大。

    你最好离开 bcrypt 原样。你更有可能搞砸散列(实际上,你已经这样做了,而且你不是第一个或最后一个犯这个错误的人),而不是你试图阻止的攻击将会发生。

    专注于保护站点的其余部分。并在注册时在密码框中添加密码熵计以指示密码强度(并指示密码是否过长,用户可能希望更改它)...

    那至少是我的 0.02 美元(或者可能超过 0.02 美元)......

    至于使用“ secret ”辣椒:

    实际上没有研究将一个哈希函数输入 bcrypt。因此,充其量也不清楚将“peppered”散列输入 bcrypt 是否会导致未知漏洞(我们知道做 hash1(hash2($value)) 可以暴露关于抗碰撞和原像攻击的重大漏洞)。

    考虑到您已经在考虑存储一个 key (“胡椒”),为什么不以一种经过充分研究和理解的方式来使用它呢?为什么不在存储之前加密散列?

    基本上,在对密码进行散列后,将整个散列输出输入到一个强大的加密算法中。然后存储加密结果。

    现在,SQL 注入(inject)攻击不会泄露任何有用的信息,因为它们没有 key 。如果 key 泄露,攻击者的处境并不比使用普通哈希(这是可证明的,胡椒“预哈希”不提供)更好。

    注意:如果您选择这样做,请使用库。对于 PHP,我 强烈推荐 Zend Framework 2 Zend\Crypt包。它实际上是我在当前时间点唯一推荐的。它已经过严格审查,并为您做出所有决定(这是一件非常好的事情)......

    类似的东西:
    use Zend\Crypt\BlockCipher;
    
    public function createHash($password) {
        $hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);
    
        $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
        $blockCipher->setKey($this->key);
        return $blockCipher->encrypt($hash);
    }
    
    public function verifyHash($password, $hash) {
        $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
        $blockCipher->setKey($this->key);
        $hash = $blockCipher->decrypt($hash);
    
        return password_verify($password, $hash);
    }
    

    它是有益的,因为您以易于理解和研究的方式使用所有算法(至少相对而言)。记住:

    Anyone, from the most clueless amateur to the best cryptographer, can create an algorithm that he himself can't break.


  • Bruce Schneier
  • 关于php - 如何使用河豚散列长密码(> 72 个字符),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16594613/

    相关文章:

    python - 限制 Python 的语法以安全地执行用户代码。这是一种安全的方法吗?

    asp.net - 登录asp.net后如何更改 session ID

    codeigniter - 如何将JS代码邮寄到Gmail,不执行,而只是显示

    python - 如何为我的类(class)实现 __cmp__() 和 __hash__() ?

    algorithm - 以两个值作为键的哈希表

    c++ - 以 pair 为键的 unordered_map 构建失败 (C++)

    php - 使用 Laravel 5.4 和推送器向公共(public) channel 广播通知

    php - 通过 sendgrid 发送邮件

    php - 为 WordPress、Joomla 或 Drupal 等 CMS 制作可安装模板是否需要具备丰富的 PHP 知识?

    php - 如何将自定义属性值应用于成本