存储密码的散列变体是否存在任何安全风险?

信息安全 密码 哈希
2021-09-03 04:54:10

例如,如果假设应用程序要求在他们的密码可能因大写锁定打开而失败时告诉用户,并且假设应用程序无法真正知道大写锁定已打开,那么是否存在任何固有的存储散列密码以及散列“大写锁定”密码版本的安全风险,以便也可以将失败的密码与“大写锁定”版本进行比较?

如果包含具有此设置的用户帐户的数据库被破坏,除了正常密码散列之外,“大写锁定”密码散列的存在是否会使密码比其他情况下更容易受到攻击?

请注意,这是一个假设,我对安全隐患更感兴趣,而不是这是否是一个好的编程实践。

4个回答

有趣的问题。我想这取决于您存储的版本和数量。这样做的主要问题是您减少了多少搜索空间/熵。

例如,如果密码的大写锁定版本是密码的全大写版本,那么通过这种方式存储它,您正在减少密码的熵。在这种情况下,攻击者只需使用仅使用大写字符集的大写锁定版本进行搜索。这使他们的工作变得容易得多。

但是,如果大写锁定版本只是字母的大小写反转,即普通密码 ==aXbJklMP和大写锁定版本 == AxBjKLmp,那么您可能不会大大减少搜索空间。仍然有两个可能的密码而不是一个,这使得搜索找到其中一个密码的可能性增加了一倍......

另一种方法,如果您确实希望允许不同的密码变体。

当用户登录到您的应用程序时,您通常会获得密码的明文版本(希望通过 SSL),然后对其进行哈希处理并将其与您存储的哈希值进行比较。

因此没有真正需要存储不同的哈希版本。您可以存储一个密码哈希,但是当用户尝试登录时,您在登录时对用户提供的密码执行这些转换,然后对这些不同的变体进行哈希处理。例如,如果提供的密码无法与散列进行比较,则应用大写锁定转换、散列,然后再次检查存储的散列。如果匹配,您可以决定授予用户访问您的应用程序的权限。

这样,您只存储一个(规范)版本的哈希,但您允许相同密码的多个排列。

在攻击场景方面:

  • 针对您的密码哈希的离线攻击 - 保持不变
  • 尝试使用不同密码进行身份验证的在线攻击 - 相对于可能的排列数量,这将变得更容易

但是,通常应该更容易限制可能的远程暴力破解或密码猜测尝试的次数,以使其足够困难。你可以在这里阅读更多关于一些你可能应该实现的技术。

我仍然会非常小心支持哪些可能的排列,并测量它们可能会减少多少密码熵,但至少您不需要存储多个散列版本来执行此操作。

扩展我之前的评论:

如果两个存储的哈希(有和没有大写锁定转换)相同,我们可以知道用户的密码中没有字母。(或者至少他们的密码中没有小写字母,具体取决于您如何进行转换。)

由于 Mac 和 Windows(以及 Linux)处理大写锁定的方式不同,因此您必须存储大写锁定转换哈希的两种变体:大小写反转(Windows / Linux)和全大写(Mac)。现在我们可以从哈希中确定更多关于密码的信息。

  1. 如果所有三个哈希值都相同,则用户的密码中没有字母(大写或小写)。
  2. 如果 Windows 转换哈希不同但 Mac 转换哈希与主哈希相同,则用户的密码中有大写字母但没有小写字母。
  3. 如果所有三个哈希值都不同,则用户的密码中同时包含大写和小写字母。

另一个想法:将相同的盐应用于所有三种变体是否安全?尽管我认为应该如此,但我认为这样做没有任何显着优势。如果您对所有三个哈希应用不同的盐,我认为上述信息泄漏被否定。请注意,我不是密码学家,尽管在使用三种不同的盐对三个相关且可能相同的密码进行散列时不应该有任何信息泄漏,但可能需要更长的盐才能确保安全。

