77

据我了解,生成盐的最佳做法是使用存储在源代码中的一些神秘公式(甚至是魔法常数)。

我正在做一个我们计划作为开源发布的项目,但问题是源代码带来了生成盐的秘密公式,因此能够在我们的网站上运行彩虹表攻击。

我想很多人在我之前就已经考虑过这个问题,我想知道最佳实践是什么。在我看来,如果代码是开源的,那么加盐毫无意义,因为盐很容易被逆向工程。

想法?

4

6 回答 6

244

由于有关加盐哈希的问题经常出现,并且似乎对该主题有些困惑,因此我扩展了此答案。


什么是盐?

盐是添加到散列算法输入的一组固定长度的随机字节。


为什么加盐(或播种)哈希有用?

向散列添加随机盐可确保相同的密码会产生许多不同的散列。salt 通常与散列函数的结果一起存储在数据库中。加盐哈希是好的,原因有很多:

  1. 加盐大大增加了预计算攻击的难度/成本(包括彩虹表
  2. 加盐确保相同的密码不会产生相同的哈希值。这确保您无法确定两个用户是否具有相同的密码。而且,更重要的是,您无法确定同一个人是否在不同系统中使用相同的密码。
  3. 加盐增加了密码的复杂性,从而大大降低了字典攻击生日攻击的有效性(仅当盐与哈希分开存储时才适用
  4. 适当的加盐大大增加了预计算攻击的存储需求,直到它们不再实用。(8 个字符区分大小写的字母数字密码和 16 位盐,散列到 128 位值,在没有彩虹减少的情况下将占用不到 200 艾字节)。


盐没有必要保密。

盐不是密钥,而是盐通过使散列函数特定于每个实例而“起作用”。使用加盐散列,没有一个散列函数,而是每个可能的盐值都有一个散列函数。这可以防止攻击者以少于攻击一个密码的N倍成本攻击N个散列密码。这是盐的重点。 “秘密盐”不是盐,它被称为“密钥”,这意味着您不再计算哈希,而是计算消息验证码(MAC)。计算 MAC 是一项棘手的工作(比简单地将键和值放入散列函数要复杂得多),而且它完全是一个非常不同的主题。

对于使用它的每个实例,盐必须是随机的。这确保了攻击者必须分别攻击每个加盐哈希。
如果你依赖你的盐(或加盐算法)是秘密的,你就进入了通过默默无闻的安全领域(行不通)。最有可能的是,您不会从盐保密中获得额外的安全性。你只会得到温暖的模糊的安全感。因此,它不仅不会使您的系统更安全,反而会分散您对现实的注意力。


那么,为什么盐必须是随机的?

从技术上讲,盐应该是唯一的。加盐的重点是每个散列密码都是不同的。这意味着全世界。由于没有按需分配唯一盐的中央组织,我们必须依靠下一个最好的东西,即使用不可预测的随机生成器进行随机选择,最好是在一个足够大的盐空间内,以使冲突不可能发生(两个实例使用相同的盐值)。

尝试从一些“可能是唯一的”数据(例如用户 ID)中提取盐是很诱人的,但是由于一些令人讨厌的细节,这种方案通常会失败:

  1. 例如,如果您使用用户 ID ,一些攻击不同系统的坏人可能只是集中他们的资源并为用户 ID 1 到 50 创建预先计算的表。用户 ID 在系统范围内是唯一的,但在全球范围内是唯一的。

  2. 这同样适用于用户名:每个 Unix 系统都有一个“根”,但世界上有很多根。“root”的彩虹表是值得的,因为它可以应用于数百万个系统。更糟糕的是,那里还有很多“鲍勃”,而且许多人没有接受过系统管理员培训:他们的密码可能很弱。

  3. 唯一性也是暂时的。有时,用户会更改密码。对于每个新密码,必须选择一个新的盐。否则,攻击者获得了旧密码的哈希值,而新密码的哈希值可以同时尝试攻击两者。

使用从加密安全、不可预测的 PRNG 中获得的随机盐可能是某种矫枉过正,但至少它可以证明可以保护您免受所有这些危险。这不是要阻止攻击者知道单个盐是什么,而是要不给他们提供将用于大量潜在目标的大而胖的目标。随机选择使目标尽可能薄。


综上所述:

使用随机、均匀分布的高熵盐。每当您创建新密码或更改密码时,请使用新盐。将盐与散列密码一起存储。喜欢大盐(至少 10 个字节,最好是 16 个或更多)。

盐不会将坏密码变成好密码。它只是确保攻击者至少会为他破解的每个错误密码付出字典攻击的代价。


有用的来源:
stackoverflow.com:密码哈希的非随机盐
Bruce Schneier:实用密码学(书)
Matasano 安全性:彩虹表足够了
usenix.org:自 1976 年以来 Unix crypt 使用了盐
owasp.org为什么添加盐
openwall.com :

免责声明:
我不是安全专家。(尽管此答案由Thomas Pornin 审核
如果有任何安全专家发现有问题,请发表评论或编辑此 wiki 答案。

于 2009-10-29T17:05:10.803 回答
22

真正的盐只需要对每个条目都是唯一的。即使攻击者可以计算出盐是什么,它也让彩虹表的创建变得异常困难。这是因为盐是在散列之前添加到密码中的,因此它有效地添加到彩虹表必须包含的条目总数中,以获得密码字段所有可能值的列表。

于 2009-10-29T17:03:55.877 回答
8

自从 Unix 流行以来,存储密码的正确方法是附加一个随机值(盐)并对其进行哈希处理。把盐放在你以后可以拿到的地方,但你希望坏人不会得到它。

这有一些很好的效果。首先,坏人不能只列出“Password1”之类的预期密码,将它们散列到彩虹表中,然后通过您的密码文件寻找匹配项。如果你有一个好的两字节盐,他们必须为每个预期的密码生成 65,536 个值,这使得彩虹表不太实用。其次,如果您可以阻止正在查看您的密码文件的坏人获取盐分,那么计算可能的值就会变得更加困难。第三,您使坏人无法确定某个人在不同站点上是否使用相同的密码。

为此,您生成一个随机盐。这应该以均匀的概率生成所需范围内的每个数字。这并不难;一个简单的线性同余随机数生成器会做得很好。

如果你有复杂的计算来制作盐,那么你做错了。如果您根据密码计算它,那么您做错了。在这种情况下,您所做的只是使哈希复杂化,而不是在功能上添加任何盐。

擅长安全的人不会依赖隐藏算法。现代密码学基于经过广泛测试的算法,并且为了进行广泛测试,它们必须是众所周知的。通常,人们发现使用标准算法比使用自己的算法更安全,并希望它是好的。代码是否开源并不重要,坏人仍然有可能分析程序的作用。

于 2009-10-30T15:34:29.553 回答
1

您可以在运行时为每条记录生成一个随机盐。例如,假设您将散列的用户密码存储在数据库中。您可以在运行时生成一个由大小写字母数字字符组成的 8 个字符的随机字符串,将其添加到密码中,对该字符串进行散列处理,然后将其存储在数据库中。由于有 62 8种可能的盐,生成彩虹表(针对每种可能的盐)将非常昂贵;并且由于您为每个密码记录使用唯一的盐,即使攻击者生成了几个匹配的彩虹表,他仍然无法破解每个密码。

您可以根据安全需要更改盐生成的参数;例如,您可以使用更长的盐,或者您可以生成一个还包含标点符号的随机字符串,以增加可能的盐的数量。

于 2009-10-29T17:10:12.790 回答
0

使用随机函数生成器生成salt,并将其存储在数据库中,每行制作一个salt,并将其存储在数据库中。

我喜欢 django-registration 中盐的生成方式。参考:http ://bitbucket.org/ubernostrum/django-registration/src/tip/registration/models.py#cl-85

salt = sha_constructor(str(random.random())).hexdigest()[:5]
activation_key = sha_constructor(salt+user.username).hexdigest()
return self.create(user=user,
           activation_key=activation_key)

他使用随机数生成的 sha 和用户名的组合来生成哈希。

Sha它本身以坚固和牢不可破而闻名。添加多个维度来生成盐本身,具有随机数、sha和用户特定组件,您拥有牢不可破的安全性!

于 2009-10-29T17:13:36.280 回答
0

对于加密数据并将其发送到远程服务器的桌面应用程序,您如何考虑每次使用不同的盐?

使用带有用户密码的 PKCS#5,它需要一个盐来生成加密密钥,以加密数据。我知道在桌面应用程序中保持盐硬编码(混淆)不是一个好主意。

如果远程服务器必须永远不知道用户的密码,是否可以每次使用不同的盐?如果用户在另一台计算机上使用桌面应用程序,如果他没有密钥(它不是在软件中硬编码),它如何能够解密远程服务器上的数据?

于 2011-05-26T15:48:10.023 回答