简体   繁体   English

密码哈希 - 如何升级?

[英]Password hashing - how to upgrade?

There's plenty of discussion on the best algorithm - but what if you're already in production? 关于最佳算法的讨论很多 - 但如果你已经在制作中呢? How do you upgrade without having to reset on the user? 如何升级而无需重置用户?


EDIT/DISCLAIMER: Although I originally wanted a "quick fix" solution and chose orip's response, I must concede that if security in your application is important enough to be even bothering with this issue, then a quick fix is the wrong mentality and his proposed solution is probably inadequate. 编辑/免责声明:虽然我最初想要一个“快速修复”解决方案并选择了orip的回复,但我必须承认,如果您的应用程序中的安全性非常重要,甚至可以解决这个问题,那么快速修复是错误的心态和他的提议解决方案可能不充分。

One option is to make your stored hash include an algorithm version number - so you start with algorithm 0 (eg MD5) and store 一种选择是使存储的哈希包含算法版本号 - 因此您从算法0(例如MD5)开始并存储

0:ab0123fe

then when you upgrade to SHA-1, you bump the version number to 1: 然后当您升级到SHA-1时,将版本号更改为1:

1:babababa192df1312

(no, I know these lengths probably aren't right). (不,我知道这些长度可能不对)。

That way you can always tell which version to check against when validating a password. 这样,您可以随时确定在验证密码时要检查的版本。 You can invalidate old algorithms just by wiping stored hashes which start with that version number. 您可以通过擦除以该版本号开头的存储哈希值来使旧算法无效。

If you've already got hashes in production without a version number, just choose a scheme such that you can easily recognise unversioned hashes - for example, using the above scheme of a colon, any hash which doesn't contain a colon must by definition predate the versioning scheme, so can be inferred to be version 0 (or whatever). 如果您已经在没有版本号的情况下生成哈希,只需选择一个方案,以便您可以轻松识别未版本化的哈希 - 例如,使用上面的冒号方案,任何不包含冒号的哈希必须按定义在版本控制方案之前,因此可以推断为版本0(或其他)。

A cool way to secure all the existing passwords: use the existing hash as the input for the new, and better, password hash. 保护所有现有密码的一种很酷的方法:使用现有的哈希作为新密码哈希的输入。

So if your existing hashes are straight MD5s, and you plan on moving to some form of PBKDF2 (or bcrypt , or scrypt ), then change your password hash to: 因此,如果您现有的哈希值是直接的MD5,并且您计划转移到某种形式的PBKDF2 (或bcryptscrypt ),那么请将您的密码哈希值更改为:

PBKDF2( MD5( password ) )

You already have the MD5 in your database so all you do is apply PBKDF2 to it. 您已经在数据库中拥有MD5,所以您只需将PBKDF2应用于它。

The reason this works well is that the weaknesses of MD5 vs other hashes (eg SHA-*) don't affect password use. 这种方法运行良好的原因是MD5与其他哈希(例如SHA- *)的弱点不会影响密码使用。 For example, its collision vulnerabilities are devastating for digital signatures but they don't affect password hashes . 例如,它的冲突漏洞对于数字签名是毁灭性的,但它们不会影响密码哈希 Compared to longer hashes MD5 reduces the hash search-space somewhat with its 128-bit output, but this is insignificant compared to the password search space itself which is much much smaller. 与较长的哈希相比,MD5通过其128位输出稍微减少了哈希搜索空间,但与密码搜索空间本身相比,这是微不足道的。

What makes a password hash strong is slowing down (achieved in PBKDF2 by iterations) and a random, long-enough salt - the initial MD5 doesn't adversely affect either of them. 密码散列强大的原因是减慢(通过迭代在PBKDF2中实现)和随机的,足够长的盐 - 初始MD5不会对它们中的任何一个产生负面影响。

And while you're at it, add a version field to the passwords too. 当你正在使用它时,也要在密码中添加一个版本字段。

EDIT : The cryptography StackExchange has an interesting discussion on this method. 编辑 :加密StackExchange对此方法进行了有趣的讨论

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

One way to do it is to: 一种方法是:

  • Introduce new field for new password 为新密码引入新字段
  • When the user logs in check the password against the old hash 当用户登录时,检查旧哈希的密码
  • If OK, hash the clear text password with the new hash 如果正常,则使用新哈希散列明文密码
  • Remove the old hash 删除旧哈希

Then gradually you will have only passwords with the new hash 然后渐渐地你将只有新哈希的密码

You probably can't change the password hashing scheme now, unless you're storing passwords in plain text. 您现在可能无法更改密码哈希方案,除非您以纯文本格式存储密码。 What you can do is re-hash the member passwords using a better hashing scheme after each user has successfully logged in. 您可以做的是每个用户成功登录使用更好的散列方案重新散列成员密码。

You can try this: 你可以试试这个:

First add a new column to your members table, or which ever table stores passwords. 首先在您的成员表中添加一个新列,或者哪个表存储密码。

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

Next, in your code that authenticates users, add some additional logic (I'm using PHP): 接下来,在对用户进行身份验证的代码中,添加一些额外的逻辑(我正在使用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);
    }
}

Your authenticateUser() function would need to be changed to something similar to this: 您的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;
    }
}

There's upsides and downsides to this approach. 这种方法有好处和缺点。 On the upsides, an individual member's password becomes more secure after they've logged in. The downside is everyone's passwords aren't secured. 从好处来看,个人会员的密码在登录后变得更加安全。缺点是每个人的密码都不安全。

I'd only do this for maybe 2 weeks. 我只会这样做2周。 I'd send an email to all my members, and tell them they have 2 weeks to log into their account because of site upgrades. 我会向所有会员发送电子邮件,告诉他们由于网站升级,他们有2周的时间登录他们的帐户。 If they fail to log in within 2 weeks they'll need to use the password recovery system to reset their password. 如果他们未能在2周内登录,则需要使用密码恢复系统重置密码。

Just re-hash the plain text when they authenticate the next time. 只需在下次验证时对明文进行重新哈希处理。 Oah and use SHA-256 with a salt of base256 (full byte) and 256 bytes in size. 哦,使用SHA-256和base256(全字节)的盐,大小为256字节。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM