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

The 72 character limit

BooFISH只考虑输入密码中的前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正在使用blowfish(又名bcrypt)来存储他们的密码(https://shouldichangemypassword.com/twitter-hacked.php),你猜怎么着:把你的twitter密码改成一个超过72个字符的长密码,你只需输入前72个字符就可以登录到你的账户.

Blowfish and Pepper

关于"胡乱输入"密码有很多不同的观点.有些人说这是不必要的,因为您必须假设秘密的胡椒字符串也是已知的/发布的,这样它就不会增强散列.我有一个单独的数据库服务器,所以很可能只有数据库泄露,而不是常量的胡椒.

在这种情况下(pepper not Leak),基于字典的攻击会变得更加困难(如果不正确,请纠正我).如果你的胡椒串也泄漏了:没那么糟糕——你仍然有盐,它就像没有胡椒的土豆条一样受到保护.

因此,我认为加密密码至少不是一个坏 Select .

Suggestion

我建议对超过72个字符(和胡椒)的密码使用河豚哈希:

<?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.

The Question

什么是更安全的方式?首先获取SHA-256散列(返回64个字符),还是只考虑密码的前72个字符?

赞成的意见

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

欺骗

  • 只有64个字符用于构建河豚散列


Edit 1:这个问题只涉及Blowfish/bcrypt的PHP集成.感谢您的 comments !

推荐答案

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

每个字符的熵

每个字节的熵位数为:

  • Hex Characters
  • Alpha-Numeric
  • "Common" Symbols
    • 比特:6.5
    • 价值:94
    • 72个字符的熵:468位
  • Full Bytes

所以,我们的行为取决于我们期望的角色类型.

第一个问题

代码的第一个问题是"pepper"哈希步骤输出十六进制字符(因为第四个参数未设置为hash_hmac()).

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

第二个问题

然而,sha256首先只能提供256位的熵.所以你实际上是把一个可能的576位降到了256位.根据定义,你的散列步骤*立即*,会丢失

您可以通过切换到SHA512来部分解决这个问题,在这种情况下,您只会将可用熵减少大约12%.但这仍然不是一个无关紧要的区别.这12%将排列的数量减少了1.8e19倍.这是一个很大的数字...这就是factor,它把它减少了.

The Underlying Issue

根本的问题是,有三种类型的密码超过72个字符.这种风格体系对他们的影响将非常不同:

注意:从现在开始,我假设我们正在比较一个胡椒系统,它使用SHA512%的原始输出(而不是祸不单行).

  • 高熵随机密码

    These are your users using password generators which generate what amount to large keys for passwords. They are random (generated, not human chosen), and have high entropy per character. These types are using high-bytes (characters > 127) and some control characters.

    对于这个组,你的散列函数将把它们的可用熵减少到bcrypt.

    让我再说一遍.对于使用高熵、长密码的用户,您的解决方案significantly将其密码强度降低了一个可测量的量.(72个字符的密码会损失62位的熵,更长的密码会损失更多)

  • 中熵随机密码

    该组使用的密码包含常用符号,但不包含高字节或控制字符.这是你的可输入密码.

    对于这个组,您将解锁更多的熵(不是创建它,而是允许更多的熵进入bcrypt密码).当我说轻微,我的意思是轻微.当您将SHA512的512位最大化时,就会出现盈亏平衡.因此,峰值为78个字符.

    让我再说一遍.对于这类密码,在熵耗尽之前,您只能存储额外的6个字符.

  • 低熵非随机密码

    这是一组使用可能不是随机生成的字母数字字符的人.比如圣经的引用之类的.这些短语每个字符大约有2.3位的熵.

    对于这个组,您可以通过散列显著地解锁更多的熵(不是创建它,而是允许更多的熵适合bcrypt密码输入).在熵耗尽之前,盈亏平衡大约是223个字符.

    再说一遍.对于这类密码,预哈希肯定会显著提高安全性.

Back To The Real World

这种熵计算在现实世界中并不重要.重要的是猜测熵.这直接影响了攻击者的行为.这就是你想要最大化的.

虽然几乎没有关于猜测熵的研究,但我想指出一些观点.

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

但我们可能不会在统计上偶然发现它.在短语的情况下,前72个字符相同的几率比随机密码高出很多.但它仍然很低(根据每个字符2.3位计算,你更有可能赢得5次Powerball彩票).

Practically

实际上,这真的无关紧要.有人猜对了前72个字符的几率是如此之低,而后72个字符有很大的不同,这是不值得担心的.为什么?

好吧,假设你在用一个短语.如果一个人能正确地说出前72个字符,那么他们要么是really个幸运(不太可能),要么是一个常见的短语.如果这是一个常见的短语,唯一的变量就是使用多长时间.

让我们举个例子.让我们从圣经中引用一句话(只是因为它是长篇文章的一个常见来源,而不是其他任何原因):

你不可贪图邻居的房子.不可贪恋邻舍的妻子、仆婢、牛驴、和一切属邻舍的.

这是180个字符.第73个字符是第二个neighbor's中的g.如果你猜了那么多,你很可能不会停在nei,而是继续读剩下的诗句(因为密码可能就是这样使用的).因此,你的"散列"没有增加多少.

顺便说一句:我绝对不是在提倡引用圣经的话.事实上,恰恰相反.

Conclusion

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

但归根结底,所有这些都不太重要.我们正在处理的数字太高了WAY.熵的差别不会很大.

你最好还是离开bcrypt吧.与您试图阻止的攻击将要发生相比,您更有可能搞砸散列(从字面上看,您已经这样做了,并且您不是第一个或最后一个犯这种错误的人).

重点抓好网站的睡觉建设.并在注册时将密码熵计添加到密码框以指示密码强度(并指示如果密码过长,则用户可能希望更改它)……

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

As Far As Using A "Secret" Pepper:

实际上,还没有研究将一个散列函数输入bcrypt.因此,目前还不清楚向bcrypt中输入一个"加胡椒"的散列是否会导致未知的漏洞(我们知道,执行hash1(hash2($value))次操作可能会expose 出围绕冲突抵抗和前映像攻击的重大漏洞).

考虑到您已经在考虑存储一个密钥("胡椒"),为什么不以一种经过充分研究和理解的方式使用它呢?为什么不在存储哈希之前对其进行加密?

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

现在,SQL注入攻击不会泄漏任何有用的信息,因为它们没有密码密钥.如果密钥被泄露,攻击者的境况也不会比使用普通散列(这是可以证明的,带有胡椒"pre-hash"的东西无法提供)好多少.

注意:如果您 Select 这样做,请使用库.对于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);
}

