Jul 1 2009

A Basic Lesson in Password Hashing

In the world of the web, lots of sites are popping up requiring users to login. When you need to do so, there’s a bit more security than you might realize. You might be making a simple To-Do list, and might think:

Security? Pfft, I’m not too worried about people’s to-do lists being stolen.

But what you didn’t account for, is that all those username/password combinations a hacker just made off with? Yea, those are the same login’s to important stuff, like e-mail , or bank accounts . Yikes!

Worst Case Scenario

Most users of the web don’t think this hard about their own security, and even those that do, it’s too complicated to remember a unique username and password for every single web-site you visit . And most of those users that don’t know much about security also don’t realize the need for using complicated passwords. So if you’re collecting usernames and passwords, you need to design for the worst possible scenario:

Password: password1

Just Hash It All

So hopefully, the first thing you’re thinking is that you shouldn’t store your passwords in plain text. Good idea. You were thinking a hash, right? Because 2-way encryptions has it’s own security problems. Once a user discovers the encryption key, they can decrypt every password you have. So let’s hash everything.

There’s plenty of debate about the best hashing algorithms, so for sake of simplicity, I’ll just use md5*. But we’re not going to use the straight output of the hash. That’s like storing plain text. Instead, we’ll make sure we use a nice salt.

Not any salt. Nothing like “NaCLS4lt”. No no, that also makes it too easy for hackers to precompute. Instead, we’re going to generate a random salt for every single password. So even if they manage to crack just one password, they haven’t gained the salt for the rest of the hashes.

$salt = substr(md5(uniqid(rand(), true)), 0, $saltLen);  

We grab a nice, random string that gets hashed, and this garbled text becomes our salt.

$hash = md5($salt . $plain);  

Optionally, here you could do some manipulation, like splitting the password in half, or adding some salt to the end. The same goes for this entire process. The more you can mix it up without trying to come up with your own hashing algorithm, the more non-standard your passwords become.

Now then, we need this salt for later use, or else we’ll never be able to regenerate this hash when a user logs in! We’re not ganna store it in a seperate database field called salt. That gives it straight away to the hacker. Instead, we attach it to the hash and make it seem like the whole long thing is the password.

Many sites will suggest simply concatenating them together, $salt . $hash . However, I figure, with such a constant location, while the hacker does have to deal with random salts, he doesn’t have to worry about the location of it.

So we take something that is constant with the user, but different enough to allow variations for storing the salt. I’ll use simply the length of the password in plain text. If the password is 11 characters long, I insert the salt at character 11 of the hash. This way, it’s different than a password that is 8 characters in length.

return substr($hash,0,$saltStart).$salt.substr($hash,$saltStart+1);

Now, this function you’ve been building up, all you need to do is add an optional argument for a hash. When a user tries to login, you look up the hash from the database, and supply their password attempt and hash to this function. Now check if the hash is supplied, and if so, calculate the position of the salt in the hash, grab the salt, and use that for your md5($salt.$hash) part. If the function returns a hash that equals the hash in the database, you have the correct password.

*I don’t claim to be a cryptographer, so use at your own risk.