Secure Password Storage with PHP / PBKDF2
The title of this article is a bit of misnomer because I'm not actually "encrypting" passwords, but rather calculating message digests or "hashes" of passwords for secure storage. Encryption and hashing are two different things. The reason I used the word encrypt in the title is because a lot of people looking for a procedure to store passwords will search encrypt, not hash. If you're looking for actual encryption, see PHP Encryption / Decryption Using the MCrypt Library (libmcrypt).
At any rate, this article is short, simple and to the point. I'm not going to go into a discussion as to why you shouldn't use MD5, SHA1 or even a simple run through SHA256. To really protect passwords (or other data we don't require the ability to literally decrypt) or generate strong encryption keys we should use PBKDF2 (Password-Based Key Derivation Function) as described in RFC 2898, which is part of RSA Laboratories' PKCS series (#5 v2.0).
PBKDF2 Implementation
So the first thing we need is a PHP-based implementation of PBKDF2, which is relatively simple as far as code complexity goes which is nice. As you can see below, this function expects four parameters: $p (password); $s (salt); $c (iteration count); and $kl (derived key length). There is also an optional fifth $a (algorithm) parameter which allows you to specify an alternate algorithm to use for hashing (defaults to sha256). The algo parameter is just there for flexibility, if for whatever reason you need to use another one. You should stick with sha256 though.
- /** PBKDF2 Implementation (described in RFC 2898)
- *
- * @param string p password
- * @param string s salt
- * @param int c iteration count (use 1000 or higher)
- * @param int kl derived key length
- * @param string a hash algorithm
- *
- * @return string derived key
- */
- function pbkdf2( $p, $s, $c, $kl, $a = 'sha256' ) {
- $dk = ''; # Derived key
- # Create key
- for ( $block = 1; $block <= $kb; $block ++ ) {
- # Initial hash for this block
- # Perform block iterations
- for ( $i = 1; $i < $c; $i ++ )
- # XOR each iterate
- $ib ^= ($b = hash_hmac($a, $b, $p, true));
- $dk .= $ib; # Append iterated block
- }
- # Return derived key of correct length
- }
PBKDF2() Explained
PHP's hash_hmac function generates keyed hashes using the HMAC method. This is the hashing function used in pbkdf2 to generate the actual hashes, and it expects an algorithm (sha256), the data, a key and optionally a boolean value specifying if raw binary output should be returned and in this case we want it to.
The first thing pbkdf2 does is determine the hash length ($hl) in octets of the specified algorithm ($a) which is to be used when generating hashes. For sha256, the length is 32 octets which is 256 bits (it's the same length as 32 ASCII characters). The derived key length you specify ($kl) tells this function how long you want the returned key to be. If $kl is longer than $hl, more than one hash block will need to be created and appended to the derived key ($dk) on each block iteration until it has a key long enough to satisfy the derived key length.
- $dk = ''; # Derived key
To determine the number of key blocks to compute, it uses the ceiling of $kl divided by $hl and places the value into $kb (key blocks to compute). If we request a 32 octet key, one block is sufficient. If we wanted a key 40 octets in length returned, two main iterations would be required for the sha256 algorithm (block 1(32) + block 2(32) = 64). The last 24 octets would be stripped, and the first 40 returned.
The simplest way to describe the meat of the pbkdf2 function is to say that for each block it creates a hash of a hash of a hash $c (iteration count) times, performing an exclusive or (XOR) operation against each previously created hash with the newly created hash. This operation is peformed $kb (key blocks to compute) times, with each new iterated block ($ib) being appended to the derived key ($dk).
- # Create key
- for ( $block = 1; $block <= $kb; $block ++ ) {
- # Initial hash for this block
- # Perform block iterations
- for ( $i = 1; $i < $c; $i ++ )
- # XOR each iterate
- $ib ^= ($b = hash_hmac($a, $b, $p, true));
- $dk .= $ib; # Append iterated block
- }
PBKDF2() Usage
If you want to use pbkdf2 to create password hashes for database storage, a TINYBLOB column should do the trick for its binary output. Or, you could run the function's output through base64_encode and store that in a VARCHAR. For encryption keys, use the raw binary output as the actual key (no need to run it through anything).
- $pass = 'users password';
- $salt = 'the salt';
- $hash = pbkdf2($pass, $salt, 1000, 32);
Introduction to JSON and PHP
Introduction to Computer Storage and Memory
Cross Browser CSS Opacity and the JavaScript Fade / Fading Effect