为什么使用随机盐?

信息安全 密码
2021-08-14 04:25:30

我阅读了这篇关于如何以安全方式将密码存储在数据库中的博文。埃尔伯特告诉我们这样做:

  1. 生成一个随机盐并在其中包含随机的内容和用户名。哈希这个。
  2. 哈希<hashed-salt><password>很多次。
  3. 存储<hashed-salt><hash>在数据库中。
  4. 每次用户登录时重新生成盐。

现在,检查密码是否正确:

  1. 在数据库中找到散列盐。
  2. 哈希<found-salt><password>很多次。
  3. 检查该值是否与您可以在数据库中找到的哈希值相同。

我明白为什么这有效。我不明白为什么这比散列/加盐密码更好。使用此解决方案,哈希盐在数据库中以明文形式存在,因此当有人可以访问数据库时,他可以访问哈希盐。假设他也可以访问脚本,他可以再次执行蛮力攻击。这种蛮力攻击不会很快,因为我们散列了很多次,但是,这并不能解释随机加盐相对于使用常量词加盐在安全性方面增加了什么。

为什么使用可以在数据库中找到的随机盐比使用可以在脚本中找到的恒定盐增加安全性?

4个回答

“为什么使用可以在数据库中找到的随机盐比使用可以在脚本中找到的恒定盐增加安全性?”

因为“恒定盐”(实际上最好称为“胡椒”)对于所有密码散列都是相同的,因此不能满足盐的主要目的——确保即使两个用户具有相同的密码,他们的哈希值会有所不同。

特别是,假设您有 1000 个用户,并且您的带有密码哈希的用户数据库被攻击者窃取。很可能,攻击者还可以窃取您的脚本,因此也可以窃取其中包含的辣椒。(情况可能并非总是如此——例如,SQL 注入攻击可能会危及数据库,但不一定会危及应用程序代码——但您不应假设它不会发生。)

为了破解密码哈希,攻击者可以通过蛮力猜测随机密码(或者,对于第一遍,遍历一个常见密码列表),用你的固定胡椒对它们中的每一个进行哈希,然后在一个被盗哈希的数据库。值得注意的是,他们只需要为每个猜测的密码执行一次,就好像您根本没有使用过辣椒一样。

但是,如果您改为使用存储在数据库中的唯一盐,用于每个密码散列(有或没有胡椒),然后用其中一种盐对猜测的密码进行散列并将结果与​​被盗散列进行比较就可以看出攻击者是否该特定用户正在使用该特定密码。因此,破解所有密码(或破解任何一个或任何n 个密码)所需的时间增加了 1000 倍(数据库中的用户数量)。

或者,换个角度来看,为 1000 个用户的数据库中的每个密码哈希使用唯一的盐会使攻击者的工作轻松 1000 倍。即使你使用迭代次数为 1000 的 PBKDF2 来减缓暴力攻击,好吧,猜猜看——不使用独特的盐只会吃掉你从使用 PBKDF2 中获得的任何好处。哎呀。


现在对于您问题的另一部分,您链接到的文章提出了一种相当复杂的选择盐的方法:

$salt = hash('sha256', uniqid(mt_rand(), true) . 'something random' .
             strtolower($username));

当盐真正需要满足的唯一属性是唯一性时,为什么这么复杂?

好吧,盐真的不应该只是在您的数据库中是唯一的,而是全球唯一的,因此即使是拥有多个被盗数据库的攻击者也不会发现破解它们变得更容易。某些攻击场景中,还希望盐是不可预测的,这样攻击者就无法在窃取您的数据库之前开始预先计算密码表,希望在您检测到违规行为时给您更多时间做出响应。

实现这两个目标的相对简单的方法是使盐足够长和随机(足够长意味着,例如,至少 100 位熵)。然而,生成真正的随机盐可能有点棘手,尤其是在 PHP 中,不幸的是,它仍然缺乏用于获取加密安全随机数的标准可移植方法。

