Discover the best of the web!
Learn more about Digg by taking the tour.
Password hashing using PHP and MySQL
phpsec.org — This article will explain what a hash is, why you want to use them instead of storing real passwords in your applications, and give you some examples of how to implement password hashing in PHP and MySQL.
- 871 diggs
- digg it
- Stumpokapow, on 10/12/2007, -1/+12The article is pretty good, but it fails to actually explain anything about MD5 or SHA1. What's the difference between the two? Which should I use? Are there other encryption algorithms? These are important questions. If you check the PHP documentation for the md5 function ( http://www.php.net/md5 ), you'll see a long and lively debate about whether or not MD5 is adequate.
I've been commenting this on most of the programming tutorials for the last few days. The tutorial is fine in that it tells you how to do what you want to do, but the best way to teach programming is to explain all of the "Why?" questions. Learning syntax is easy. Anyone can use the PHP docs. Learning the theory behind why you do something and all the details is tougher. Tutorials like this have produced a generation of people who can write PHP code but have no idea what the hell makes that code work or any of the theory behind it. This has resulted in largely insecure, inefficient, and buddy code that no one can fix because they don't have the skills.
I digg'd for a good tutorial, but this really doesn't cover the issue fully or enough to use.- phpirate, on 10/12/2007, -6/+4The difference between md5 and sha1 is that apparently sha1 is easier to attack. md5 isn't flawless but its more difficult to break as far as I know.
One thing I'd suggest is adding a salt to the password (it being random symbols letters and numbers) for that extra bit of security. Simply you store the password as md5($salt.$pass) for the best results. Heck, you can even md5 the password and then md5 the salt pass hash. - Stumpokapow, on 10/12/2007, -2/+6Sorry, I guess I wasn't clear; It wasn't that I needed an answer to those questions, it was that the tutorial needed an answer to those questions. The tutorial vaguely talks about salts, but even then--it really just says "DO THIS IT WORKS" instead of explaining why you should do it. That's a problem with a lot of code tutorials, and it really hurts the quality of code produced.
- TenebrousX, on 10/12/2007, -1/+5I've heard the opposite, that SHA1 is more secure
- coding, on 10/12/2007, -1/+9Between MD5 and SHA1 the question of "how secure" they are isn't an issue at all in password storage. They are both extremely vulnerable if you don't salt your data. For example there are databases out there with literally billions of precomputed sha1 and md5 hashed passwords, so discussing the security of one or the other in the realm of passwords is insignificant. What gets past these databases are salted + hashed passwords... Now again this is only an issue if your database is freely accessed as well.
- merreborn, on 10/12/2007, -1/+1Both MD5 and SHA1 are much better than storing passwords in your db plaintext anyway. That's really the only goal of the article. It really doesn't matter which you go with -- either is hundreds of times more secure than no hashing at all.
- stardustwd, on 10/12/2007, -3/+2phpirate: SHA1 is a stronger encryption than MD5 but yes you are right we should be adding salt's to our passwords. We should also be trying to use an even stronger encryption such as SHA256 or SHA512 where possible.
- natmaster, on 10/12/2007, -3/+2MD5 has a reputation of *frequent collisions. I.E. multiple words mapping to the same hash. This is bad because I can type in blah, and get authenticated for a password that was really smidelgrump.
* frequent is a relative term here....doesn't actually happen that often in the big scheme of things. - phpirate, on 10/12/2007, -1/+1http://en.wikipedia.org/wiki/Sha1 "Attacks have been found for both SHA-0 and SHA-1."
- ciphergoth, on 10/12/2007, -1/+0"MD5 has a reputation of *frequent collisions. I.E. multiple words mapping to the same hash."
Uh, no. The only collisions known for MD5 are those deliberately constructed by cryptanalysts using the Wang attack or variants thereof. No collisions have ever arisen "by accident".
- phpirate, on 10/12/2007, -6/+4The difference between md5 and sha1 is that apparently sha1 is easier to attack. md5 isn't flawless but its more difficult to break as far as I know.
- jamey, on 10/12/2007, -0/+1"They are both extremely vulnerable if you don't salt your data."
I didn't realize it was that bad. The likelihood of someone obtaining raw data from the database is low, in most cases. Even then, you would have to be quite determined to spend time and processing power computing thousands of hashes until you crack the password you need.
I agree, it is a useful tutorial but it should have concentrated on the concepts behind it rather than presenting mainly coding methods.- timmarhy, on 10/12/2007, -0/+5don't bet on them being unable to obtain hashs.
and you can buy rainbow crack tables with all combinations up to like 7 characters precomputed for you, which makes finding out what that password is VERY easy.
it's a much better approch to use the databases authentication methods.
also just to nit pick, but hashing is NOT encryption. things like rainbow tables do not work against something encrypted - merreborn, on 10/12/2007, -0/+2Yeah, read up on "rainbow tables". Precomputation is very feasible these days -- and widespread. Of course, if you've chosen a good password to begin with, then it's not going to be in your average rainbow table anyway.
Salt. Always. Hell, unix has been salting the passwd file for decades now. 'Bout time the rest of the world catches up :p
- timmarhy, on 10/12/2007, -0/+5don't bet on them being unable to obtain hashs.
- Zhay, on 10/12/2007, -0/+1Regardless, if you keep your database safe by preventing sql injections and the whole nine yards, this would never be a problem. Granted, I don't suggest not encrypting user passwords. I personally use a combination of md5, sha1, and base64 all together (doesn't hinder loading times much at all). That way, if a cracker sees what he or she thinks is an md5 hash, it really is a combination of md5, sha1, and base64. Do note, however, that base64 does not render a hash and should not be treated as such. The article is good in that it keeps new developers aware of potential security issues.
Also, the issue of security between md5 and sha1 is mainly an issue of brute force. If you have a rainbow table of equal size, each hash is secure as the other. The idea is that they are *hashes*. Thus, neither can be reversed to the original text. Therefore, it is necessary to brute force the passwords.- merreborn, on 10/12/2007, -1/+2All the database security in the world won't protect you from a malicious employee with access.
- Zhay, on 10/12/2007, -2/+1That's why I employ no one ;)
- stardustwd, on 10/12/2007, -1/+2I could be wrong but i have heard that hashing you're passwords with more than one hashing technique e.g. MD5 and SHA1 makes them more prone to hacking.
- Zhay, on 10/12/2007, -0/+1Interesting. How would it make it less secure if I encode ten different ways?
- cuenca, on 10/12/2007, -0/+1Let's say you MD5 your password. An attacker has to find one password with the same MD5.
If you SHA1 your password, the attacker has to find one password with the same SHA1.
Now, if you SHA1(MD5(password)), then the attacker has to find a password with the same MD5 as yours OR a password with the same SHA1(MD5) of yours.
So if you combine SHA1 with MD5 the attacker can use any failure in either MD5 or SHA1 to break in (ie, it's easier to crack than using only md5 or sha1).
- tempusrob, on 10/12/2007, -0/+1SHA256 FTW:
http://forums.devnetwork.net/viewtopic.php?t=32334- cavicster, on 10/12/2007, -0/+1Thanks for the link, I didn't know about that. PHP 5.1.2 is bundled with a new hash extension which supports many algorithms.
http://us3.php.net/manual/en/function.hash-algos.php
- cavicster, on 10/12/2007, -0/+1Thanks for the link, I didn't know about that. PHP 5.1.2 is bundled with a new hash extension which supports many algorithms.
- jellygraph, on 10/12/2007, -0/+1im sorry, but any php developer worth his salt would have known enough to know what MD5 and SHA1 is and to use it, instead of storing pure text passwords... I recommend you fire the bunch who don't... they are likely to delete your servers /etc/ folder... and other such hazards
- timmarhy, on 10/12/2007, -1/+5i think this tutorial is aimed that a hobbiest who is doing this in their spare time.
- digi7al64, on 10/12/2007, -1/+0the problem with the script presented here is that it is still expoiltable. For instance lets examine it a little further,
>> $sql = 'SELECT username FROM user WHERE username = ? AND passwordHash = ?';
>> $result = $db->query($sql, array($_POST['username'], $passwordHash));
If i where to inject ' this would return an error and tell me the column name where the error was produced, in this case 'username'
Then i could use this
un: ' or 1=1 and username like %admin%--
which would result in the following query
>> SELECT username FROM user WHERE username = '' or 1=1 and username like %admin%--
Then assuming there is user called admin it would then validate as true and we now have a recset and hopefully only one record returned which would then validate the rowcount check as well.
Therefore you need to hash the username as well.- Zhay, on 10/12/2007, -0/+1Or just use mysql_real_escape_string().
- davetufts, on 10/12/2007, -0/+2digi7al64 said:
> If i where to inject ' this would return an error and tell me the
> column name where the error was produced, in this case 'username'
He's using the PEAR db (or mDB2, or whatever they call it now) class. It automatically escapes special characters.
Also, nothing in his code echo's back the mysql_error() message. Neither does, the PEAR class, so you'd never see the raw database error.
But your point is well taken. Programmers should think about quoting and escaping user input before sending it to another source.
- numlock, on 10/12/2007, -0/+1A simple alternative to storing a salt with the password as mentioned in the article, is to do something like:
$hash = md5("somesecret".$username.$pwd);
Assuming you have unique user names, then this will generate a different hash regardless of whether the same password is used. - marcan, on 10/12/2007, -0/+4Just *please* don't do what php-nuke does: use the MD5s for authentication in the cookie. This effectively turns the MD5 into the password, and then you've got all your problems again (it makes the MD5 sufficient for authentication). Use a salt/challenge/response system *please*.
I once was lurking around the nuke code (some greps revealed a TON of unescaped SQL variables - can't the devs just run grep on the code?) and found this big SQL injection vulnerability on the main freaking authentication function. One would've thought they would at least look through *that* (IIRC it was like 15 lines of code). Planting SQL into the cookie = instant access to the database, without even having to use weird SQL statements with UNION and the like - the usual ' OR junk worked.
I wrote some test code to get the MD5s from the database (it was kind of fun - the SQL had no data output, so I couldn't just print the MD5 to the screen. But I set the SQL up to check each character of the MD5 against all possibilities 0-9a-f, and use a benchmark function to stall the server if a match was found. So the time the response took to arrive contained the information.) and got the MD5. It's just sad that a potential cracker wouldn't even have to run it through johntheripper to get the cleartext password (since inevitably the admin would have a weak password - I did check this one, and it happened to be 00000000. Nice.) Just putting the MD5 into the cookie gave you instant access. sigh. For the record, I only used this as a test against a friend's website with permission, and didn't release the code. It's not exactly a hard to find bug though.
Oh, and those "security systems" for nuke and similar are a joke. All they do is scan for keywords on the URL. Most are vulnerable to a darn simple URLEncoding of the SQL, and all are vulnerable to simple stuff like inserting bogus comments and string concatenation and using char().
This might have been an old version of nuke. But even if they eventually fixed it, it's totally unacceptable.
</rant> - cosmotic, on 10/12/2007, -1/+2I dont understand, the salt is still stored in plain text in the database.
- christopherk, on 10/12/2007, -0/+1The salt is to make it too expensive to keep a database of all possible* password hashes, which could be used to look up the clear password given its hash.
* well, enough to be truly useful. - vann, on 10/12/2007, -0/+1Let's say the hacker gets ahold of the password database. The hashes stored in the database are the hash of salt+password, which means any pre-generated list is useless. The hacker will have to recompute his list for *every* user, using that user's salt.
- christopherk, on 10/12/2007, -0/+1The salt is to make it too expensive to keep a database of all possible* password hashes, which could be used to look up the clear password given its hash.
- op12, on 10/12/2007, -0/+1What happens if you need the plaintext password for purposes of a "Forgot your password?" page which is so common on major websites? How can you retrieve that information without ever storing the password in plaintext?
- sgent, on 10/12/2007, -0/+1You can't -- you can just send the user an email allowing them to reset their password. This is considered better practice and more secure for the user (since system admins won't be able to look at their password either).
- bairy, on 10/12/2007, -0/+1As stated above you can't retrieve, but you can have a password reset form emailed to you and make another.
- Niten, on 10/12/2007, -0/+1Perfect timing; I just started writing my first real LAMP app, and this is exactly what I was looking for. Thanks, vagabond0101!
- egingras, on 10/12/2007, -1/+0It's really easy to avoid having your password cracked.
9+ characters
INCLUDING upper and lowercase AND numbers...
Adding symbols would be a plus - aphexcoil, on 10/12/2007, -0/+0"What's your password?"
"Password"
"Yeah, what is it?"
"Password!"
"YES!! What's your freakin' password?"
"I just told you!"
(smacks face) - ciphergoth, on 10/12/2007, -0/+2There seems to be some confusion about the security of these various approaches; I'm a cryptographer so hopefully I can shed some light on this.
1) There are attacks on both MD5 and SHA-1; the attacks on MD5 are practical in under an hour on an ordinary PC, while the attacks on SHA-1 are so compute-intensive that they're currently out of reach. These attacks are collision attacks, though, and don't say very much about security for this purpose. If you're using a password system based on MD5 or SHA-1, don't panic, it's probably fine (though try to migrate away from MD5 when you can). However, if you're building a new system, you might as well use SHA-256 if you have it.
2) The advice in the article isn't bad, but there's one extra step you can take to greatly improve your password security: hash multiple times. The more times you hash the password, the harder a dictionary attack will be, because the attacker will have to repeat all your hashes to check each password. See
http://www.schneier.com/paper-low-entropy.html
3) If you want to store a cookie that verifies that the user has the right password, store the penultimate hash. With this scheme, you can quickly verify the cookie - just hash it once, and compare to your database password.
4) Better than all of these schemes are true secure password protocols. If you have control over the client (you're not writing a web-based app) look at things like SRP:
http://srp.stanford.edu/- ciphergoth, on 10/12/2007, -0/+0Note that if you use point 3, you should treat the salt like a secret key: ie it should be a random, sufficiently large, and secet. Otherwise the attacker can do the lots of hashes required by step 2 on their own hardware, potentially making their lives easier.
- cope, on 10/12/2007, -0/+1i use this
function generateHash($plaintextpassword, $usalt = null)
{
if ($usalt === null) {
$this->salt = substr(md5(uniqid(rand(), true)), 0, SALT_LENGTH);
}
else {
$this->salt = substr($this->salt, 0, SALT_LENGTH);
}
$this->password = sha1($this->salt . $plaintextpassword);
return $this->salt . sha1($this->salt . $plaintextpassword);
}
it will create a hash and a random salt..
so if someone steals a whole bunch of passwords, the salt is different they'll have to change their bruteforce for every password.- ciphergoth, on 10/12/2007, -1/+0Why make one function do double duty? Something like this would be cleaner (not PHP, I don't speak that)
def hash_password(salt, password):
h = salt + password
for i in range(num_iters):
h = sha1(h)
return h
def new_password(password):
salt = new_salt(salt_len)
return salt + hash_password(salt, password)
def check_password(dbentry, password):
return dbentry[salt_len:] == hash_password(dbentry[:salt_len], password)
- ciphergoth, on 10/12/2007, -1/+0Why make one function do double duty? Something like this would be cleaner (not PHP, I don't speak that)
- tobyjoe, on 10/12/2007, -0/+0"$this->password = sha1($this->salt . $plaintextpassword);
return $this->salt . sha1($this->salt . $plaintextpassword);"
Why the redundant call to sha1 when you have a pointer to that value in the $this->password instance var?- cope, on 10/12/2007, -0/+1i didn't see that.
i guess return $this->salt;
- cope, on 10/12/2007, -0/+1i didn't see that.
- DonWilson, on 10/12/2007, -0/+1Welcome to Day 1 of PHP/MySQL development.
- tobyjoe, on 10/12/2007, -0/+0cope - I think you mean:
return $this->salt . $this->password; - no_tellin, on 10/12/2007, -0/+0The option we're going for is to store the hash of a password generated by an algorithm guaranteed to have many, many collisions. As long as the collisions per hash bucket are significant enough to indicate a correct password, but low enough to be very small compared with the overall password space, then you might be ok.
For example, if you have an algorithm that will have 100,000 collisions per bucket, but over a password space of 1,000,000,000,000 possible combinations, then only 0.1% of the possible passwords will hash to a given hash value, giving you 99.9% certainty a correct password was entered. If you can live with 99.9% instead of 100% when checking passwords, they you protect your hashed passwords from brute force or precomputed validation, even if your hashing algorithm is known (not secret). A successful match by a hacker would still leave them with 100,000 possible passwords to have to try against some other validation site (assuming your passwords are used in more than one system).
Digg is coming to a city (and computer) near you! Check out all the details on our