切换密码实现时,在现有 SHA1 哈希上使用 bcrypt 是否足够好?

信息安全 密码 密码管理 bcrypt
2021-08-14 11:44:10

我正在改进一个 CMS,其中当前存储密码的实现只是sha1(password). 我向我的老板解释说这样做非常不安全,并告诉他我们应该改用 bcrypt,他同意了。

我的计划是通过 bcrypt 运行所有现有哈希并将其存储在密码字段中,然后使用以下伪代码检查密码correctPassword = bcrypt_verify(password, storedHash) or bcrypt_verify(sha1(password), storedHash)

这样,新用户或更改密码的用户将获得“真正的”bcrypt 哈希,而现有用户不必全部更改密码。这样做有什么缺点吗?虽然要求所有用户选择一个新密码可能是理想的,但这样做是否会在安全性方面损失很多?

我在想,即使攻击者可以同时访问数据库和代码,即使 bcrypt 的大部分“输入”是 40 个字符的十六进制字符串,破解也不会快得多,因为慢速部分 ( bcrypt_verify()) 仍然必须为每个用户的每次密码尝试调用。

4个回答

实际上,这是保护原本不安全存储的密码的好方法。不过,这个方案有一个弱点,可以在标记旧哈希时轻松克服,所以我更喜欢这个解决方案:

if (checkIfDoubleHash(storedHash))
  correctPassword = bcrypt_verify(sha1(password), storedHash)
else
  correctPassword = bcrypt_verify(password, storedHash)

想象一下,攻击者获取了旧备份。他会看到 SHA 哈希,如果您使用bcrypt_verify(...) or bcrypt_verify(sha1(...)).

大多数 bcrypt 库都会自己添加所用算法的标记,因此如果添加自己的“双哈希标记”也没问题,当然您也可以为此使用单独的数据库字段:

$2y$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |
 hash-algorithm = 2y = BCrypt

为什么不简单地将 bcrypt(sha1(password)) 用于所有新旧密码?这避免了人们使用您的旧哈希作为密码的问题,并且也比您的建议更简单。

这是一个很好的策略,除非用户决定生成一个长度超过 160 位的真正随机密码,否则您不会失去任何安全性,因为它将被截断。所以差别很小。(在这种情况下,暴力破解原始文本仍然需要大量时间)

您可能会选择实施一些逻辑来在用户下次更改密码时迁移密码,但我认为没有任何需要立即更改密码的风险,除非您认为哈希值已泄露。

我最近实现了一个类似的系统,用于将密码迁移到 bcrypt。然而,我们最初使用的是 SHA256(密码 + 盐)哈希而不是 SHA1。

当我们在用户登录(可选)或更改密码时将用户切换到 bcrypt 时,会重新生成此盐。所以哈希不会基于原始。然后,我们主要使用这个随机数作为 IV,使用存储在数据库外部的密钥加密数据库中的 bcrypt 哈希。

这样做只会真正防止注入攻击和仅从提供任何有用密码信息的数据库中提取的数据。但是开销对我们来说不是问题,我们可以在需要时更改此外部密钥。

使用 SHA256 哈希作为 bcrypt 的输入还可以将输入密码的长度保持在 bcrypt 的最大值以下(相关 文章

当我四处寻找有关我所做的任何事情的建议和问题时,我也看到了以这种方式使用 SHA1 的任何参考,唯一担心的是这两个链接中的第二个来自 Thomas Pornin:

使用安全哈希函数对密码进行预处理是安全的;可以证明,如果 bcrypt(SHA-256(password)) 被破坏,那么要么密码被猜到,要么 SHA-256 的某些安全特性被证明是错误的。没有必要在那个水平上摆弄盐;只需对密码进行哈希处理,然后在结果上使用 bcrypt(使用盐,作为 bcrypt 的要求)。SHA-256 被认为是一种安全的散列函数。

因此,持有 SHA1可能不是一个好的选择 - 为什么要从一开始就单独使用它来迁移。话虽这么说,在不久的将来,不可能有一种攻击会在攻击 bcrypt(SHA1(password)) 时提供任何实际价值,而不会涉及与 bcrypt 本身的某种妥协。