因此,您链接到的示例代码似乎采用了一种强大的“腰带和吊带”方法:通过连接几条具有不同随机性和唯一性的数据并将它们提供给加密哈希函数,我们可以相当确定输出将与输入的总和一样唯一和随机(无论如何,直到哈希输出长度施加的限制,对于像 SHA-256 这样的 256 位哈希基本上是无限的)。特别是,只要至少有一个输入是唯一的(分别是随机的),我们基本上可以确定输出也是如此。

作为一般原则,这是一种非常好的和值得称道的安全方法。人们可能会对文章中建议的输入的具体选择提出质疑(特别是,甚至没有任何尝试从诸如openssl_random_pseudo_bytes()或Unix 系统之类的源获得真正的加密随机性/dev/urandom),但至少一般设计是声音。

特别要注意,哈希函数的建议输入包括以下内容:

  • 用户名(尽管为什么它是小写的,我不知道),确保系统上的所有用户都有唯一的盐,
  • 一个希望唯一的每个系统标识符(“随机的东西”),它应该确保同一个用户在不同的系统上会有不同的盐,
  • 当前时间(通过uniqid()获得),确保用户每次更改密码时都会获得不同的盐,以及
  • 通过 mt_rand() 和 uniqid() 获得的各种其他(伪)随机性,这可能会引入一些不可预测性(并且,至少在PHP 的某些强化版本中,甚至可能是一些真正的随机性),并且至少不会做任何事情伤害。

你描述的方案没有意义。Matrix 提供的答案也不完全正确。

盐的唯一要求是唯一性。实现这一点的最简单方法是使用 CSPRNG。然而,随机并不是盐的必需属性。

但是,请避免使用用户名或电子邮件作为盐。这是为了防止攻击者生成带有admin或类似盐的彩虹表,以对特定用户进行有针对性的攻击。

因为彩虹表(https://en.wikipedia.org/wiki/Rainbow_table)。为所有用户使用相同的盐,攻击者只需要使用该盐和 X 次哈希迭代创建一个彩虹表。如果每个用户都有一个唯一的盐,那么创建彩虹表不再实用,并且进行蛮力搜索会更快。

我已经阅读了您链接的文章,我不建议您使用其中的代码,因为它存在一些缺陷。

  1. mt_rand() 不是加密安全的随机源。

  2. uniqid() 不是加密安全的随机源。

  3. 将用户名添加到随机盐是没有意义的。

  4. 'something random' 似乎是一个没有被标记的辣椒。把胡椒想象成一种全场范围的盐。它只提供边际安全优势。

  5. 制作自己的密码散列解决方案不是一个好主意。最好使用http://www.php.net/manual/en/function.hash-pbkdf2.phphttp://www.php.net/manual/en/function.crypt.php(使用 bcrypt -河豚)。另外:我们为什么不自己推出呢?

  6. 直接在应用程序中动态生成和执行 SQL 语句不是一个好主意,因为如果输入未正确转义,则存在 SQL 注入风险。准备好的语句更好,存储过程更好。

加盐的目的是使蛮力攻击和哈希密码的解释更加困难:

如果您正在查看单个密码,那么没有区别。但是,如果您有 1000 个用户,那么使用未加盐的密码条目,您可以通过将每个条目获取一次并与散列条目进行比较来执行字典攻击。通过使用盐,您必须对每个单词进行 N 次散列,其中 N 是 1000 个散列之间唯一盐的数量(理想情况下为 1000,但您可能会遇到盐冲突)。加盐还使得无法确定哪些用户使用相同的密码。

上面的意思是你可以有几千个用户,只需要几个字节的盐,你可以把盐加一。

然而,这在更大范围内是不安全的。由于您将使用一种众所周知的加密方法,因此获取哈希数据库的方法可以使用哈希密码的预编译列表或彩虹表,或者只是将您的密码与另一个密码结合起来。为防止您需要(长)散列。这意味着,如果您可以确保哈希密码在地球上的其他哈希中相当独特,那么哈希密码会变得更加安全。