Password hashes were the hobby horse that first convinced me to get involved in MediaWiki programming, back in 2003. Here's my outraged post:
http://marc.info/?l=wikitech-l&m=104904771815534&w=2
The relevant code was the first code I wrote for MediaWiki. Looking back now, my immaturity as a programmer at the time shows through, and if someone tried to submit something similarly naive today, I'd hope that we'd have a few senior developers around (e.g. me) to point out its problems and send it back for a rewrite.
The idea was to use md5($userId.'-'.md5($password)) as a hash. This has a number of problems:
* There's no way to tell whether a hash is in the old style or the new style. So migration is difficult and error-prone. * The migration issues will be repeated every time we have to change hash functions, which might be every decade or so due to improving cryptanalysis and computing power. * It links the password to the user ID, making it impossible to transfer passwords from one user to another. * The salt always has the same value on single-user wikis (1), so it's insecure.
The obvious solution is to attach the salt to the hash. Luckily user_password has been a tinyblob since the dawn of time, so we're not space-constrained. We could store the salt in a different field (like what's done in CentralAuth), but that wouldn't solve quite so many of the problems listed above.
So I've committed a hash format change, so that salted hashes look like this:
:B:a2a5c3b9:44ebabb085ce78dd20c2d59c51e4080c
That is, a type "B" hash, with salt "a2a5c3b9" and MD5 hash "44ebabb085ce78dd20c2d59c51e4080c". The way the salt and the password are mixed together is the same as in the old system, so you can migrate to this password format simply by prepending :B: and the user ID.
Unsalted hashes look like this:
:A:d41d8cd98f00b204e9800998ecf8427e
Password hashes will be migrated to the :A: or :B: style on upgrade, and after that, you'll be able to switch $wgPasswordSalt on and off at will, and all passwords will continue to work. Before upgrade, the software will understand the old-style hashes, but it requires $wgPasswordSalt to be set correctly.
When we eventually migrate from MD5 to Tiger/192 or whatever, we can introduce a type "C" hash and then convert the old hashes at our leisure.
This change has been on my mind for a while, but the immediate motivation is bug 14330.
-- Tim Starling
Tim Starling wrote:
So I've committed a hash format change, so that salted hashes look like this:
:B:a2a5c3b9:44ebabb085ce78dd20c2d59c51e4080c
That is, a type "B" hash, with salt "a2a5c3b9" and MD5 hash "44ebabb085ce78dd20c2d59c51e4080c". The way the salt and the password are mixed together is the same as in the old system, so you can migrate to this password format simply by prepending :B: and the user ID.
Unsalted hashes look like this:
:A:d41d8cd98f00b204e9800998ecf8427e
Password hashes will be migrated to the :A: or :B: style on upgrade, and after that, you'll be able to switch $wgPasswordSalt on and off at will, and all passwords will continue to work. Before upgrade, the software will understand the old-style hashes, but it requires $wgPasswordSalt to be set correctly.
When we eventually migrate from MD5 to Tiger/192 or whatever, we can introduce a type "C" hash and then convert the old hashes at our leisure.
While we're at it, why not introduce some form of adjustable key strengthening:
http://en.wikipedia.org/wiki/Key_strengthening
For example, we could store the hashes like this:
:C:65000:e2a6fb36:37860b819a5e8e71538370316f06a8db
where 65000 is the number of times to iterate the statement $hash = md5($hash . $salt . $password) to compute the hash (starting with $hash = ""). The value for new passwords would be set through a config variable (e.g. $wgPasswordIterations), with old records updated to use the current iteration count on login.
Ilmari Karonen wrote:
Tim Starling wrote:
So I've committed a hash format change, so that salted hashes look like this:
:B:a2a5c3b9:44ebabb085ce78dd20c2d59c51e4080c
That is, a type "B" hash, with salt "a2a5c3b9" and MD5 hash "44ebabb085ce78dd20c2d59c51e4080c". The way the salt and the password are mixed together is the same as in the old system, so you can migrate to this password format simply by prepending :B: and the user ID.
Unsalted hashes look like this:
:A:d41d8cd98f00b204e9800998ecf8427e
Password hashes will be migrated to the :A: or :B: style on upgrade, and after that, you'll be able to switch $wgPasswordSalt on and off at will, and all passwords will continue to work. Before upgrade, the software will understand the old-style hashes, but it requires $wgPasswordSalt to be set correctly.
When we eventually migrate from MD5 to Tiger/192 or whatever, we can introduce a type "C" hash and then convert the old hashes at our leisure.
While we're at it, why not introduce some form of adjustable key strengthening:
http://en.wikipedia.org/wiki/Key_strengthening
For example, we could store the hashes like this:
:C:65000:e2a6fb36:37860b819a5e8e71538370316f06a8db
where 65000 is the number of times to iterate the statement $hash = md5($hash . $salt . $password) to compute the hash (starting with $hash = ""). The value for new passwords would be set through a config variable (e.g. $wgPasswordIterations), with old records updated to use the current iteration count on login.
Well, I did consider it, back in 2003, the tradeoff of course is speed. Because we're working in PHP, an attacker could do the same operation several times faster than we could, using C/C++. Serving web pages is meant to be fast, with lots of concurrent requests, and there might be a need to do batch operations. There's probably an argument for stretching it out to a few milliseconds, but with 65000 iterations I get 130ms on zwinger which is probably going a bit too far.
-- Tim Starling
On Thu, Jun 5, 2008 at 12:51 PM, Tim Starling tstarling@wikimedia.org wrote:
Well, I did consider it, back in 2003, the tradeoff of course is speed. Because we're working in PHP, an attacker could do the same operation several times faster than we could, using C/C++. Serving web pages is meant to be fast, with lots of concurrent requests, and there might be a need to do batch operations. There's probably an argument for stretching it out to a few milliseconds, but with 65000 iterations I get 130ms on zwinger which is probably going a bit too far.
While you are taking requests:
JS SRP please: http://code.google.com/p/clipperz/source/browse/trunk/crypto.library/src/js/...
(http://en.wikipedia.org/wiki/Secure_remote_password_protocol)
:)
On Thu, Jun 5, 2008 at 1:07 PM, Gregory Maxwell gmaxwell@gmail.com wrote:
While you are taking requests:
JS SRP please: http://code.google.com/p/clipperz/source/browse/trunk/crypto.library/src/js/...
(http://en.wikipedia.org/wiki/Secure_remote_password_protocol)
That implementation is AGPL v3, seemingly. What are the issues with combining that with MediaWiki? The AGPL v3 is compatible with GPL v3, but not GPL v2.
-----Original Message----- From: wikitech-l-bounces@lists.wikimedia.org [mailto:wikitech-l-bounces@lists.wikimedia.org] On Behalf Of Tim Starling Sent: 05 June 2008 17:52 To: wikitech-l@lists.wikimedia.org Subject: Re: [Wikitech-l] Password hash format
Ilmari Karonen wrote:
Tim Starling wrote:
So I've committed a hash format change, so that salted
hashes look like this:
:B:a2a5c3b9:44ebabb085ce78dd20c2d59c51e4080c
That is, a type "B" hash, with salt "a2a5c3b9" and MD5 hash "44ebabb085ce78dd20c2d59c51e4080c". The way the salt and
the password
are mixed together is the same as in the old system, so you can migrate to this password format simply by prepending :B:
and the user ID.
Unsalted hashes look like this:
:A:d41d8cd98f00b204e9800998ecf8427e
Password hashes will be migrated to the :A: or :B: style
on upgrade,
and after that, you'll be able to switch $wgPasswordSalt
on and off
at will, and all passwords will continue to work. Before
upgrade, the
software will understand the old-style hashes, but it requires $wgPasswordSalt to be set correctly.
When we eventually migrate from MD5 to Tiger/192 or
whatever, we can
introduce a type "C" hash and then convert the old hashes
at our leisure.
While we're at it, why not introduce some form of adjustable key strengthening:
http://en.wikipedia.org/wiki/Key_strengthening
For example, we could store the hashes like this:
:C:65000:e2a6fb36:37860b819a5e8e71538370316f06a8db
where 65000 is the number of times to iterate the statement $hash = md5($hash . $salt . $password) to compute the hash (starting with $hash = ""). The value for new passwords would be set through a config variable (e.g. $wgPasswordIterations), with old
records updated
to use the current iteration count on login.
Well, I did consider it, back in 2003, the tradeoff of course is speed. Because we're working in PHP, an attacker could do the same operation several times faster than we could, using C/C++. Serving web pages is meant to be fast, with lots of concurrent requests, and there might be a need to do batch operations. There's probably an argument for stretching it out to a few milliseconds, but with 65000 iterations I get 130ms on zwinger which is probably going a bit too far.
-- Tim Starling
Hi, Quite a few projects seem to be standardising on http://www.openwall.com/phpass/
It has stretching, and the number of iterations are stored within the hash, so the strength of the hashes can be upgraded over time.
Jared
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Tim Starling wrote:
Password hashes were the hobby horse that first convinced me to get involved in MediaWiki programming, back in 2003. Here's my outraged post:
Ahh, the good old days... :D
Password hashes will be migrated to the :A: or :B: style on upgrade, and after that, you'll be able to switch $wgPasswordSalt on and off at will, and all passwords will continue to work. Before upgrade, the software will understand the old-style hashes, but it requires $wgPasswordSalt to be set correctly.
When we eventually migrate from MD5 to Tiger/192 or whatever, we can introduce a type "C" hash and then convert the old hashes at our leisure.
Awesome!
This change has been on my mind for a while, but the immediate motivation is bug 14330.
Indeed; this nicely solves the problem of migrating passwords across wikis... applicable to other potential "merge multiple wiki" sort of tasks as well, if needed.
- -- brion
Regarding the specific implementation: http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/User.php?r1=...
comparePasswords() will treat future methods as Old-Style. I think it would be preferable to determine as old-style only if $type[0] != ':' and let hooks be able to handle unknown cases failing to "don't know this codification" if noone is able to detect it.
I don't like that comparePasswords and crypt are doing the same thing (knowing the intrinsecs of the hashing formats).
Same with oldCrypt, which should be a special case of crypt.
Expressed into code, it could be something like this:
static function cryptPassword( $password, $salt = false, $method = null, $showMethod = true) { $hash = ""; if ($method === null) $efectiveMethod = $wgPasswordSalt ? "B" : "A"; else $efectiveMethod = $method;
switch ($efectiveMethod) { case "A": $salt = false; $hash = md5( $password ); break; case "B": if ( $salt === false ) $salt = substr( wfGenerateToken(), 0, 8 ); $hash = md5( $salt.'-'.md5( $password ) ); break; default: if (wfRunHooks('cryptPassword', array(&$hash, $password, $salt, $method))) throw new MWException("I have no idea how to handle password method $efectiveMethod!"); }
if ($showMethod) { if ($salt !== false) $hash = "$salt:$hash"; $hash = ":$efectiveMethod:$hash"; } return $hash; }
static function comparePasswords( $hash, $password, $userId = false ) { $A = explode( ':', $hash, 4 ); if (count(A) < 4) return cryptPassword($password, $userId, null, false) == $A[0]; else return cryptPassword($password, $A[2], $A[1]) == $A[3]; }
Opinions?
wikitech-l@lists.wikimedia.org