盐必须是随机的,还是只是独特的和未知的?

信息安全 密码 密码学 哈希
2021-08-22 07:45:13

首先,我的动机是避免将盐作为纯文本存储在数据库中。就这个问题而言,盐没有存储在数据库中。

在评论和聊天中讨论后,我提出了一个理论:

似乎doman_name + user ID辣椒一起使用将提供随机性和独特性的充分组合。

这样的方法是否可以提供与随机盐一样多的安全性,而不必将指定的盐作为纯文本存储在数据库中?

4个回答

盐的唯一要求是全球唯一。它不必随机的,当然也不必是未知的。

请注意关键字global一个盐不仅在你的数据库上下文中必须是唯一的,而且在每个单独的数据库的上下文中都必须是唯一的。电子邮件或增量用户 ID(非常)不太可能满足此要求。使用随机生成的盐就可以了。

编辑

我将使用domain + userid评论中提出的作为盐的想法来解决整个问题。我不认为这个方案是一个好主意,尤其是与仅使用随机盐相比。

如果您的站点是高目标站点,攻击者可能会发现在攻击发生之前生成针对管理员帐户的彩虹表是值得的。这可能允许他在攻击被发现之前获得访问权限。当您考虑这种情况时,该domain + userid方案不能很好地工作,因为管理员帐户通常在userid系统的前几秒内,尤其是在userid使用计数器递增时。

话虽如此,整个讨论在实践中确实没有实际意义。生成随机盐既简单又快速。许多密码散列库,尤其是bcrypt已经为您完成的那些。即使它同样安全,也没有必要推出自己的方案。让我们面对现实吧,我们都是懒惰的人不是吗?:P 为什么要重新发明轮子?

密码盐的重要特征(正如 Terry Chia 正确指出的那样)是它应该是全球唯一的:任何两个密码哈希(即使在不同的站点上)都不应该具有相同的盐。

使用独特的盐可以防止攻击者可能使用的各种相关攻击方法,这些攻击者已经破坏了用户数据库并希望从其哈希中恢复密码:

  • 首先,它可以防止攻击者轻易判断多个用户是否具有相同的密码(这将使这些用户成为明显的目标,因为这样一个常见的密码可能很容易被猜到并提供高额回报)。

  • 相反,它还可以防止已经破坏多个站点的攻击者能够轻松地判断哪些用户在两个站点上使用了相同的密码。

  • 此外,为每个用户使用唯一的盐可以防止攻击者从同时攻击多个帐户中获得任何好处。如果没有盐(或使用非唯一盐),攻击者可以散列猜测的密码,然后将其与数据库中的每个密码散列进行比较,从而更有可能获得至少一个匹配项。使用唯一的盐,这是不可能的,因为攻击者必须在散列每个密码猜测之前选择盐(以及单个目标用户)。

  • 最后,独特的盐也使得使用预先计算的哈希查找表(如所谓的“彩虹表”)来加速密码破解变得不切实际,因为从本质上讲,每个用户都需要一个单独的表。

基本上,加盐的主要目标是确保一起破解多个受损密码哈希应该不会比分别破解它们更容易——不多也不少。使用全球唯一的盐可以实现这一目标。


确保全局唯一性(可能性很高)的一种简单方法是使用足够长的随机字符串作为盐。但是,也存在其他方式。

例如,您可以通过将本地唯一的用户标识符(例如电子邮件地址或帐号)与固定的、全球唯一的站点标识符(例如网站的域名)相结合来获得全球唯一的盐,并且可能其他一些项目,例如目的指示符(以防每个用户可能需要多个唯一的盐)和时间戳(因此新密码总是会得到不同的盐)。例如,以下字符串将是非常好的全局唯一盐:

“这是 security.stackexchange.com 上用户 5457 的密码盐,创建于 2013 年 9 月 1 日 13:35:23.94730 UTC”

这个也是如此:

