When a user attempts to log in, you hash the password they submitted with whatever's in that user's `hashing_scheme`. If the password was incorrect, fail early.
If the password was correct, and this user's `hashing_scheme` value is different to the application's `current_hashing_scheme`, you rehash the user's password (that you got via the POST request or otherwise) with the new scheme, update the `password_hash` and `hashing_scheme` value in the database.
This is just one way to do it, which is how Django performs hashing scheme migrations IIRC.
new_hashing_scheme(old_hashing_scheme(password))
Not as elegant, but the benefit is you can immediately move everyone to a new scheme if you need to for some reason.Once the user has logged in successfully, you can replace new_hash(old_hash(password)) with simply new_hash(password).
This is less useful for optimistic upgrades like bcrypt->argon2i, but is absolutely critical if you find yourself taking over responsibility for a database that has a password column full of, say, MD5s.
Waiting for each and every user to log in to upgrade those vulnerable hashes is suicidal.
For example, Django currently stores its passwords in a single database column as
<algorithm>$<iterations>$<salt>$<hash>
So suppose we generalize that to a chain of one or more hash algorithms, with each output fed in as the password to the next, resulting in a final hash: <algorithm>$<iterations>$<salt>|<algorithm>$<iterations>$<salt>|$<hash>
For example if you had a user who logged in way back in unsalted md5 days and never logged in again, you might end up with a chain like this: md5|sha256$<salt>|bcrypt$<iterations>$<salt>|$<hash>
... and then to upgrade to argon you would grab that string, shove the final hash through argon(), and write back: md5|sha256$<salt>|bcrypt$<iterations>$<salt>|argon$<iterations>$<salt>|$<hash>
For new users, or if the old user finally came back and logged in with their correct password, you could then take the opportunity to write back a simplified version: argon$<iterations>$<salt>|$<hash>
Iterations could be calibrated differently depending on the chain, if the earlier steps take enough time to matter.As far as I can tell this is the best possible service you can offer to the users whose long-ago passwords you're storing, short of deleting their passwords entirely. Throwing away the earlier version and replacing it with the argon-wrapped version can't hurt them, and will almost certainly help.
(Usual caveats apply about not doing things that add complexity if you don't need it.)
http://php.net/manual/en/function.password-needs-rehash.php (PHP >= 5.5)
Are you sure? And if so, can you convince me as to why you are so sure?
Wrapped hashes could have other side effects for probing non-hacked sites -- for example, attackers could probably figure out which chain a user has based on timing, which would let them narrow down a bit when the user last logged in. Hash chains could also theoretically reduce the total output space for the final hash, making it easier to brute force a password through the login form of a non-hacked site. I don't consider either of those likely to matter, but you might.
* Fully expire the accounts (and send emails asking them to reset their password)
* If they successfully login to their account and you have the plain text password, you can re-hash that using argon2i.
* Have them reset their password on next sign in before allowing them to do anything.
* Let the user know their account has less security than it could, and they can upgrade the security by changing their password.
Afaik, there is nothing you can do to upgrade their passwords, modulo argon2'ing the result of another hash function - I am simply too unclear on the safety of this approach so I don't do it.
I.e. store the result of `argon(bcrypt(md5(password)))`
Any security drawbacks to this?
https://security.stackexchange.com/questions/93730/why-not-m... [0]
The top voted answer has a comment that's spot on:
I would offer the opposite argument: if one uses a single hashing function which has a 0.1% chance of having a discoverable weakness that would allow an attacker to speed it up by a factor of a million, there will be a one-in-a-thousand chance that an attacker will be able to gain a million-fold speedup. If one used three independent functions, each of which had a 0.1% chance of allowing such a breakthrough, there would be a 0.3% chance of an attacker being able to achieve a 33% speedup, a 0.0003% chance of an attacker getting a 66% speedup, and only a 0.0000001% chance of an attacker... – supercat Jul 13 '15 at 17:21
...getting a million-fold speedup. I would consider the possibility of an attacker getting a 33% speedup as inconsequential compared to the reduction in the probability of the attacker getting a 70%-or-better speedup. – supercat Jul 13 '15 at 17:22
In short: You lose on average (but who care?) but you reduce the risk of catastrophic failure.