盐需要是唯一的还是不可预测的?

信息安全 密码 哈希 密码管理
2021-08-12 15:29:11

我一直认为盐只是用来防止使用彩虹表。其他人建议他们应该在每个帐户的基础上都是独一无二的。目前我一直在使用配置文件作为盐。过去我做过 md5(盐 + 密码),但现在我通过 .NET PBKDF2 使用.NET PBKDF2(pass, salt_from_config)

我做错了还是不安全?盐应该是唯一的,还是它们只是不可预测的,所以没有人可以提前生成彩虹表?

3个回答

我发布了一个答案,在另一个问题上对此进行了解释,这应该为您提供有关密码存储的所有主要安全问题的良好背景。

要更直接地回答您的问题-盐:

  • 必须是唯一的。
  • 应该是不可预测的。
  • 潜在的攻击者应该不知道。

类似方案的问题H(pass + username)在于攻击者知道盐。因此,虽然他无法使用单个彩虹表破解数据库中的每个密码,但他可以为关键用户帐户创建彩虹表。这允许他提前为特权用户帐户(例如管理员)计算彩虹表,然后在他破坏数据库时立即使用它。

这是一个问题,因为它让您没有时间对违规行为做出反应。在您收到有关攻击的警报后的几秒钟内,攻击者已登录您的管理员帐户。如果攻击者在入侵后被迫破解密码,那么您就有时间锁定特权帐户并更改密码。

您还可以查看盐+胡椒方案,其中胡椒是存储在数据库之外的第二种盐,例如在代码中。这有助于防止攻击的 SQL 注入模型,其中坏人只能访问数据库。

最佳做法是这样的(随意省略辣椒):

hash = KDF(pass + pepper, salt, workFactor)

在哪里:

  • KDF 是一个强大的、缓慢的密钥派生函数,例如 PBKDF2 的 bcrypt。
  • pass 是一个强密码(执行密码策略!)
  • pepper 是存储在代码中的长随机常数值。
  • salt 是一个长的随机唯一值,与密码一起存储在数据库中。
  • workFactor 适合您的硬件和安全要求。

每个密码的盐应该是唯一的。

如果您对每个密码使用相同的盐,攻击者可以简单地使用该特定盐生成彩虹表并破解您数据库中的大部分密码。

关于关于 PBKDF2 和 GPU 加速的评论,我想在 Security.SE 上向您指出这个链接,Thomas Pornin 给出了非常出色的答案。

对盐的确切要求取决于密码散列算法,但对于通常的方法(bcrypt、PBKDF2...),唯一的要求是盐是唯一的或者至少与实际一样独特;奇怪的碰撞不是什么大问题,只要它不经常发生并且不能从外部强迫。

独特性是全球性的;盐在给定的服务器中是唯一的是不够的。两个不同的服务器,使用相同的散列算法,也应该有不同的盐。

获得全球唯一性的一种相对常见且廉价的方法是从具有足够长度的加密强 PRNG 生成盐(16 个随机字节就足够了)。这就是bcrypt所做的。如果 PRNG 有偏差,那么您需要更长的盐来实现唯一性。如果 PRNG 由于种子太小或内部状态太小而弱,那么这种方式将无法令人满意地获得唯一性。用户名不是好盐,有两个原因:

  • 相同的用户名可以出现在多个服务器中(例如,每个服务器可能有一个“管理员”帐户,使用该确切名称);
  • 用户更改密码时不更改姓名,导致盐重用。

密钥保密且必须保密)或初始化向量不同(IV是一些算法的“起点”,在CBC加密的情况下可能会有统一性和不可预测性等附加要求)。放弃你的盐值通常没有问题;无论如何,从密码中重新计算哈希值的人必须知道盐。因此,盐的发布是基于密码的文件加密所固有的(盐然后被编码在文件头中)。它还必须在客户端上发生散列的身份验证协议中发布(这在 Web 上下文中非常罕见,因为 Javascript 太慢了)。不必要地发布盐是没有意义的,但是将它们保密也不能真正增强安全性。

这个答案中,引发了一个边缘场景:攻击者事先学习盐,准备一个大的预先计算的表,然后实施揭示密码哈希的实际攻击。这不会让攻击者更容易破解密码;事实上,这增加了他的工作量(他必须生成一个完整的表,而不是在找到密码时停止,所以这平均是双倍的成本;如果表是彩虹说服的,则为表生成额外输入 1.7 个因子;和有存储费用)。它改变的是动态:这缩短了从闯入(哈希值被盗)和密码恢复之间的时间。这是一个边缘案例,所以不要担心。如果您在身份验证协议中使用密码散列进行存储,其中散列发生在服务器端,那么您只需将盐与散列值一起存储,并且盐将与散列值本身一样保密,这很好。在其他情况下(例如基于密码的文件加密),盐会更加“公开”,但这在任何方面都不是关键,所以不要为了保护盐的秘密而增加额外的复杂性(额外的复杂性是不好的,而且很多比公共盐更差)。