在将密码传递给 bcrypt 之前使用 sha256 对密码进行哈希处理的优缺点是什么?

信息安全 密码 bcrypt Python sha256 django
2021-09-03 18:11:18

我最近意识到 bcrypt 将密码截断为 72 个字符。实际上,我的直觉是,这不会造成任何重大的安全问题。但是,我知道这确实意味着任何使用 bcrypt 的软件库都可能存在“错误”,即两个以相同 72 个字符开头的超长密码将是等效的。

Django Web 框架的作者写了一些东西BCryptSHA256PasswordHasher来解决这个问题。它所做的是首先使用 SHA256 对用户的密码进行哈希处理,然后再将其传递给 bcrypt。

我团队中的一些开发人员正在讨论这种方法的优点,我只是想收集来自一般社区的想法。一方面,这是否在某种意义上通过缩小输入 bcrypt 的可能值的总空间来降低安全性?SHA256 将始终输出 32 个字节,远远少于我们原本应该拥有的 72 个(重要)字节。另一方面,如果不首先对密码进行哈希处理,我们基本上有一个分布不均匀的空间,对于每个 72 个字符的前缀,所有以该前缀开头的密码都会发生冲突。

我的直觉是,超过 72 字节的分配问题并不重要,而且BCryptSHA256PasswordHasher真的没用。也就是说,我也认识到,一般来说,像 Django 一样流行和广泛使用的框架往往会认真对待这些事情,并且有充分的理由支持他们的决定。因此,如果这有意义的话,我对自己的直觉并没有太大的信心。

我错了吗?在 bcrypt 之前使用 SHA256 散列密码是否不会降低安全性,即使在理论上也是如此?分配问题是否比我意识到的更严重?还有什么我完全没有想到的吗?

4个回答

链接中所述的 Bcrypt 限制为 72 个字符。

SHA256 的 OUTPUT 大小可能只有 32 字节,它的消息输入是 ((2^64)-1)\8 或大致

2305843009213693952 字节(假设 char 为 8 位)

对于 Bcrypt,它接收一个 32 字节的密码来加密,对于 SHA256,它可能是一个 400 个字符的数据流(IE 密码)。

