views:

311

answers:

7

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.

+3  A: 

Wait until your user logs in (so you have the password in plaintext), then hash it with the new algorithm & save it in your database.

Seth
Don't forget to remove the old hash once it's done.
zneak
+3  A: 

After each successful login, encrypt the password using the new method, storing in a separate field and keeping the old format the production one. Once you've got "enough" of them, you can switch over and force the remaining users to re-enter their passwords.

bemace
Ok, but you're not encrypting at all. You're hashing.
Steven Sudit
+20  A: 

One option is to make your stored hash include an algorithm version number - so you start with algorithm 0 (e.g. MD5) and store

0:ab0123fe

then when you upgrade to SHA-1, you bump the version number to 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).

Jon Skeet
My first thought would be to store that hash version number in its own field. Not rhetorical: why include it in the string?
Matchu
@Matchu: Since one is useless without the other, storing them in the same field ensures that you never run into a situation where you have one and not the other.
Amber
@Matchu it's pretty much the same thing, but you're right, storing it in a separate field would make it easier to query the table to see how many users have been upgraded
SampleJACK
@Matchu: Yes, it would make sense to store them separately. In the places I've seen this used, I've seen them stored together. Perhaps it's because it started out as a table with just two columns, and this way you can keep that structure :)
Jon Skeet
@Amber, @SampleJACK, @Jon: Thanks :)
Matchu
@SampleJACK: Since all you need to do is see how many fields *start with* a particular prefix, there's no benefit to storing that separately.
Steven Sudit
We should probably mention that the hash value should actually consist of the random salt and then the hash that uses that salt, preferably with HMAC.
Steven Sudit
+1 this is how it should be done.
Rook
This keeps the old weak hashes in the db, at least until the user logs in the next time. So using the double hash technique (similar to orip's post but with salt) on the old hashes should be used in addition to this.
CodeInChaos
Version numbers aren't a particularly good plan. Why not refer to the official way to do this that is compatible with what other people do? RFC 2307
Slartibartfast
@Slartibartfast: Unless you happen to use "crypt", "md5" or "sha", it's basically equivalent to using RFC 2307... so in what way is RFC 2307 a good plan, but an equivalent one isn't? They both basically keep the algorithm used to perform the hash along with the hash itself.
Jon Skeet
+1  A: 

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

Shiraz Bhaiji
+10  A: 

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:

PBKDF2( MD5( password ) )

You already have the MD5 in your database so all you do is apply PBKDF2 to it.

The reason this works well is that the weaknesses of MD5 vs other hashes (e.g. SHA-*) don't affect password use. 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.

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.

And while you're at it, add a version field to the passwords too.

orip
+1 This is what I was looking for, all other responses take a long time to complete the upgrade and may require resetting users that don't log in for a long time.
SampleJACK
+1 for simplicity. My solution (and just about everyone else's) requires all users log in.
mellowsoon
True. If you have a really, really bad password scheme right now, e.g. unsalted MD5, it might be important to shift everyone immediately. It has the potential to become confusing in the long term, but it's a good short-term solution.
Matchu
Imagine MD5 was a really simple hash, e.g. the sum of all bits. Then it would be really simple to get the original easy hash, and then this double hash would be useless.. Or is it only me thinking "a chain is only as strong as its weakest link"?
Marcus Johansson
@Marcus, correct for your fictional hash function but incorrect for MD5, at least with its currently-known weaknesses. If you can help it, just use the secure password scheme instead, but this is for the times when you're stuck with existing password data.
orip
Doesn't this approach actually make the hashing scheme easier to break? My understanding is that by using one hash as input for another hash limits the set of input characters for the new hash just to the characters which the first hash outputs. This means that if anyone is using stronger passwords with any special characters in them, you make these passwords less strong, as the first hash reduces them to just plain characters and digits.
MicE
@MicE - The security against brute-force attacks is as strong as its weakest link - which in this case is the passwords themselves, not the hash. The range of the passwords is much smaller than the range of the hashes, and rehashing - although theoretically losing some of the range - is insignificant in comparison. In fact, internally PBKDF2 re-hashes the password thousands of times or more. For example, to get the equivalent of 128-bits of entropy in your password you'd need a 20-character completely-random password from all the available keyboard characters, including all special characters.
orip
Where is the salt in this scheme?
Steven Sudit
-1 Your use of md5 makes this system vulnerable to collisions. Also PBKDF2 cannot be used on a production web server. Even 1,000 rounds of PBKDF2 isn't enough to make an appreciable difference in brute force. If its too heavy for an attacker to lift, its too heavy for your web server.
Rook
@orip - ah, ok. Thanks for the explanation!
MicE
@Steven - the salt and slowing-down are both part of the proper password scheme, e.g. PBKDF2, bcrypt, or scrypt
orip
@Rook - MD5 is vulnerable to collisions, but that shouldn't be a problem in a password-hashing scenario. If you have conflicting information I'd appreciate a link! Regarding "heavy lifting" - your server hashes a single password to check it, but an attacker is hashing a huge dictionary (millions, billions, or more) - that's why slowed-down hashes are effective. Hashing MD5 and even SHA-256 takes a microsecond or two on modern machines. That's slowed down by a factor of 1000 or more then the server takes a few milliseconds to check the password but the attacker has 1000 as much work to do.
orip
@orip: Yes, salt is a required input into PBKDF2, but your answer doesn't show this. Instead, it makes it look as if the password is the only input.
Steven Sudit
@Rook: We've disagreed on this before. In practice, PBKDF2 usually uses more than just 1000 iterations. These days, it's closer to 10000. The idea is to make it too heavy for the attacker to lift comfortably, which does not make it too heavy for the web server, particularly when you consider that *all* the attacker does is try to lift this, while authentication is a small part of what a server does. Some architectures defend against DOS here by moving authentication to its own farm, but throttling is usually enough.
Steven Sudit
@Steven Sudit yes we have talked about this. Now implement your 10k PBKDF2 on a production web server and then tell me how it works for you. As a hacker and as an engineer I only see a horrible design.
Rook
@orip The Oxford English Dictionary lists about 500,000 words. Your implementation does not have a salt and a PBKDF2 rainbow table can be generated, and even if it did the attacker would have the salt value. A Chosen-prefix attack could cause 2 plain texts to produce the same md5 hash and thus the same pbkdf2 hash (http://www.win.tue.nl/hashclash/ChosenPrefixCollisions/)
Rook
@orip @Steven Sudit read john skeet's post or my post and tell me that solution is better.
Rook
@Rook - in the meantime the old entries in the DB are insecurely hashed, and if an attacker gets the DB she can much more easily reverse them. Also, you'll always have users that don't re-login and their password entries will always stay insecure.
orip
@orip Then use this horrible double hash until they login again. Also you have a CWE-759 violation (http://cwe.mitre.org/data/definitions/759.html).
Rook
In some strange sense, if the main goal is to prevent the attacker from discovering the original plaintext password, wouldn't a larger number of collisions be preferable? :)
SampleJACK
@SampleJACK no, because the plain text used to produce that collision is then a valid password :)
Rook
Yes, john skeet has the right answer to this question.
Rook
@Rook: There is no John Skeet. It's Jon.
Steven Sudit
Rook you haven't explained why the weaknesses of md5 are relevant here. While PBKDF2( MD5( password ), salt, iterations) might in *theory* be weaker than PBKDF2(password, salt, iterations) I know of no attack on md5 which is relevant here. There isn't even a preimage attack on plain md5, so how should a preimage on the doublehash work?
CodeInChaos
@CodeInChaos actually I was referring to the prefixing attack. Its a matter of the weakest link in the chain. If md5 produces a collision then your mighty pbkdf2() will also produce the same value. I had a compromise where this could be used until the next login, and md5 can then be ditched. Although if a user doesn't login he probably doesn't care if his account gets hacked. Oah well.
Rook
But how is a collision attack relevant here, this isn't a digital signature or a document verification hash. Yes you can produce a collision between two passwords of your choosing. But how is that a vulnerability of a password hashing system? But to find a password which fits the hash in the db, you need to first execute a pre-image attack on PBKDF2 and then on a pre-image attack on md5. And I know of no such attack on either of them.
CodeInChaos
@CodeInChaos There are known preimage attacks against md5 (http://en.wikipedia.org/wiki/MD5#Preimage_vulnerability). PBKDF2 isn't a message digest function but rather a way in which message digests can be used there for it may or may not suffer form a preimage attack based on the underlining primitive. I am done with this conversation.
Rook
I can only find a 2^123 preimage(i.e. still impractical) against md5 and none against full round sha1. And of course a modern hashfunction such as one from the SHA-2 family should be used inside the key-derivation-function.
CodeInChaos
A: 

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
$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:

<?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. 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. If they fail to log in within 2 weeks they'll need to use the password recovery system to reset their password.

mellowsoon
Where's the salt? Also, why have a separate flag field instead of a prefix (as others have suggested)?
Steven Sudit
A: 

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.

Rook
SHA-256 has 'fast' as one of it's design goals. 'fast' is not a desirable feature for password hashing. Adaptable cost hashing schemes (BCrypt hash for example) are better suited for password hashing.
Jacco
@Jacco If its too heavy for an attacker to lift its too heavy for your web server. No slow message digest function will be approved by NIST. Look at the wiki page for PBKDF2, it is only used by applications where 1 user is logging in at a time.
Rook
Rook, once again, you should have at least mentioned the use of salt.
Steven Sudit
@Steven Sudit you should assume that none of my code or suggestions violates a CWE.
Rook
Rook, whether your implementation has violations is not the issue: due to omission, the system you describe does have violations. I'm suggesting that you at least nod towards random salting, and perhaps towards HMAC as the way to mix that salt in.
Steven Sudit
@Steven Sudit there is no message to authenticate I don't see why an HMAC would be anything other than wasted resources. Further more your in violation of the majority of the CWE-255 family, by omission. Your just jealous of my hacking skills and spelling ablates and grammar ablates.
Rook
The same reason we always use HMAC's for: to avoid extension attacks.
Steven Sudit
@Steven Sudit I think it is a good solution to use an HMAC as a salting if scheme for md5 or sha1, but then again a prefixed salt would also do the trick.
Rook
@Rook, As far as I know, BCrypt hash is the recommended hashing scheme for hashing passwords at this moment (owasp, openwall). The heavy lifting is a subtle issue, it will slow down your authentication process, but nothing to costly (say .1 of a second), the cost will add up however if you run for example a dictionary attack against the system. the the half second means the number of tries/second rapidly drops. There are papers out there describing the benefits better than I can. I'm quite sure you would find them interesting to read if you haven't done so already.
Jacco
@Jacco: I've been down this road with Rook before. Have fun.
Steven Sudit
@Jacco blowfish is a depreciated primitive, and twofish replaced it more than 10 years ago, no serous cryptographer will recommend its use. Not as a block cipher, not as a message digest. I'll say it again, if its too heavy for the attacker is too heavy for your server. PBKDF2 is only used in places where there is a single user authenticating, then you can burn the kind of resources to slow down the attacker. Also message digest functions are much much faster than that, try and `openssl speed` sometime.
Rook
@Steven Sudit hehe :) I'm a dangerous mix of skill and stubbornness. Very similar to the mix of exploit writer and security engineer.
Rook
@Rook, unfortunately, the comments on SO are ill suited for a full blown discussion :)
Jacco