【问题标题】:Password hashing - how to upgrade?密码哈希 - 如何升级?
【发布时间】:2011-04-26 16:40:45
【问题描述】:

有很多关于最佳算法的discussion - 但是如果您已经投入生产怎么办?如何在不重置用户的情况下升级?


编辑/免责声明:虽然我最初想要一个“快速修复”解决方案并选择了 orip 的响应,但我必须承认,如果您的应用程序中的安全性非常重要,甚至会困扰这个问题,那么快速修复是错误的心态他提出的解决方案可能是不够的。

【问题讨论】:

  • 从什么升级到什么?如果你是从“无哈希”升级到“哈希”,那么应该没有问题。
  • @Greg hashing to hashing - 我当然希望 SO 上没有人存储明文 :)
  • 你得到了错误的答案,实现仍然容易受到碰撞。下次登录时只需对纯文本进行哈希处理。
  • @Jacco - 我很抱歉 @Rook - 我会修改

标签: security database-design authentication hash passwords


【解决方案1】:

一种选择是让您存储的哈希包含一个算法版本号 - 因此您从算法 0(例如 MD5)开始并存储

0:ab0123fe

那么当您升级到 SHA-1 时,您会将版本号提升到 1:

1:babababa192df1312

(不,我知道这些长度可能不对)。

这样您就可以在验证密码时始终确定要检查的版本。您可以通过擦除以该版本号开头的存储哈希来使旧算法失效。

如果您已经在生产环境中获得了散列没有版本号,只需选择一个方案,以便您可以轻松识别未版本化的散列 - 例如,使用上述冒号方案,任何散列不包含冒号的定义必须早于版本控制方案,因此可以推断为版本 0(或其他)。

【讨论】:

  • 我的第一个想法是将该哈希版本号存储在其自己的字段中。不是修辞:为什么将它包含在字符串中?
  • @Matchu:因为一个没有另一个就没用,将它们存储在同一个字段中可以确保您永远不会遇到只有一个而没有另一个的情况。
  • @Matchu 几乎是一回事,但你是对的,将其存储在单独的字段中可以更轻松地查询表以查看有多少用户已升级
  • 这会将旧的弱哈希值保留在数据库中,至少在用户下次登录之前。因此,除此之外,还应在旧哈希上使用双哈希技术(类似于 orip 的帖子,但使用盐)。
  • @Slartibartfast:除非您碰巧使用“crypt”、“md5”或“sha”,否则它基本上等同于使用 RFC 2307……那么 RFC 2307 在什么方面是一个好计划,但是不是等效的吗?它们基本上都保留了用于执行散列的算法以及散列本身。
【解决方案2】:

保护所有现有密码的好方法:使用现有哈希作为新的更好密码哈希的输入。

因此,如果您现有的哈希是直接的 MD5,并且您计划迁移到某种形式的 PBKDF2(或 bcrypt,或 scrypt),那么将您的密码哈希更改为:

PBKDF2( MD5( password ) )

您的数据库中已经有了 MD5,所以您只需对其应用 PBKDF2。

这很好的原因是 MD5 相对于其他哈希(例如 SHA-*)的弱点不会影响密码的使用。例如,它的碰撞漏洞对数字签名来说是毁灭性的,但它们don't affect password hashes。与更长的哈希值相比,MD5 的 128 位输出在一定程度上减少了哈希搜索空间,但这与密码搜索空间本身相比微不足道,密码搜索空间要小得多。

使密码哈希变得强大的原因是变慢(在 PBKDF2 中通过迭代实现)和随机的、足够长的盐 - 初始 MD5 不会对它们中的任何一个产生不利影响。

当您使用它时,也可以在密码中添加一个版本字段。

编辑:加密 StackExchange 在此方法上有一个 interesting discussion

【讨论】:

  • +1 这是我一直在寻找的,所有其他响应都需要很长时间才能完成升级,并且可能需要重置长时间未登录的用户。
  • +1 为简单起见。我的解决方案(以及几乎其他所有人的解决方案)要求所有用户都登录。
  • 是的。如果您现在有一个非常非常糟糕的密码方案,例如未加盐的 MD5,立即转移每个人可能很重要。从长远来看,它有可能变得令人困惑,但它是一个很好的短期解决方案。
  • @MicE - 抵御暴力攻击的安全性与其最薄弱的环节一样强大 - 在这种情况下是密码本身,而不是哈希。密码的范围远小于散列的范围,并且重新散列 - 尽管理论上会丢失一些范围 - 相比之下微不足道。事实上,PBKDF2 在内部对密码进行了数千次或更多次的重新哈希处理。例如,要在密码中获得相当于 128 位的熵,您需要一个包含所有可用键盘字符(包括所有特殊字符)的 20 个字符的完全随机密码。
  • 这个方案中的盐在哪里?
【解决方案3】:

等到您的用户登录(这样您就拥有明文密码),然后使用新算法对其进行哈希处理并将其保存在您的数据库中。

