盐、密码和安全性

12

我已经阅读了SO上许多关于此问题的问题,但是许多答案互相矛盾或者我不能理解。

您应该始终将密码存储为哈希值,而不是明文。但是,您是否应该在数据库中将盐(每个用户唯一的)存储在哈希密码+盐旁边?对我来说,这似乎不太聪明,因为某人可以获取对数据库的访问权,查找所谓的管理员账户等,然后从中推断出密码。


https://dev59.com/_HVC5IYBdhLWcg3wsTRi#215165 - tanascius
6个回答

26
很多人说要“防止彩虹表”,但没有解释彩虹表是什么,以及为什么这可以防止它们。
彩虹表是一种巧妙的方法,可以预先计算大量哈希值并将它们存储在比朴素方法所需的内存更少的空间中,并且您可以使用它们非常快速地反向计算哈希值。例如Bare functions,如 hash = md5(password)hash = sha1(password) 的表格很常见。
然而,它们可以为任何可以描述为 output = f(input) 形式的哈希函数生成。例如,如果您对所有用户密码使用一个网站范围的salt,例如 hash = md5(salt+password),则可以构造一个函数 ff(password) = md5(salt+password)。因此,您可以为此函数生成彩虹表,这需要很长时间,但然后会让您非常快速地破解数据库中的每个密码。
如果每个密码的salt不同,则无法生成可破解数据库中所有密码的彩虹表。您可以为每个用户生成新的salt,但这样做是没有意义的 - 朴素暴力方法的速度不会变慢。因此,为每个用户设置单独的salt可以防止彩虹表攻击。
有几种方法可以实现这一点。流行的方法包括:
  • 为每个用户设置单独的salt,存储在数据库中与其它详细信息一起: hash = hashfunction(salt + password)
  • 全局salt和某些唯一值(例如user_id): hash = hashfunction(salt + password + user_id)
  • 全局salt和每个用户的salt: hash = hashfunction(global_salt + user_salt + password)
使用全局salt可能会增加一点额外的复杂性,因为它可以存储在数据库之外(例如,在代码中),攻击者在数据库遭到入侵时可能无法获得它。从密码学的角度来看,我认为这并没有增加多少保护,但在实践中,它可能会使攻击者速度变慢。
最后,回答您的实际问题:

将盐值与用户数据一起存储并不会削弱哈希的安全性。哈希函数是单向的:即使对于一个未加盐的密码,给定其哈希值,也很难找到该密码。加盐的动机不是为了使单个哈希更安全,而是为了使多个哈希的集合更安全。针对未加盐哈希值集合的攻击有以下几种途径:

  • 彩虹表攻击
  • 对常用密码(如123passwordgod)进行哈希,并查看数据库中是否存在这些密码,然后破解这些账户
  • 在数据库中查找相同的哈希值,这意味着相同的密码(很可能是)

你说的“全局哈希和每个用户哈希”是指“全局盐和每个用户盐”,对吗?其他句子也类似。 - David Balažic
在“salt + password”中的“+”是否表示字面上的加法? - HopefullyHelpful
“+”符号用于表示组合,通常在实践中是指连接。然而,我不想说“这是正确的做法”,因为老实说,我不知道在这种构造中是否有任何微妙的错误需要避免。正确的答案是,你应该使用一个现代密码哈希库来处理这个问题 - 这些细节不应该由你控制。 - ZoFreX