所以不,你并没有失去熵,你正在克服 Bcrypts 设计的限制(如果你想称之为限制。

讨论 SHA2 的链接是大小: https ://en.wikipedia.org/wiki/SHA-2

我错了吗?在 bcrypt 之前使用 SHA256 散列密码是否不会降低安全性,即使在理论上也是如此?

bcrypt 有一个 184 位的输出哈希将更多熵位作为输入并不会改变可能输出的数量被限制在该值之下。

256 位 > 184 位,因此我看不出安全性会如何降低。

您可能会问为什么输入更宽?(72 字节与 23 字节)

它的长度与熵位无关,单词和表情符号可以使用更多的长度/字节,当这些用作“字母”的单位来组成密码时,您可以了解熵位的数量如何不限于单个字节/字符(这是误解似乎集中的地方)。

SHA-256 允许您将该表示压缩到 bcrypt 接受的输入大小,它仍然可以保持原始输入的熵


更多细节

256 位足够安全/防御,并且比 192 位输出更多

我在这里看到关于 SHA-256 是 32 个字节64 个字符作为字符串的讨论(假设十六进制编码,16 值字符集 0-9,af),无论你怎么看,你仍然有 256 位表示。

这已经是一个不切实际的攻击量(并不是说这些哈希会受到攻击,因为可以肯定实际密码的熵少于 256 位)。

您还将获得 184 位的输出哈希(在 Radix-64 编码为 31 个字符之前,8 位被截断),因此任何关于减少输入的担忧都没有实际意义,无论如何,您很快就会在输出上遇到冲突

另请注意,虽然限制为 72 个字节,但某些实现可能会将输入截断/限制为 55 个字符长度的字符串(包括空终止符字节时为 56 个)。

因此,如果您没有将 SHA-256 哈希的 32 个字节传递给 bcrypt,而是将其作为十六进制字符串提供给 bcrypt,则您可能希望使用 base64 编码,它将 32 个字节表示为 44 个字符而不是 64 个字符。

使用散列来限制输入的长度还可以避免实现错误(修复 OpenBSD 2014、NodeJS 2020),其中超过 255 字节的密码会溢出 8 位字符串长度,这可能会将密码视为只有几个字符长。

密码熵的组成不限于单个字母数字/ASCII 字符

密码熵不仅仅是通过单个字符/字节来衡量,这在其他关于 95 个 ASCII 值字符集的答案中是一个共同的焦点。您可以使用单词(例如7776 个单词的 EFF diceware 列表)替换密码组合中的单个 ASCII 值,或者在这种情况下使用密码短语。

这些当然长度更长,因此字节更长,如果每个单词平均 10 个字符,那么在从其他单词到 bcrypt 的任何额外熵丢失之前,您只能容纳 7 个单词。那只有大约 90 位 ( log2(7776^7))。

密码还不必局限于有限字母表中的单词。外语甚至表情符号都可以是有效的输入,但对于单个视觉字形,它们可能使用多个字节。

单个字形(“字符”)在视觉上可以用多个字节表示,尤其是表情符号

您可以拥有一个使用17 个字节的表情符号,例如:(🕵🏼‍♀️ 侦探 + 肤色 + 性别组合),用 unicode 中的 5 个代码点表示:0x1f575 0x1f3fb 0x200d 0x2640 0xfe0f. 这些表情符号由一系列其他基本表情符号和一些不可见的修饰符(如0x200dZWJ 和0xfe0fVS16)组成。

单个字形,多个代码点(每个 UTF-8 编码代码点的字节数各不相同)。有些表情符号仍然使用更多字节,但表情符号的整体熵位并没有那么高,无法证明字节成本是合理的,就像使用 bcrypt 一样。一个典型的表情符号(不涉及任何序列)可能使用 3-4 个字节。

TL;DR:SHA-256 允许避免长度限制,否则熵会丢失

因此,输入密码的 SHA-256 哈希可以解决长度问题。当前的表情符号约为3,521(截至 2020 年 9 月 Unicode 13.1),21 个表情符号将适合 256 位熵 ( log2(3521^21) = ~247),但可以很好地使用超过 72 个字节的大小,根据表情符号的选择可能超过 500 个字节。使用 SHA-256 哈希可确保您不必担心用户密码的字节长度。

👩‍👩‍👧‍👦👩‍👩‍👧‍👦👩‍👩‍👧‍👦🕵🏼‍♀️(92 字节)与👩‍👩‍👧‍👦👩‍👩‍👧‍👦👩‍👩‍👧‍👦❤️(81 字节),前 3 个家族表情符号都使用 75 个字节(每个 25 个)。如果您使用 bcrypt 输出具有相同盐的散列,它们都将忽略第 4 个字形,从而产生相同的散列。

根据Django 的文档,这是因为

密码在第一个 NULL 字节(如果有)处被截断,并且只有密码的前 72 个字节被散列......所有其余的都被忽略。

他们承认这不是安全问题:

虽然本身不​​是安全问题,但 bcrypt 确实有一个主要限制(见前文)

此外,他们注意到 72C 密码的结尾并不像其他密码“重要”:

此外,字节 55-72 没有完全混合到生成的哈希中(需要引用!)。

我可以将其翻译为“密码的结尾比开头更容易破解”。

这就是为什么他们首先做一个 has first (它不是 NULL 字符,并且少于 72 个字符并且没​​有 55-72 个字符):

为了解决这两个问题,许多应用程序首先通过消息摘要(例如 SHA2-256)运行密码。Passlib 提供了预制的 passlib.hash.bcrypt_sha256 - BCrypt+SHA256 来解决这个问题。

如果您需要处理长密码,这会提高安全性。如果您只有很短的常用密码(A-Za-z0-9 和键盘上的特殊字符)密码,这可能会稍微减少它,因为潜在的冲突(嗯,没那么重要)。所以对于一个通用库来说,散列是有意义的。对于管理用户密码的 lambda 网站/应用程序,它似乎没用(做错比完全不做的风险更大)。

我认为在将密码发送到 bcrypt 之前使用散列密码是为了克服不良的密码习惯。只要您生成随机密码,那么使用散列将是不利的。

假设您的密码是 . 所以现在我可以将我的 72 个字符保存在一个文档中并复制/粘贴它,然后附加它所在的计算机的名称(或其他一些人类容易做的计算)。正如我们所看到的,这将导致它们基本上都是相同的密码。通过对其进行散列处理,我们可以防止这种情况发生,但代价是密钥空间,因为我们使用的特定算法输出 32 个字符。

现在,让我们假设每次设置密码都是随机生成的,整个过程。一个 72 个字符的密码有 (95 72 )个组合你的碰撞几率是 1/95 72

如果我们散列这个随机值,我们将得到 95 32 个值中的 1 个,因此我们串通的机会是 1/95 32所以我们已经可以看到散列会花费我们一些安全性。

不过还有更多。如果我们考虑哈希总是会输出一个 32 个字符的字符串,即使输入少于 32 个字符,那么我们正好有 95 32 个可能的密码。但是 bcrypt 将采用长度范围(31、55 等)的密码。所以你也可以计算那些(95 72 +95 71 +95 70 +95 69 ...),它们很快加起来,导致两种方法之间的进一步差异。

这是理论上的,但实际情况如何。我们需要多少个字符才能获得合适的安全性?如果在 32 个字符的散列中发现碰撞需要比宇宙热死之前更长的时间(我不知道这是事实,只是举个例子),那么我们已经压缩了所有的安全性我们可以摆脱那个。通过延长密码,我们只获得温暖的模糊感觉。但是,我们可以通过散列增加安全性,这有助于减少不良密码做法(当密码长度超过 72 个字符时)。