Sorry, encrypting passwords with a secret stored on the server isn't secure: If an intruder breaks into your codebase, intruder can retrieve each and any password using your codebase and the stored password (somewhere in code or your persistent store).
OWASP, the Online Web Application Security Project, provides well prepared documents: Password Storage Cheat Sheet, Authentication Cheat Sheet and PHP Security Cheat Sheet. Have a look!
The way to go is a salted hash.
How to handle newly created passwords
- If you create a new password, compute
hash = HashFunction( password, salt )
with a random salt - Save hash and salt in your database along to the user's ID
How to verify password
- Locate the userID's record in your database
- retrieve hash and salt from the record
- Based on the password, that user entered to log in, compute
hash = HashFunction( enteredPassword, salt )
- Finally, verify if the hash retrieved from store is identical to the one computed.
Why use a hash operation?
A hash operation is a so called trapdoor function: While you can compute the function easily, it's hard to compute the reverse function. Corollary: It's easy to compute a password-hash from a password, but it's hard to compute the password from the password-hash.
PHP's hash functions
These days, PHP's PBKDF2 is first choice for password hashes [Wikipedia on PBKDF2].
If your PHP installation is too old, even salted hashes with md5()
are better than two-encrypted password. But only in case definitely nothing else is available!
Sample of PBKDF2 usage
function getHashAndSaltFromString( $password ) {
// choose a sufficiently long number of iterations
// ... to make the operation COSTLY
$iterations = 1000;
// Generate a random IV using mcrypt_create_iv(),
// openssl_random_pseudo_bytes() or another suitable source of randomness
$salt = mcrypt_create_iv(16, MCRYPT_DEV_RANDOM);
$hash = hash_pbkdf2("sha256", $password, $salt, $iterations, 20);
return array( $hash, $salt );
}
Beautiful side effect of hash usage
Many websites do restrict the length of passwords to a certain length - quite likely due to the length of the underlying persistent storage [= length of field in a database table].
If you use the hash-based password storage technique, your users might use passwords of arbitrary length!
Since the hash is of constant length and you only persist password and salt, the length restriction is superfluous. Support long passwords in your web-app!
At an extreme case, you could even allow users to upload a file - e.g. a picture of their home - as a credential [=password].
Side note on random sources in PHP's MCRYPT functions
Note, that PHP does provide two sources of randomness MCRYPT_DEV_RANDOM
and MCRYPT_DEV_URANDOM
.
MCRYPT_DEV_RANDOM
gets its randomness from/dev/random
MCRYPT_DEV_URANDOM
gets its randomness from/dev/urandom
/dev/urandom
provides random data immediately and non-blocking each time you query it, /dev/random
might block (take some time to return).
Therefore, at first sight, /dev/urandom
and MCRYPT_DEV_URANDOM
might be better suited for random number generation. In fact, it is not!
/dev/random
might block request up to a point of time, at which sufficiently much entropy has been collected. Thus /dev/random
and MCRYPT_DEV_RANDOM
effectively collects randomness.
If you need to do strong crypto operations, use
MCRYPT_DEV_RANDOM
or/dev/random
.