盐值生成和开源软件

78

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

我正在开发一个计划作为开源发布的项目,但问题是随着源代码的公开,会泄漏生成盐的秘密公式,从而可能使我们的网站受到彩虹表攻击。

我想很多人在我之前已经思考过这个问题,想知道最佳实践是什么。对我来说,如果代码是开源的,就没有必要使用盐,因为盐可以很容易地被逆向工程破解。

你有什么想法吗?

6个回答

246

由于关于哈希加盐的问题经常出现,并且似乎有相当多的混淆,因此我扩展了这个答案。


什么是盐?

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


为什么给哈希加盐(或种子)很有用?

在哈希中添加随机的盐可以确保相同的密码产生许多不同的哈希值。盐通常与哈希函数的结果一起存储在数据库中。 对哈希进行加盐有许多好处:

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


盐值无需保密。

盐值不是一个秘密密钥,而是通过使哈希函数特定于每个实例来“工作”。使用盐值哈希,不是一个哈希函数,而是每个可能的盐值对应一个哈希函数。这可以防止攻击者以少于攻击一个密码的N倍的代价攻击N个哈希密码。这就是盐的作用。
“秘密盐”不是盐,它被称为“密钥”,这意味着你不再计算哈希,而是计算消息认证码(MAC)。计算MAC是非常棘手的(比简单地将密钥和值组合成哈希函数要棘手得多),它是一个完全不同的主题。

必须对每个实例都是随机的。这确保攻击者必须分别攻击每个盐值哈希。
如果你依赖于你的盐值(或盐算法)是秘密的,那么你就进入了安全通过混淆的领域(不起作用)。最有可能的是,你并没有从盐值保密中获得额外的安全性;你只是从中获得了一种安全感。所以,与其使你的系统更加安全,它只会分散你对现实的注意力。


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

从技术上讲,盐应该是独特的。盐的作用是为每个哈希密码生成不同的标识,这是全球范围内的。由于没有中央组织可以按需分配唯一的盐,因此我们必须依赖于下一个最好的选择,即使用不可预测的随机生成器进行随机选择,最好在足够大的盐空间内进行,以使碰撞不太可能发生(两个实例使用相同的盐值)。

有时候人们会尝试从“可能是独特的”某些数据中派生出盐,例如用户ID,但此类方案通常失败,原因如下:

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

  2. 用户名也是如此:root只有一个,但世界上有很多根用户。针对“root”的彩虹表将是值得一试的,因为它可以应用于数百万个系统。更糟糕的是,世界上也有许多“bob”,而且许多人没有系统管理员培训:他们的密码可能非常脆弱。

  3. 独特性还与时间有关。有时候,用户会更改密码。对于每个新密码,都必须选择一个新盐。否则,攻击者将获得旧密码的哈希值和新密码的哈希值,并尝试同时攻击两者。

使用从密码学安全、不可预测的PRNG获得的随机盐可能有点过度,但至少可以证明它能保护您免受所有这些危险。这不是防止攻击者知道单个盐是什么,而是不让他们得到将用于大量潜在目标的大而肥的目标。随机选择使目标尽可能细小。


总之:

使用随机、均匀分布、高熵盐。每次创建新密码或更改密码时使用新的盐。将盐与哈希密码一起存储。偏爱大盐(至少10字节,最好16字节或更多)。

盐不能将弱密码变成强密码。它只能确保攻击者为破解每个弱密码而付出词典攻击的代价。


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

免责声明:
我不是一名安全专家。(尽管该回答已由Thomas Pornin审核)
如果有任何安全专业人士发现错误,请在评论中指出或编辑此维基回答。