6
盐(Salt)用于增加攻击者破解数据库中密码哈希值所需的时间。通常使用查找表,例如彩虹表(见http://en.wikipedia.org/wiki/Rainbow_table),以实现此目的。
耗时的不是查找本身,而是计算彩虹表所需的时间。添加盐会强制攻击者重新计算新的彩虹表,即使攻击者已知该盐值并试图破解数据库。

8
即使两个用户使用相同的密码,盐也会使存储的哈希值唯一。 - Anders Abel
@Anders Abel - 仅从统计学角度来看。当然,这取决于盐的大小和用户基数,但是对于旧的UNIX基于DES的密码哈希,只有12位盐,因此如果我们有4097个使用相同密码的用户,则至少会有两个具有相同的密码哈希。 - Vatine
计算彩虹表对于拥有10万用户的应用程序来说是绝对值得的。关键在于,如果每个用户的盐值不同,你就无法这样做——计算10万个彩虹表将毫无意义。 - ZoFreX

3
盐值的作用是干扰现有的彩虹表。知道盐值不会对安全造成威胁。即使你知道盐值,仍需要知道密码才能访问。

但是如果盐 + 密码 = 哈希值,那么哈希值 - 盐 = 密码,这应该是正确的吧? - Jonathan.
1
请注意,哈希是一种单向算法。[ do_hash(salt + password)= hashed_password。] 如果已知盐值,则攻击者必须对每个密码尝试执行do_hash(salt + password_guess),以构建彩虹表或暴力破解/字典攻击每个密码。为每个用户更改盐值,这将留下非常大的搜索空间。 - Cheekysoft
但是攻击者为什么要获取所有用户的密码,他肯定只会寻找管理员等特权账户。 - Jonathan.
@Jonathan,泄露的不仅是管理员的密码。许多人在不同的网站上使用相同的密码。这样,您可以更安全地保护他们的密码,以便攻击者无法从您的数据库中获取它们。 - Ikke

2
首先,盐的主要目的是防止密码被暴力破解,例如它可以阻止使用彩虹表来快速破解密码。
即使攻击者获得了数据库访问权限,也很难从盐中推断出真实的密码(特别是如果您没有代码并且不知道密码如何散列)。然而,为了增加安全性,我喜欢使用静态哈希值对密码进行哈希。这样,仅仅获取到数据库是不够的。以下是一个 PHP 示例:
$hashed_password = sha1($user_password . $user_salt . $static_salt);

$user_salt 是数据库中每个用户唯一的盐值,$static_salt 是在代码设置中指定的盐值。


我并不认为这比提供虚假的安全感更有意义。 - Tom Hawtin - tackline
1
正如Grimmy所说,如果有人入侵数据库并获取用户的哈希密码和盐值,他们可以使用这些信息生成新的彩虹表(假设他们知道使用的哈希方法,在开源软件的情况下,您可以轻松地找出)。如果您还在哈希密码中包含未存储在数据库中的盐值,则攻击者无法仅通过入侵数据库来生成新的彩虹表。这不仅是虚假的安全感。 - reko_t
如果每个用户的盐值不同,就无法生成彩虹表。我看到reko_t建议的用法很多,但我也不确定它是否真的增加了安全性。我有一个想法,在某些情况下确实能起到作用:如果你有一个非常大的数据库,并且每个密码哈希都像他所说的那样,但没有静态盐:举个例子,你可以为每个用户运行一些查询,将"1234" + user_salt哈希值存储为匹配项。你将保证破坏了一些账户。如果存在未知的站点级哈希,则此向量将不可能存在,找到哈希将非常困难。 - ZoFreX

2
嗯嗯嗯...讨论如此之多...信息如此之多。有这么多问题...我看到在回答所有问题后,这是总结。

  1. 为什么使用盐?
  2. 为什么将盐与(密码+盐)的哈希值一起存储?

这是我的理解。

  1. 黑客拥有所有字典单词的彩虹表,他们需要花费大量精力制作。每次制作彩虹表都会使黑客疲惫不堪。简单来说,这非常困难。
  2. 根据第1点,如果我们让黑客为每个用户计算彩虹表,那么他知道密码时已经老了。如果用户经常更改密码,则即使黑客的孙子也会在知道密码之前变老。
  3. 好的。因此,为每个用户使用不同的盐。这使得黑客需要使用单独的攻击来了解每个单个用户的密码。
  4. 我注意到各种读者指出,与其对每个单个用户进行攻击,一个伟大的黑客将把所有努力集中于管理员帐户,然后就完成了: )。所以,在这种情况下,我们将向前迈进一步,然后使用某种不同的方法来计算哈希值。假设盐不是存储的确切内容。它可能是实际哈希的一半。另一半以某种其他方式(以某种其他方式计算)存储在其他地方。这只是一项额外的安全措施。我们知道,在当前时代,计算机的计算能力正在增加。因此,道德人士知道世界上最快的超级计算机需要1年才能破解管理员密码(仅为示例)。因此,他们强制执行一个策略,该策略规定每3个月更改管理员密码。黑客知道旧密码的同时,新密码已经生效。或者说数据库被攻击了,那么好人在黑客破解密码之前就会知道这件事。而好人则会及时更改事情(请记住算法的升级...sha1 sha2和现在的sha3).....

好吧...虽然不完全完整,但我的观点是坏人需要很长时间....但是好人总是比坏人领先一步,以确保他们知道坏人将用于破解的技术。所以只需随着时间的推移设计更好的技术。

保重....


1
如果您不存储盐值,那么如何检查提供的密码是否正确?

1
我的意思是,你应该将它存储在主数据库中,而不是其他地方。 - Jonathan.
1
@Jonathan - 将其存储在与登录ID和密码列相同的数据库表中。如果盐被泄露也没有关系。 - Sripathi Krishnan

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