“password_salt:5457:security.stackexchange.com:20130901T133523.94730”

如果您希望您的盐更紧凑,您还可以通过像SHA-256之类的加密哈希函数提供上述任一字符串,并将输出用作盐。或者,如果您的系统提供了内置的GUID生成器,您可以直接使用它(当然,假设它可以正常工作)。


综上所述,使密码盐不仅唯一而且不可预测有一个潜在的好处:它可以防止攻击者在破坏您的数据库之前开始暴力攻击。

基本上,如果我作为攻击者知道您的管理员帐户的盐是“12345678”,那么我可以设置我的计算机来编译一个查找表,其中包含使用该盐散列的或多或少的常用密码,甚至在我之前找到了一种方法来获取您的实际密码哈希一旦我确实找到了一种方法(可能是几周或几个月后),我就可以在我的表中查找哈希,希望它可能与我的程序同时尝试过的密码之一匹配,在这种情况下,我会立即知道密码。

但是,如果我什至不知道您使用的实际盐是什么,那么在我第一次获得正确的盐之前,我将无法开始猜测密码。这需要额外的时间,可能会让你在我破解密码之前更改密码。

同样,长随机盐(很有可能)不仅是唯一的,而且是不可预测的。也就是说,还有其他方法可以实现这一目标。例如,您可以采用上面显示的任一唯一示例盐,然后将一些秘密值(例如,存储在配置文件中的随机字符串,可能定期更改)附加到它。由于这个秘密对所有用户都是相同的,所以它对唯一性没有帮助,但它可以确保我在没有首先以某种方式学习秘密的情况下无法开始暴力攻击。


附录:关于您对问题的编辑,我想说您的方案(使用域名+用户ID+胡椒作为盐)应该足够了。如果可能的话,我还会包含一个时间戳,但这显然需要您将时间戳存储在某个地方。

也就是说,我仍然觉得你的前提(“盐没有存储在数据库中”)有点令人费解。如果您可以将密码散列本身存储在数据库中,为什么盐也不能呢?

事实上,许多常见的密码散列方案根本不使用单独的数据库列作为盐,它们只是将其存储为散列字符串的一部分,例如 as method$salt$hash,其中method标识所使用的散列方法,salt并且hash是盐和散列值使用一些合适的方案(例如base64)进行编码,并且$只是一个任意的分隔符,不会出现在编码的盐或散列中。

盐应该是唯一的,必须足够长,并且应该是不可预测的。随机性不是必需的,但它是计算机满足这些要求的最简单方法。保密并不是盐的目的,盐即使在已知的情况下也能实现其目的。

唯一性意味着它不仅在您的数据库中应该是唯一的(否则您可以使用用户 ID),它应该在全球范围内是唯一的。有人可以为盐创建彩虹表,例如 1-1000,并且能够检索具有这些用户 ID 的所有帐户的密码(通常管理员帐户的用户 ID 较低)。

足够长:如果盐太短(可能的组合太少),构建彩虹表再次变得有利可图。然后盐和密码一起可以被视为一个更长的密码,如果你可以为这个更长的密码构建一个彩虹表,你也会得到更短的原始密码。对于非常强且长的密码,实际上根本不需要加盐,但是大多数人为生成的密码都可以被暴力破解,因为它们很短(人们必须记住它们)。

使用从其他参数派生的盐也可以属于这一类。只是因为您从用户 ID 计算哈希,这不会增加可能的组合。

不可预测性稍微不那么重要,但再次想象一下,如果您将用户 ID 用作盐,攻击者可以找出接下来的几个用户 ID 将是什么,因此可以预先计算出少量的彩虹表。根据使用的哈希算法,这可能适用或不适用。然后他有时间优势,可以马上找回密码。如果管理员帐户使用可预测的盐,那么问题就更大了。

因此,使用从操作系统随机源(dev/urandom)生成的真正随机数是您能做的最好的事情。即使您忽略了上述所有原因,当有更好的方法时,为什么要使用衍生盐,为什么不使用您知道的最佳方法?

