密码哈希:加盐+胡椒还是盐就够了?

信息安全 密码学 密码 哈希 hmac
2021-09-05 21:58:21

请注意:我知道安全密码存储散列的正确方法是 scrypt 或 bcrypt。这个问题不是为了在实际软件中实现,而是为了我自己的理解。

有关的


背景
据我所知,存储密码验证器的推荐/批准方法是存储:

$verifier = $salt + hash( $salt + $password )

在哪里:

  • hash() 是一种密码散列算法
  • $salt 是一个随机的、均匀分布的、高熵值
  • $password 是用户输入的密码

有些人建议在混合中添加一个密钥(有时称为胡椒)。辣椒是一个秘密的、高熵的、系统特定的常数。

基本原理似乎是,即使攻击者掌握了密码验证器,他或她也很有可能不知道胡椒值。因此,发起成功的攻击变得更加困难。

所以,我的问题是:
当散列密码增加整体安全性时,除了盐之外还添加胡椒值吗?

还是基于错误假设而感知到的安全性增加?

快速更新
我知道$salt(我在 StackOverflow 上写了一个很长的答案$pepper)的目的,附加的关键并没有改进盐的作用。
问题是,除了盐的作用之外,它是否增加了任何$pepper安全性

4个回答

在某些情况下,辣椒可能会有所帮助。

作为一个典型示例,假设您正在构建一个 Web 应用程序。它由 webapp 代码(在一些 webapp 框架、ASP.NET MVC、Python 上的 Pyramid 中运行,没关系)和一个用于存储的 SQL 数据库组成。webapp 和 SQL DB在不同的物理服务器上运行

针对数据库的最常见攻击是成功的 SQL 注入攻击。这种攻击不一定能访问您的 webapp 代码,因为 webapp 在不同的服务器和用户 ID 上运行。

您需要将密码安全地存储在数据库中,并提出以下形式的内容:

$hashed_password = hash( $salt . $password )

where与表示形式$salt一起以明文形式存储在数据库中,并为每个新的或更改的密码随机选择$hashed_password

每个密码散列方案最重要的方面是它hash是一个缓慢的密码安全散列函数,有关更多背景知识,请参阅https://security.stackexchange.com/a/31846/10727

那么问题是,考虑到向应用程序代码添加一个常量值几乎是零努力,并且应用程序代码通常不会在 SQL 注入攻击期间受到损害,那么下面的内容是否比上面的内容要好得多?

$hashed_password = hash( $pepper . $salt . $password )

where$salt以明文形式存储在数据库中,并且$pepper是以明文形式存储在应用程序代码中的常量(或配置,如果代码在多个服务器上使用或源是公共的)。

添加它$pepper很容易——您只需在代码中创建一个常量,输入一个大的加密安全随机值(例如来自 /dev/urandom hex 或 base64 编码的 32 字节),然后在密码散列函数中使用该常量. 如果您有现有用户,则需要迁移策略,例如在下次登录时重新散列密码并将密码散列策略的版本号与散列一起存储。

回答:

如果数据库受损并不意味着应用程序受损,则使用$pepper 确实会增加密码哈希的强度在不知道辣椒的情况下,密码仍然是完全安全的。由于密码特定的盐,您甚至无法确定数据库中的两个密码是否相同。

原因是hash($pepper . $salt . $password)有效地构建了一个伪随机函数$pepper作为键和$salt.$password作为输入(对于理智hash像带有 SHA*、bcrypt 或 scrypt 的 PBKDF2 这样的候选人)。伪随机函数的两个保证是您不能在密钥下从输出中推断出输入,并且在不知道密钥的情况下也不能从输入中推断出输出。这听起来很像哈希函数的单向属性,但不同之处在于,使用密码等低熵值,您可以有效地枚举所有可能的值并计算公共哈希函数下的图像,从而找到其值图像与原图像匹配。使用伪随机函数,如果没有密钥(即没有辣椒),您将无法这样做,因为您甚至无法计算没有密钥的单个值的图像。

$salt如果您可以长时间访问数据库并且您仍然可以从外部正常使用应用程序,则此设置中的重要作用会发挥作用。如果没有,$salt您可以将您控制的帐户的密码设置为已知值$passwordKnown,并将哈希值与未知密码的密码进行比较$passwordSecret好像hash($pepper . $passwordKnown)==hash($pepper . $passwordSecret)且仅当$passwordKnown==$passwordSecret您可以将未知密码与任何选择的值进行比较(作为技术性,我假设哈希函数具有抗碰撞性)。hash($pepper . $salt1 . $passwordKnown)==hash($pepper . $salt2 . $passwordSecret)但是当且仅当$salt1 . $passwordKnown == $salt2 . $passwordSecret和 as$salt1$salt2被随机选择为$passwordKnown和时,你得到的盐$passwordSecret 盐永远不会相同(假设足够大的随机值,如 256 位),因此您无法再将密码相互比较。

(注意:使用盐只是工作的一半;您还需要使散列函数变慢——这样攻击单个低熵密码仍然很困难。慢通常通过多次迭代来实现,或者散列10000份盐和密码。)

您的“胡椒”所做的是将哈希转换为MAC从散列函数中制作一个好的、安全的 MAC 并不容易,所以你最好使用HMAC而不是自制的构造(理论上的说法是,抗冲突的散列函数不一定与随机预言没有区别)。

使用 MAC,您可能会在以下意义上获得一些安全性:攻击者的数据库读取访问可能不再是一个真正的问题。MAC 密钥(“胡椒”)可能集中保密需求。但是,这依赖于 MAC 也是一种单向函数,这是您可以从许多 MAC 结构(包括 HMAC)中获得的属性,但从密码学上讲,它并不能真正得到保证(有一些微妙之处)。

“胡椒”意味着您有一个要管理的密钥,包括以一种抗重启的方式进行安全存储。密钥很小,适合 RAM,但由于存储要求,尚不清楚它是否真的提高了安全性。可以读取整个数据库的攻击者通常也可以读取整个硬盘,包括任何“受保护”文件。小尺寸的密钥可以允许一些高级设置,例如存储在智能卡上的密钥,这些智能卡在启动时使用,但之后不会保持连接。总而言之,胡椒粉是否值得努力完全取决于上下文——在一般情况下,我建议不要这样做,以避免增加复杂性。

我想指出辣椒的真正作用。

辣椒什么时候有用?

正如其他人已经指出的那样,添加辣椒只是一个优势,只要攻击者可以访问数据库中的哈希值,但无法控制服务器,因此不知道辣椒这是典型的 SQL 注入,可能是更常用的攻击之一,因为它很容易做到。

辣椒能改善什么?

$hashValue = bcrypt('12345', $cost, $salt);

即使您正确使用了慢速密钥派生功能,您也可以通过字典攻击轻松获得此密码。将最常用的密码放入字典中,并使用此弱密码进行暴力破解。我们很可能在(太多)情况下找到密码。

$hashValue = bcrypt('12345anm8e3M-83*2cQ1mlZaU', $cost, $salt);

有了胡椒,弱密码的长度就会增加,它现在包含特殊字符,更重要的是,您将在任何字典中都找不到它。因此,只要辣椒保持秘密,它确实可以防止字典攻击,在这种情况下,它可以保护弱密码。

编辑:

有一种添加服务器端密钥的更好方法,而不是将其用作辣椒。使用辣椒,攻击者必须在服务器上获得额外的权限才能获取密钥。通过首先计算哈希值,然后使用服务器端密钥加密哈希值(双向加密),我们可以获得同样的优势。这使我们可以选择在必要时交换密钥。

$hash = bcrypt($passwort, $salt);
$encryptedHash = encrypt($hash, $serverSideKey);

关于 Unix 密码的加盐和迭代发明的论文(Password Security: A Case History, Morris & Thompson, 1978)也描述了辣椒的等价物:

用户密码的前八个字符用作 DES 的密钥;然后该算法用于加密一个常数。虽然这个常数目前为零,但它很容易访问并且可以依赖于安装。

不过没听说有人用过。还有其他人吗?