截断加密哈希是否使其无法破解?

信息安全 密码学 密码 哈希
2021-09-03 15:22:39

例如,我以完整值存储密码哈希,$h = sha256('foo')输出 64 个字符:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae

我将它直接存储在数据库中(连同盐、迭代等)。

我的问题是,如果我将散列截断 32 个字符(或任何不太短的长度)然后存储它,是否就不可能“破解”或“反转”(以防黑客获得对散列数据库的访问权限) ? 如果是这样,那么我想这是强烈推荐的还是有问题?

4个回答

绝对不是,如果这种做法破坏了散列函数,则在穷举搜索期间更容易找到冲突。在密码的意义上,任何产生相同哈希值的纯文本都是有效的密码

话虽如此,ChromeOS 在他们的 s2k 函数中使用你的谷歌密码的截断散列用于谷歌磁盘加密方案我怀疑这是为了更难破解你的谷歌密码,以便更容易找到解密你的磁盘的密码......这有点令人不安。

什么是“开裂”?它正在寻找您的系统将接受的密码值,即与您的系统在其数据库中存储为哈希值的任何内容相匹配的密码值。如果攻击者找到另一个密码,与“真实”密码不同,但与哈希匹配,那么攻击者仍然会获胜。

对于给定的散列值,已经有许多匹配的密码(因为“只有” 2 256个可能的散列值,如果您接受“长”密码,比如 50 个字符,还有更多可能的密码)。通过截断散列值,您只会增加密码的数量,这些密码在经过散列和截断后将与您存储的内容相匹配;即你只会让攻击者的事情变得更容易

现在事情并不一定那么可怕。SHA-256 提供 256 位的输出,因为它试图提供对至少2 128的碰撞的抵抗力,并且您需要 256 位的输出才能获得这么多的抵抗力。但是,冲突不是散列密码存储的问题。需要的是对原像的抵抗力对于2 n电阻,这个只需要n位输出。换句话说,如果您只保留 128 位(即 32 个十六进制字符),那么您仍然“很好”,或者至少不会比使用完整 64 个字符时的情况差很多。

注意:简单的哈希是不好的尽管您不会通过截断它来进一步削弱它(至少不是以实际有意义的方式),但您已经遇到了两个问题:

  • 你没有盐,它允许攻击者应用时间或空间并行。即,同时攻击多个密码,和/或使用预先计算的表(彩虹表)。
  • 单个哈希太快了,攻击者很容易每秒尝试数百万甚至数十亿的潜在密码。

所以你需要一个更好的密码散列过程,一个可以配置慢并且使用盐(每个密码的新随机盐)的过程。通常的推荐是bcrypt然后,并且仅在那时,您可能会设想截断(但至少保留 128 位,并且一般不建议对密码算法进行自制更改)。


编辑:澄清一下:具有无限计算能力的攻击者可能会尝试枚举所有可能的密码并保留与存储的哈希值匹配的密码——这些是“真实”密码的候选者。通过截断哈希,您增加了候选者的数量,因此,从某种角度来看,攻击者离猜测“真实密码”更远了。但是,攻击者不是在“真实密码”之后,而是在授予访问权限的密码之后。任何与存储值匹配的密码都将授予访问权限,因为服务器基于“输入的密码与存储的哈希值匹配”授予访问权限。所以任何候选人对攻击者来说都足够好。

通过截断哈希,您只能使其更容易破解。假设哈希是 2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae 并且您存储 2c26b46b68ffc68ff99b453c1d304134。攻击者现在有了更简单的工作:他可以找到哈希以 2c26b46b68ffc68ff99b453c1d304134 开头的任何密码。如果他正在使用查找表,他可能需要重新排列它以应对您的非标准格式,但这是与表大小大致成比例的工作量。

我认为“哈希函数”是指PBKDF2bcrypt之类的东西,因为您提到了“盐、迭代等”。诸如 SHA 系列之类的函数不是存储哈希的合适方式(请参阅为什么使用盐更安全?)。反转正确散列函数的唯一方法是直接蛮力:猜测密码,计算猜测的散列,与参考散列进行比较。如果参考散列是真实散列的截断,则攻击者在进行比较时获得的时间可以忽略不计。对于大规模的蛮力攻击,或者如果您将哈希截断太多,攻击者可能会通过在截断的哈希上找到一个不会在原始哈希上出现的冲突来简化其工作。但是,这在实践中并不是一个主要问题,因为弱点无论如何都会是密码。

使用良好的散列函数,每个位都尽可能独立于其他位,并且不会透露有关散列的任何信息。因此,截断的散列函数仍然是散列函数,但强度降低了。如果您的散列函数是 SHA 函数之一(或暗示从 SHA 派生的迭代散列),则NIST 使用散列的建议(NIST SP-800-107,§5.1)讨论了您可以从 N-位截断。

我认为这个问题有很好的答案,所以我只想尝试用不同的方法思考:

鉴于散列、盐等都以最知名的方式完成,您将获得 64 个字符(32 个字节)作为输出。如果你扔掉 30 个字节而只保留 2 个字节,是否更容易找到会生成 2 个字节输出的密码?可能是的,会有很多密码会生成这 2 个字节。

如果你扔掉 28 个字节,保留 4 个字节?可能会使用更少的密码,但仍然比原来的 32 字节散列多得多。

等等...