其他问题:

由于我们需要在用户登录时检查三个密码散列变体,因此需要计算所有三个散列。如果您使用 bcrypt(或类似的)具有适当数量的散列轮,您将希望减少该轮数,以便花费的时间大约是旧值的 1/3。(假设您将轮数设置为您可以处理的最高轮数,您现在需要计算每次登录的哈希数的三倍。)

即使第一个或第二个匹配,您也需要计算所有三个哈希的原因是,如果您不这样做,它会通过定时攻击启用凭证枚举。理论上,为了防止凭据枚举,无论用户不存在还是存在但密码错误,您都应该提供完全相同的响应。如果您为这两种情况返回相同的响应,但在用户不存在时需要几毫秒,而在用户存在时需要一整秒,您仍然启用了凭据枚举。

总而言之,我认为如果您对所有三个哈希使用不同的盐,总是计算所提供密码的所有三个变体并减少轮数,以便将时间减少 1/3,那么它应该是相对安全的。

但是,只存储一个哈希值可能更简单,检查用户提供的密码是否为小写字母,如果没有并且哈希值不匹配,建议他们可能已启用大写锁定。

迟来的想法

如果您打算接受 Mac 风格的转换,则无需像我之前建议的那样存储三个不同的哈希值。在散列之前将所有密码大写,并且仅存储全大写版本。这也可以防止定时攻击,因为您每次登录只需要计算一个哈希值。

通过这样做,您将人们可以使用的可能字母/数字/符号的数量减少了 26 个,因此您可能希望建议您的用户使用稍长的密码来进行补偿。

碰巧存储两个哈希值,或反转大写锁定效果并尝试两个版本,确实会降低安全性,因为它的系数介于 1 和 2 之间看起来有点棘手,所以让我们清楚地定义事物。

我正在使用大写锁定的大写反转概念。如果 capslock 效果真的是一切都是大写的效果,那么你永远不应该接受或存储那个全大写的版本;相反,按照@DW 的建议返回警告。

我假设您使用bcrypt这样的慢速和加盐哈希函数(如果您不这样做,那就是一个更大的问题,您应该先修复它)。“慢”部分可配置为迭代计数,您可以根据两个约束尽可能提高迭代计数:散列的CPU 预算(取决于您拥有多少可用 CPU 以及每秒有多少客户端连接),以及用户耐心(从来都不是很高)。攻击者的成本与迭代次数成正比。如果您存储两个哈希值(对于密码的“普通”和“大写”版本),那么两者都必须有自己的盐。

当您“尝试”用户发送的密码,然后使用相同密码的大写版本“重试”时,您实际上是在散列两次,因此您的约束存在开销。平均而言,CPU 工作量将乘以1+f,其中f是错误密码的比例(如果所有用户都输入正确,则f = 0 , f如果所有用户都是黑猩猩,在正确输入密码之前必须尝试十几次,则非常接近 1)。此外,每次您必须尝试使用​​大写锁定版本的密码时,用户必须等待两次才能获得访问权限(如果大写锁定版本被证明是正确的)或被可耻地拒绝(如果密码真的错误,大写锁定与否)。在某种程度上,当普通用户觉得这是他们的错时,他们对延迟有更多的理解,因为他们输入了错误,但我不会指望它。

最终结果是测试两个版本的密码会增加 1 到 2 倍的成本;相应地,您必须将迭代次数减少该因子,并且攻击者的有效性乘以该因子。

这实际上是用户体验(在许多情况下意味着“帮助台成本”)和安全性之间的权衡。如果可能,最好在输入密码之前检测到大写锁定并警告用户;但是,这可能很困难(我知道一个网站在客户端是 Internet Explorer 时成功执行此操作,但在 Chrome 和 Firefox 上失败;相反,它会为每个大写字母发出可见的警告,这非常糟糕,因为警告是可见的从远处看,这是有关密码的信息泄露)。