为什么我们推荐的总是`HASH(salt + password)`?

信息安全 密码 验证 密码学 哈希
2021-09-03 08:57:31

浏览这个网站、许多论坛、在线文章,我们总是建议使用一种特定的方式来存储密码哈希:

function (salt, pass) {
   return ( StrongHash(salt + pass) );
}

但为什么要采用这种确切的方式?为什么我们不建议这样做?

function (salt, pass) {
   return (StrongHash(( StrongHash(salt) + StrongHash(pass) ));
}

或者甚至是这样的?

function (salt, pass) {
   var data = salt + pass;
   for (i=0;i < 1000; i++) {
       data += StrongHash(salt + data)
   };
   return (data);
}

还是其他一些疯狂的组合?为什么我们特别说哈希原始盐和原始密码的连接?两者都被散列的散列似乎是一个相当高熵的替代方案,就像我的第三个示例一样散列 1000 次。为了熵的缘故,我们为什么不将第一个哈希值再哈希几次?

第一种方式有什么了不起的?


根据要求,这方面的例子:

  1. http://www.codinghorror.com/blog/2007/09/rainbow-hash-cracking.html
  2. 密码哈希:加盐+胡椒还是盐就够了?
  3. 拉伸哈希,多次迭代与更长的输入字符串
  4. https://www.aspheute.com/english/20040105.asp
  5. https://phpsec.org/articles/2005/password-hashing.html
  6. https://msdn.microsoft.com/en-us/library/aa545602.aspx#Y93
  7. https://www.developerfusion.com/article/4679/you-want-salt-with-that/3/
  8. https://ca3.php.net/manual/en/function.hash.php#101987
  9. https://ca3.php.net/manual/en/function.hash.php#89568
2个回答

实际上,“我们”并不推荐您展示的任何内容。通常的建议是PBKDF2bcrypt目前在 Linux 中使用的基于 SHA-2 的Unix crypt

如果您使用的哈希函数是一个完美的随机预言机, 那么您输入盐和密码的方式并不重要;只关心处理盐和密码所需的时间,我们希望这个时间长,以阻止字典搜索;因此使用多次迭代。然而,作为一个完美的随机预言机是散列函数的一个困难属性;安全散列函数必须提供的通常安全属性并不暗示它(抗冲突和原像),并且众所周知,一些广泛使用的散列函数不是随机预言;例如,SHA-2 函数遭受所谓的“长度扩展攻击”,这并没有降低它们的安全性,但在时髦的密码散列方案中使用该函数时需要小心。出于这个原因,PBKDF2 经常与HMAC一起使用。

强烈建议您不要对密码散列方案或一般密码学感到有创意。安全性依赖于微妙的细节,您无法自行测试(在测试期间,不安全的功能与安全的功能一样有效)。

关于建议的两个 -

function (salt, pass) {    return
    (StrongHash( StrongHash(salt) + StrongHash(pass) ) 
}

这里没有奖金。哈希将信息呈现为随机的东西。所以在第一遍,你把两条数据变成了两个随机字符串,然后组合了两个随机字符串。为什么?通过默默无闻的安全性没有任何好处。普遍提出的解决方案的关键要素是:

  • 结合盐和密码,以便盐可以为创建密码的哈希提供额外的混乱因素
  • 一种方法是对集群进行哈希处理以生成看似随机的字符串。

你不能比随机更随机——并且在安全的情况下,最好避免没有目的的工作。

function (salt, pass) {
   var data = salt + pass;
   for (i=0;i < 1000; i++ {
       data += StrongHash(salt + data)
   }
   return (data)
}

这里发生的第一件事是数据被赋予了盐和密码。所以你的输出看起来像:

<Salt><Password><randomString0><randomString1>....<randomString999>

<Salt> = the salt in cleartext
<Password> = the password in cleartext
<randomString> = a series of different random strings.

所写的代码只是暴露了密码,所以所有的随机字符串都没有用。

解决此问题的方法是:

function (salt, pass) {
   var data = salt + pass;
   for (i=0;i < 1000; i++ {
       data = StrongHash(salt + data)
   }
   return (data)
}

注意删除 += 并更改为简单的分配。这是一个很小的变化,但这意味着现在您的最终数据是您的哈希算法输出长度的单个随机字符串。

从安全的角度来看,这可能很好,但是与简单的原始版本相比,再一次没有真正的改进。许多递归散列的重复不会增加安全性 - 使用散列算法的第一遍应该产生随机结果。在最好的情况下一遍又一遍地散列相同的东西没有任何作用,最坏的情况可能最终会降低散列算法的价值。

第一种方式提供了一个非常显着的好处——KISS 主体。把事情简单化。当重复该函数没有任何好处时,没有理由让你的逻辑更复杂、处理时间更长、更容易出错和更难调试。

此外 - 使用密码学,所有算法都应该附带一个用户手册,可以填满你的平均隔间。对弱键、重复问题、暴露问题和其他数学输入/输出辩论的讨论将让数学家在余下的时间里掌握论文主题。除非有特定的算法在起作用,否则很难讨论真正的影响,但将重复和排列留给算法设计而不是试图提供帮助通常是一个很好的策略。许多哈希算法已经有重复和递归的循环,这意味着用户没有理由做更多的事情。