先放盐会让攻击者更容易暴力破解哈希吗?

信息安全 密码 哈希 密码破解
2021-08-30 05:00:49

许多存储密码的建议是推荐hash(salt + password)而不是hash(password + salt).

不把盐放在首位会让攻击者更快地破解密码,因为他们可以用盐的字节预先计算散列函数的状态,然后每次他们的数十亿和万亿次尝试他们只需要使用密码的字节完成计算哈希。

换句话说,每次蛮力迭代只需要计算密码的哈希值,intermediateHashState(password) 而不是整个hash(salt + password).

如果盐放在密码之后,攻击者就没有这个快捷方式。

这种优势是否存在,是否显着?

4个回答

实际上,您正在以相反的方式处理它。

确实,这样做hash(salt + password)可以让您预先计算盐(但请参阅下面的注释),并且只对所有这些试验的每个密码候选者进行哈希处理。如果您根本不使用盐,您将承担相同的成本。

但是,salt 的目标不是让破解单个哈希变得更加困难,而是确保不同用户的哈希即使选择相同的密码也是不同的,因此破解工作不能应用于多个用户。

假设您在他们使用 MD5 哈希的地方转储了一个 PayPal 数据库哈希。您要检查密码“paypal”、“PayPal”、“PayPal123”...

  1. 如果他们使用 MD5(密码),您可以简单地对其中任何一个进行哈希处理,并找出是否有人在使用这种弱密码。

  2. 如果他们使用 MD5(盐 + 密码),您可以为每个人预先计算部分 MD5(盐),但仍然需要为每个用户散列每个候选密码。

  3. 如果他们使用 MD5(密码 + salt),您可以为每个候选密码预先计算部分 MD5(密码),然后为每个用户应用他们的 salt。

#1显然是这里最糟糕的。您可以根据密码和盐的不同长度以及用户数量在#2和#3之间争论,但我认为#2更可取。仅基于长度,密码的强制最小长度可能高于盐大小。但我怀疑#3 构造也可能存在其他弱点。

这是一个显着的优势吗?

并不真地。

首先,许多散列函数在块中工作,小于块大小的值的预计算只是存储“预计算字节”的副本。在 99% 的情况下,盐和密码的长度都会比块大小短,因此实际上不会执行真正的预计算。您需要在那里使用非常长的字符串才能使用。

此外,任何现代密码哈希函数都将至少使用多次迭代,如果不使用更高级的方法来使暴力破解变得昂贵,并且您的优化仅适用于初始迭代。

在任何情况下,不是简单地将盐和密码连接起来,最安全的组合它们的方法是对它们进行HMAC,从而以更好的方式混合它们。

许多存储密码的建议是推荐hash(salt + password)而不是hash(password + salt).

这些建议显然很糟糕,因为他们应该告诉您的是使用专门为此目的设计的密码散列函数,例如(按照粗略的顺序,从新的和更好的到旧的和更差的):

  • 氩气2(最佳)
  • 加密
  • bcrypt(还不错,但开始看起来过时了)
  • PBKDF2(远非理想,但比自制密码散列好得多)

这些功能要么:

  1. 将密码和盐作为单独的参数,从而在内部处理您的问题;
  2. 为您提供更高级别的 API,在内部负责盐生成和管理:
    • 一个“注册”函数,它接受一个密码,生成一个 salt,并输出一个封装了 salt 和 hash 的验证字符串(例如,password_hash()在 PHP 中);
    • 一个“验证”函数,它接受密码和验证字符串,并验证密码是否与后者匹配(例如,password_verify()在 PHP 中)。

如果您手动连接密码和盐,那么您做错了


也就是说,密码散列函数通常最好先吸收盐,然后再吸收密码。为什么?因为该顺序通常更擅长抵抗预计算攻击。在密码优先顺序中,攻击者可以预先计算对应于常见密码的中间状态,也许有一些聪明的方法可以构建某种大表,利用它来比其他方法更快地计算它们的加盐哈希。而盐优先顺序使这不可能,如果盐是随机的,则更是如此。

不把盐放在首位会让攻击者更快地破解密码,因为他们可以用盐的字节预先计算散列函数的状态,然后每次他们的数十亿和万亿次尝试他们只需要使用密码的字节完成计算哈希。

不,因为关键是攻击者在窃取密码数据库之前不应该学习盐。相对于精心设计的密码散列函数的成本,您提到的那一点预计算仅提供了微小的加速,并且预计算状态仅适用于攻击一个单独的密码条目(假设没有重复的盐,无论如何这是一个要求)。

相反,使用密码优先顺序,他们可以:

  • 在窃取密码数据库之前,预先计算和存储大量常用密码的哈希函数状态;
  • 跨使用不同盐散列的密码条目重用预先计算的表,甚至跨多个密码数据库。

推荐用于密码的散列函数没有这个优势 - 事实上,我不确定任何非平凡的散列函数都有,但我不想在那里做一个笼统的陈述。

相反,散列函数在每个阶段混合来自整个输入的部分。例如,给定输入ABCDEFGHIJKLMNOPQRSTUVWXYZ,第一步可能类似于将第一个字符与最后一个字符配对,然后迭代直到没有输入。这会给AZBYCXDWEVFUGTHSIRJQKPLOMN. 假设“salt”是ABCDEF,而密码是其余的,然后将密码更改为PASSWORD- 此示例第一步的输出将是ADBRCODWESFSPA,这对您的原始哈希毫无帮助。

这只是一个示例 - 真正的哈希函数适用于二进制值,并执行更复杂的混合,但存在一些差异 - 但您可以看到,盐在哪里并不重要,因为输出在很早的时候就发生了变化.

甚至有一个原则(雪崩效应)表明,在散列函数的输入中更改的单个位应该会更改大约 50% 的输出位,以及大多数散列函数,甚至是现在被认为不安全的散列函数,例如 MD5 ,遵循这个(而我上面的例子没有!)。

本质上,您不能按照您建议的方式预先计算部分哈希,除非系统中存在其他缺陷。如果您出于某种原因对盐进行哈希处理,获取输出的前半部分,并将其固定到密码哈希的后半部分以进行存储,这将引入理论上的弱点,因此您只需要计算密码的哈希值。

虽然上述答案确实提出了一些有效的观点,但它们似乎并不完全正确,应该注意的是,某些散列函数确实存在弱点,这些弱点对 "$pass.$salt" 的破解速度产生了显着影响或“$salt.$pass”。

参见例如 md5。根据 hashcat 密码破解软件的创建者 atom ( https://hashcat.net/forum/thread-8365.html ):

hashcat 如何利用 MD5 中的一些弱点来获得额外的加速,需要它只更改输入数据的前 4 个字节。由于这部分是固定的(因为盐)它不能使用加速度。

这确实显着反映了开裂速度。
在 Titan RTX 上,md5($pass.$salt) 的速度为 63819.9 MH/s,几乎是 md5($salt.$pass) 速度的两倍,“仅”为 34696.2 MH/s。

其他哈希算法也存在类似的差异。密码破解软件的开发人员和社区花费大量时间来寻找弱点和捷径以提高速度。因此,从破解的角度来看,查看当前的 hashcat 基准测试(如 https://gist.github.com/Chick3nman/5d261c5798cf4f3867fe7035ef6dd49f并比较不同 Salt-Password-Variants 的速度)可能是一个好主意。
如果该链接丢失,您还可以使用以下命令生成自己的基准:

hashcat -b