Storing Passwords Safely

From Hashphp.org
Jump to: navigation, search

Storing passwords securely and safely

One of the really common things you do when building web apps is storing passwords for user accounts. Here's what you need to know:

  1. Don't store the actual password typed in by the user. Use hashing instead.
  2. But actually, don't hash the password alone - use a salt, and a different one for each password
  3. And don't use a simple hash function like md5 - use a cryptographic hash function such as bcrypt
  4. But in practice, don't implement your own hashing - use a library such as phpass

I'll go into each of these in detail.

#1 - Don't store the actual password typed in by the user. Use hashing instead.

Doing this is incredibly bad because

  • if your password file is disclosed people can log in using those passwords
  • people reuse passwords, so an attack on your site can lead to you being responsible for other sites being hacked

So, if you're not storing passwords, how do you check user logins? The answer is to store the hash of the password. A hash is a one-way process - the idea is that if you hash the string 'abc', you'll get something like '5eb63bbbe01eeed093cb22bb8f5acdc3', and there's no simple function to go backwards. Hashes are used for lots of things in computer science, and there are lots of different hash algorithms.

To check that a user's password matches the hash you have on file, you don't 'decrypt' the hash - instead, you run the hash algorithm again, and check that the results match. Hashes are deterministic - they always return the same result for the same input.

So, in pseudocode, where you might have done:

store($userpassword);
// some time later
$userpassword = fetch();
if($suppliedpassword == $userpassword) { 
	// log in 
} else { 
	// failed login 
}

instead you might do something like

store(hashpassword($userpassword));
// some time later
$userpassword = fetch();
if(hashpassword($suppliedpassword) == $userpassword) { 
	// log in 
} else { 
	// failed login 
}

But keep reading!

#2 - Don't hash the password alone - use a salt, and a different one for each password

One approach to breaking hashed passwords is to precompute dictionaries of hashes for a sensible set of possible passwords. That is, since we know that lots of people use 'password123' as their password, we can precompute the hash of that password, and in fact all short words or strings.

Salting is a straightforward way of making dictionary attacks useless - it still allows brute forcing, but it helps a lot. A salt is just a random string that's added to the user's password before it's hashed, and is then stored with the resulting hash. When you come back to check the password later, you take the same salt, add it to the password again, and rehash.

A few things about salts:

  • they should be different for every hash - no single salt for your whole application
  • it's most convenient to store them with the hash - many hashing libraries will actually give you back both the hash and the salt packed together in one string.

#3 - Don't use a simple hash function like md5 - use a cryptographic hash function such as bcrypt

There are lots of hash functions and they have quite different features. md5 is probably the most well-known, but it's actually a terrible hash for use with passwords - the thing is that md5 is designed to be fast. This is actually a major failing in a password hash, because the faster the hash, the faster you can brute force it.

A good cryptographic hash is bcrypt, or CRYPT_BLOWFISH. It's designed for just this purpose - it's very slow, and you can turn the knob to make it slower by adjusting its cost. This means that as computers become faster, you can rebuild your hashes to become slower and slower to process. It sounds silly to have functions be deliberately slow, but the point is that when you're using it to check a password, generally it doesn't matter if the hash takes a tenth of a second or whatever to finish - you're only doing it once. But for someone brute forcing millions of passwords, that's suddenly very hard for them.

Here's how to use bcrypt in PHP:

<?php
function hashpassword($password) {
$rounds = '09'; // the cost of the hash - increase as hardware permits
// generate a random salt - borrowed from http://nz.php.net/manual/en/function.crypt.php#102278
$salt = substr(str_replace('+', '.', base64_encode(pack('N4', mt_rand(), mt_rand(), mt_rand(), mt_rand()))), 0, 22);
 
// build a bcrypt formatted hash
$hashformat = '$2a$'.$rounds.'$'.$salt;
 
// do the hash
$hash = crypt($password,$hashformat);
return $hash;
}
 
function checkpassword($password, $hash) {
if(crypt($password,$hash) == $hash) {
return true;
} else {
return false;
}
}
 
$hash = hashpassword('password123');
 
echo "Hash was $hash";
 
if(checkpassword('password123',$hash) {
echo 'Authentication succeeded';
} else {
echo 'Authentication failed';
}

#4 - Don't implement your own hashing - use a library such as phpass

Ignoring everything above, the problem with writing your own security software is that you're likely to have bugs in it. In general, it's a better idea to use code written by someone else who's a security professional and that's been reviewed and used in practice.

I recommend http://www.openwall.com/phpass/ , which has been well-tested and is used by a wide variety of open source apps. It implements bcrypt as above, with various other nice features.

Here's the equivalent code using phpass:

<?php
 
require 'PasswordHash.php';
 
$hasher = new PasswordHash(9, false);
$hash = $hasher->HashPassword('password123');
 
if ($hasher->CheckPassword('password123', $hash)) {
echo 'Authentication succeeded';
} else {
echo 'Authentication failed';
}

Further reading