更新:回答更改后的问题:

只使用 domain_name + user_id 来生成 salt 肯定不是一个好主意,因为它可以被猜到并且可以预测。辣椒在这里无济于事,因为它与域名一样恒定,您不应该依赖它来保密。使用 user_id 可以获得唯一性,使用 domain_name 可以获得全局唯一性,但是您错过了不可预测的部分。

在您之前的示例中,您将密码本身添加为随机参数,例如salt = hash(domain_name + user_id + password). 只要根本不存储盐,这就可以工作,要验证密码,您将拥有所有必要的参数。我看不出这个概念有什么缺陷,但这并不意味着我推荐它。正如已经提到的,你不能比生成一个随机的独立盐做得更好。将此盐添加到哈希中并提取它以进行验证并不是一项艰巨的工作,大多数合适的哈希函数已经为您完成了,因为它们还必须以相同的方式存储成本因子。

首先,我的动机是避免将盐作为纯文本存储在数据库中。

为什么?没有充分的理由避免将盐以纯文本形式存储在数据库中。这就是盐的正常储存方式。

通过您的问题的各种迭代,您提出了各种不同的做事方式,但它们最终要么具有相同的安全属性,要么(通常)使事情变得更糟。

加盐的目的是防止攻击者在一次破解多个帐户时共享工作。攻击者通常不是针对特定站点上的特定用户,他们想要的是获得立足点或破解尽可能多的帐户。加盐的目的是使在 ACME 站点上破解 Joe 的帐户与在 Yoyodyne 站点上破解 Jane 的帐户或 Joe 的帐户需要完全不同的计算。因此需要一种全局唯一的盐:跨账户,跨数据库(甚至跨时间)。

如果您使用密码作为盐计算的一部分,那么它根本就不是盐。盐需要独立于密码。如果你根据密码计算盐,那么它就成为哈希函数的一部分:H(S(P),P) 是 P 的函数,你只创建了一个新的哈希函数 H'(P) = H( S(P),P)。

使用用户名不好,因为它在数据库中可能是相同的。如果使用从用户名派生的 salt,拥有 ACME 数据库和 Yoyodyne 数据库的攻击者可以在不重复工作的情况下破解这两个数据库。

如果您使用域名和用户 ID,则可以。确保世界上没有其他人使用相同的域名;特别是,如果您有多个子站点,请不要使用相同的域名:包括服务名称或使名称唯一所需的任何内容。另外,不要重复使用用户 ID。只要你的盐是全球唯一的,它就是一种合适的盐。

但是请注意,使用域名和用户 ID 作为盐而不是通常使用的随机盐,您不会获得任何安全性。盐仍然有效地存储在数据库中,只是以某种奇怪的方式。您为每个数据库记录节省了几个字节,这应该不重要。

辣椒也不会显着提高安全性。你不能假设你的辣椒是秘密的。如果攻击者已经能够获得您的密码数据库的副本(通常是通过破坏您的应用程序或通过访问备份),那么他们很可能也可以访问胡椒粉(它需要可供应用程序访问并得到支持起来,毕竟)。胡椒粉有用的唯一情况是诸如 SQL 注入之类的妥协,它只授予攻击者对数据库的访问权限,而不会扩展到应用程序妥协,甚至通过管理帐户也不行。不要指望它。即使您使用胡椒,您的盐也必须是全球唯一的

我强烈建议生成随机盐并将其与密码一起存储。最好不要自己编写代码:使用正确的现有库你越偏离推荐的做法,你做错的风险就越大——可能是完全的、危险的错误,就像从密码中提取盐一样。

请记住,除了加盐之外,密码哈希必须很慢。这意味着 PBKDF2 或 bcrypt 或 scrypt,迭代计数根据当前计算机速度进行了调整。

有关更多背景信息,请阅读我们关于此主题的众多主题: