【问题标题】:Moving old passwords to new hashing algorithm?将旧密码转移到新的哈希算法?
【发布时间】:2013-01-02 05:05:33
【问题描述】:

我正在将网站切换到 Rails。这是一个拥有 50k+ 用户的大型网站。问题是,现有的密码散列方法非常很弱。我有两个选择:

1) 切换到新算法,为每个人生成随机密码,然后将这些密码通过电子邮件发送给他们并要求更改后立即进行

2) 实现新算法,但使用之前的旧算法,然后对结果进行哈希处理。例如:

密码:abcdef =算法 1=> xj31ndn =算法 2=> $21aafadsada214

任何新密码都需要通过原始算法 (md5),然后将其结果进行散列,如果这有意义的话?这有什么缺点吗?

【问题讨论】:

    标签: security passwords


    【解决方案1】:

    您可以为所有使用新密码方法更新密码的用户创建一个新密码字段,并使用您的选项 2 更新每个人。

    将此与强制所有使用旧密码方法的用户在登录时更新密码相结合,将自动将所有活动用户移动到新密码方法。

    【讨论】:

      【解决方案2】:

      另一种方法是在数据库的不同列中保留迁移阶段可用的两个哈希值:

      • 如果登录时新哈希不存在,请检查旧哈希并保存新哈希并删除旧哈希。
      • 如果新的哈希值存在,只用它来验证。

      因此,一段时间后,您将只剩下新的哈希值 - 至少对于那些至少登录过一次的用户而言。

      【讨论】:

        【解决方案3】:

        一般不用重新设置密码,等用户下次登录即可。

        1. 首先尝试使用新算法验证输入的密码。届时,新密码和已转换的密码将不再需要验证。
        2. 如果不匹配,则与旧的哈希算法进行比较。
        3. 如果旧的哈希值匹配,那么你可以计算并存储新的哈希,因为你知道密码。

        每个密码存储系统都必须有切换到更好哈希算法的选项,您的问题不是一次性迁移问题。像 BCrypt 这样好的密码哈希算法有一个成本因素,有时您必须增加这个成本因素(因为更快的硬件),然后您需要与迁移所需的完全相同的过程。

        如果您的第一个算法真的很弱,并且您想立即提供更多保护,那么您使用散列旧哈希的选项 2 是一件好事。在这种情况下,您可以计算一个双哈希并将数据库中的旧哈希替换为新的双哈希。

        $newHashToStoreInTheDb = new_hash($oldHashFromDb)
        

        您还应该标记此密码哈希 (see why),以便您可以将其识别为双重哈希。这可以在单独的数据库字段中完成,或者您可以包含您自己的签名。现代密码散列函数还包括算法的签名,以便它们可以升级到更新的算法,并且仍然可以验证旧的散列。该示例显示了 BCrypt 哈希的签名:

        $2y$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
        ___
         |
         signature of hash-algorithm = 2y = BCrypt
        

        验证将像这样运行:

        1. 判断是否为双重哈希。
        2. 如果是新的哈希,调用新的哈希函数验证输入的密码,就大功告成了。
        3. 如果是双哈希,与双哈希算法new_hash(old_hash($password))比较。
        4. 如果双哈希值匹配,则可以计算并存储新的哈希。

        【讨论】:

        • 在我看来,双重哈希是一个可怕的想法。您散列密码的原因之一是,如果您的散列被泄露,他们仍然无法作为用户进行身份验证。如果您先执行 new_hash($raw_password),则用户只需在密码字段中输入旧的泄露哈希即可登录。违背了转向安全散列算法的目的。
        • @Lemon - 当然数据库中的旧密码哈希会被双哈希替换,但你是对的,这不是最佳解决方案。我编辑了答案以显示最佳实践,它仍然使用双重哈希,但标记了旧哈希。
        【解决方案4】:

        最简单的解决方案可能是在数据库中添加一个“密码哈希类型”列。最初将其设置为“旧”;当用户登录时,使用新算法重新哈希密码并将数据库类型设置为“新”。

        此方法的一个变体是将散列类型存储为散列字符串的部分。这同样有效,只要您可以明确区分不同的哈希格式,并且您还可以在相同的字符串,而不必为每个字段添加额外的字段到您的数据库中。

        例如,modern Unix crypt(3) implementations(以及各种高级语言中的相应函数,如 PHP)通常使用这种方法:经典的基于 DES(并且非常弱)的密码哈希看起来像 @ 987654326@,而(稍微)更现代的散列可能看起来像$1$z75qouSC$nNVPAk1FTd0yVd62S3sjR1,其中1 指定散列方法,z75qouSC 是盐,nNVPAk1FTd0yVd62S3sjR1 是实际散列,并且选择分隔符$ 是因为它不能出现在旧式 DES 哈希中。


        您建议的方法,其中新的哈希计算为:

         hash = new_hash( old_hash( password ) )
        

        在某些情况下很有用,因为它允许更新所有现有记录,而无需等待用户登录。但是,只有旧的哈希函数保留足够的密码中的熵。

        例如,即使是相当古老且较弱的加密哈希函数,如 unsalted MD5,也足够好,因为它的输出取决于整个输入,并且具有高达 128 位的熵,这比几乎任何密码将有(并且足以承受暴力攻击,无论如何)。另一方面,尝试使用旧的基于 DES 的 crypt(3) 函数作为旧的散列来应用这种构造将是灾难性的,因为旧的 crypt(3) 会忽略除前 8 个字符之外的所有字符每个密码(以及这些字符的最高有效位)。

        【讨论】:

        • 只需要补充一点,将“旧”或“新”存储为数据值是一个糟糕的主意。新的之后是什么?我曾经使用过一个名为“NewModel”的数据模型,但那不是。这一直是混乱的根源。
        猜你喜欢
        • 1970-01-01
        • 2011-10-10
        • 2021-09-27
        • 2020-09-16
        • 2012-05-24
        • 1970-01-01
        • 2012-06-20
        • 2021-03-15
        • 1970-01-01
        相关资源
        最近更新 更多