这是有益的,因为你使用的所有算法都是经过充分理解和研究的(至少相对而言).记得:

任何人,从最无知的业余爱好者到最优秀的密码学家,都能创造出他自己无法破解的算法.

Php相关问答推荐

为什么PHP输出\n而不是实际创建一个新元素?

无法使用PHP DomDocument找到IMG元素

添加自定义Metabox到WooCommerce管理订单,启用HPOS

make dd()如何正确工作?¨

FatFreeFramework上的不同路由

Mysqli_stmt::Execute():结果字段元数据中存在过早的EOF

如何使用这种格式构建XML?

使用PHP curl的Amazon SP API批处理调用

在PHP中读取JSON

按类别层次 struct 过滤 Laravel 集合

在php中计算两个没有假期的日期之间的小时差

Laravel 10 中的自定义类未找到

使用 foreach php 将记录正确分配给正确的父级

symfony/notifier + twig/inky-extra 在 dunglas/symfony-docker 中缺少 ext-xsl.

WooCommerce - 调用功能ID不正确.不应直接访问订单属性

PHP:如何将多列sql查询结果转换成数组

如何通过额外的属性和本地化来使用 laravel 枚举

如何使用相同的变量使函数参数对于数组和字符串都是可选的

htaccess php 文件,然后是文件夹

在帖子内容中使用短代码触发 Woocommerce 挂钩