【讨论】:

    【解决方案4】:

    一种方法是:

    • 为新密码引入新字段
    • 当用户登录时,对照旧哈希检查密码
    • 如果正常,则使用新哈希对明文密码进行哈希处理
    • 删除旧哈希

    然后逐渐你将只有带有新哈希的密码

    【讨论】:

      【解决方案5】:

      您现在可能无法更改密码哈希方案,除非您以纯文本形式存储密码。您可以做的是在每个用户成功登录后使用更好的散列方案重新散列成员密码

      你可以试试这个:

      首先在您的成员表中添加一个新列,或者在哪个表中存储密码。

      ALTER TABLE members ADD is_pass_upgraded tinyint(1) default 0;
      

      接下来,在验证用户的代码中,添加一些额外的逻辑(我使用的是 PHP):

      <?php
      $username = $_POST['username'];
      $password = $_POST['password'];
      
      $auth_success = authenticateUser($username, $password); 
      if (!$auth_success) {
          /**
           * They entered the wrong username/password. Redirect them back
           * to  the login page.
           */
      } else {
          /**
           * Check to see if the member's password has been upgraded yet
           */
          $username = mysql_real_escape_string($username);
          $sql = "SELECT id FROM members WHERE username = '$username' AND is_pass_upgraded = 0 LIMIT 1";
          $results = mysql_query($sql);
      
          /**
           * Getting any results from the query means their password hasn't been
           * upgraded yet. We will upgrade it now.
           */
          if (mysql_num_rows($results) > 0) {
              /**
               * Generate a new password hash using your new algorithm. That's
               * what the generateNewPasswordHash() function does.
               */
              $password = generateNewPasswordHash($password);
              $password = mysql_real_escape_string($password);
      
              /**
               * Now that we have a new password hash, we'll update the member table
               * with the new password hash, and change the is_pass_upgraded flag.
               */
              $sql = "UPDATE members SET password = '$password', is_pass_upgraded = 1 WHERE username = '$username' LIMIT 1";
              mysql_query($sql);
          }
      }
      

      您的 authenticateUser() 函数需要更改为类似于以下内容:

      <?php
      function authenticateUser($username, $password)
      {
          $username = mysql_real_escape_string($username);
      
          /**
           * We need password hashes using your old system (md5 for example)
           * and your new system.
           */
          $old_password_hashed = md5($password);
          $new_password_hashed = generateBetterPasswordHash($password);
          $old_password_hashed = mysql_real_escape_string($old_password_hashed);
          $new_password_hashed = mysql_real_escape_string($new_password_hashed);
      
          $sql = "SELECT *
              FROM members
              WHERE username = '$username'
              AND
              (
                  (is_pass_upgraded = 0 AND password = '$old_password_hashed')
                  OR
                  (is_pass_upgraded = 1 AND password = '$new_password_hashed')
              )
              LIMIT 1";
          $results = mysql_query($sql);
          if (mysql_num_rows($results) > 0) {
              $row = mysql_fetch_assoc($results);
              startUserSession($row);
              return true;
          } else {
              return false;
          }
      }
      

      这种方法有利也有弊。好处是,个人会员的密码在登录后会变得更加安全。坏处是每个人的密码都不安全。

      我只会这样做 2 周。我会向我的所有成员发送一封电子邮件,并告诉他们由于网站升级,他们有 2 周的时间登录他们的帐户。如果他们未能在 2 周内登录,他们将需要使用密码恢复系统来重置密码。

      【讨论】:

      • 盐在哪里?另外,为什么有一个单独的标志字段而不是前缀(正如其他人所建议的那样)?
      【解决方案6】:

      当他们下次进行身份验证时,只需重新散列纯文本即可。哦,使用带有 base256(全字节)盐和 256 字节大小的 SHA-256。

      【讨论】:

      • SHA-256 将“快速”作为其设计目标之一。 “快速”不是密码散列的理想功能。自适应成本散列方案(例如 BCrypt 散列)更适合密码散列。
      • @Jacco 如果它对攻击者来说太重而无法为您的 Web 服务器举起它。 NIST 不会批准慢消息摘要功能。查看 PBKDF2 的 wiki 页面,它仅由一次有 1 个用户登录的应用程序使用。
      • Rook,再一次,你至少应该提到盐的使用。
      • @Steven Sudit 你应该假设我的代码或建议都没有违反 CWE。
      • Rook,你的实现是否有违规不是问题:由于遗漏,你描述的系统确实有违规。我建议你至少对随机加盐表示赞同,或许还支持 HMAC 作为混合盐的方式。
      猜你喜欢
      • 2014-11-24
      • 2012-07-07
      • 1970-01-01
      • 2011-09-22
      • 2015-10-02
      • 1970-01-01
      • 2023-04-08
      • 2019-10-22
      • 2015-11-30
      相关资源
      最近更新 更多