4
系统在所有机器上使用时,每一行都需要不同,盐值也需要是不可预测的。这可以通过创建随机盐值来实现。请参见:https://dev59.com/3XRB5IYBdhLWcg3wuZU1#536756 - Jacco
1
@Jacco,这太棒了。我也同意你对于随机性的论点。这是防止攻击者在所有系统中“猜测”盐最有效的方法。(虽然密码学随机数生成器绝对是过度杀伤力的)。 - Jeremy Powell
3
如果你知道盐值,可以对这个特定的哈希进行暴力破解攻击。如果你想破解单个哈希,创建彩虹表没有任何优势。此外,为了一个好的哈希方案创建一百万个哈希将需要比你估计的时间更长。 - Jacco
1
感谢您的快速回复。您认为弱密码不常见,还是自适应成本哈希算法不是一个好主意?我很欣赏不偏离这个问题的重点所带来的好处,但我看到了太多关于密码哈希的错误实践和建议,我认为至少链接到另一个有关算法的好答案将非常有帮助。您知道stackoverflow上有没有相关的内容吗?在新的IT安全stackexchange上,http://security.stackexchange.com/questions/211/password-hashing非常好,因此从这里链接到它可能是帮助大家的正确方式。 - nealmcb
2
这里还有一个更相关的观点。你说“从技术上讲,盐应该是唯一的”,但这并不够强。正如你在其他地方所说,它确实需要大量的熵。如果仅仅是唯一的,由某个“中央组织”分配,他们可能只会分配顺序盐,并且了解到目前为止分配了多少盐,或者哪些盐已经分配给了你的目标,将减少预先计算攻击的攻击空间。 - nealmcb
显示剩余16条评论

22

实际上,相对于每个条目而言,盐只需要是唯一的即可。 即使攻击者能够计算出盐是什么,它也使得彩虹表变得极其难以创建。这是因为盐在密码散列之前添加到密码中,因此实际上增加了彩虹表必须包含的条目总数,以获得密码字段的所有可能值列表。


8
自Unix变得流行以来,正确存储密码的方法是附加一个随机值(盐)并进行哈希。将盐保存在可以稍后获取但希望坏人无法获取的位置。
这有一些好处。首先,坏人不能只是制作一个预期密码列表,如“Password1”,将其哈希成彩虹表,并浏览您的密码文件查找匹配项。如果您有一个很好的两字节盐,他们必须为每个预期密码生成65,536个值,这使得彩虹表不太实用。其次,如果您可以将盐保护在查看密码文件的坏人之外,那么您已经大大增加了计算可能值的难度。第三,您已经使坏人无法确定给定的人是否在不同的站点上使用相同的密码。
为了做到这一点,您需要生成一个随机盐。这应该以均匀概率生成所需范围内的每个数字。这不难;一个简单的线性同余随机数生成器就可以胜任。
如果你需要复杂的计算来制作盐,那么你做错了。如果你根据密码计算它,那么你就完全错了。在这种情况下,你所做的只是复杂化哈希,而没有实际添加任何盐。
没有安全方面的专家会依赖于隐藏算法。现代密码学基于经过广泛测试的算法,为了进行广泛测试,它们必须是众所周知的。通常,使用标准算法而不是自己编写并希望好的方法更安全。代码是否开源并不重要,坏人仍然经常有可能分析程序的功能。

1
“无法确定某个人是否在不同的网站上使用相同的密码。” - Jacco

1

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

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


那么你需要将盐值与哈希密码一起存储在数据库中,是吗? - user199085
2
如果您将盐存储在单独的数据库中,您将获得额外的保护。但是,即使它们与哈希密码一起存储,仅使用盐方法也会显着增加成功攻击的复杂性。关键在于通过在散列生成过程中包括一个随机元素,使破解所有密码变得更加困难。有关更多信息,请参见此维基百科条目:http://en.wikipedia.org/wiki/Salt_%28cryptography%29 - Dave R.

0
使用随机函数生成器生成盐,并将其存储在数据库中,每一行一个盐,存储在数据库中。
我喜欢Django注册中如何生成盐。参考: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和用户特定组件,您将获得不可破解的安全性!

7
不可破解的安全保障?我认为那有点过于乐观了。 - Jacco
6
SHA-1已经被攻破了:http://www.schneier.com/blog/archives/2005/02/sha1_broken.html,因此请使用SHA-256。 - Jacco
2
SHA-256现在也已经过时了。目前建议使用的方法是BCrypt和PBKDF2。 - Gnanz

0
在加密数据并将其发送到远程服务器的桌面应用程序中,如何考虑每次使用不同的盐?
使用用户密码的PKCS#5需要一个盐来生成加密密钥以加密数据。我知道将盐硬编码(混淆)在桌面应用程序中不是一个好主意。
如果远程服务器绝不能知道用户的密码,是否可能每次使用不同的盐?如果用户在另一台计算机上使用桌面应用程序,如果没有密钥(它未硬编码在软件中),他将如何能够解密远程服务器上的数据?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接