<?php
/**
* Password hashing with PBKDF2.
* (modified to use the native php function if available)
* Based on the pure PHP implementation of PBKDF2 which can be found on:
* https://defuse.ca/php-pbkdf2.htm
*
* @author havoc AT defuse.ca (www: https://defuse.ca/php-pbkdf2.htm)
* @author TheBlintOne
*
* @license Public Domain (so feel free to use it): http://en.wikipedia.org/wiki/Public_domain
*/
/**
* Class to encapsulate the PBKDF2 functions
*
* @author havoc AT defuse.ca (www: https://defuse.ca/php-pbkdf2.htm)
* @author TheBlintOne
*/
class PBKDF2
{
// These constants may be changed without breaking existing hashes.
const PBKDF2_HASH_ALGORITHM = "sha256";
const PBKDF2_ITERATIONS = 1000;
const PBKDF2_SALT_BYTES = 24;
const PBKDF2_HASH_BYTES = 24;
const HASH_SECTIONS = 4;
const HASH_ALGORITHM_INDEX = 0;
const HASH_ITERATION_INDEX = 1;
const HASH_SALT_INDEX = 2;
const HASH_PBKDF2_INDEX = 3;
/**
* Creates a hash for the given password
*
* @param string $password the password to hash
* @return string the hashed password in format "algorithm:iterations:salt:hash"
*/
public function create_hash( $password )
{
$salt = base64_encode( mcrypt_create_iv( PBKDF2::PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM ) );
return PBKDF2::PBKDF2_HASH_ALGORITHM . ":" . PBKDF2::PBKDF2_ITERATIONS . ":" . $salt . ":" .
base64_encode( $this->hash(
PBKDF2::PBKDF2_HASH_ALGORITHM,
$password,
$salt,
PBKDF2::PBKDF2_ITERATIONS,
PBKDF2::PBKDF2_HASH_BYTES,
true
) );
}
/**
* Checks if the given password matches the given hash created by PBKDF::create_hash( string )
*
* @param string $password the password to check
* @param string $good_hash the hash which should be match the password
* @return boolean true if $password and $good_hash match, false otherwise
*
* @see PBKDF2::create_hash
*/
public function validate_password( $password, $good_hash )
{
$params = explode( ":", $good_hash );
if( count( $params ) < HASH_SECTIONS )
return false;
$pbkdf2 = base64_decode( $params[ PBKDF2::HASH_PBKDF2_INDEX ] );
return slow_equals(
$pbkdf2,
$this->hash(
$params[ PBKDF2::HASH_ALGORITHM_INDEX ],
$password,
$params[ PBKDF2::HASH_SALT_INDEX ],
(int)$params[ PBKDF2::HASH_ITERATION_INDEX ],
strlen( $pbkdf2 ),
true
)
);
}
/**
* Compares two strings $a and $b in length-constant time
*
* @param string $a the first string
* @param string $b the second string
* @return boolean true if they are equal, false otherwise
*/
public function slow_equals( $a, $b )
{
$diff = strlen( $a ) ^ strlen( $b );
for( $i = 0; $i < strlen( $a ) && $i < strlen( $b ); $i++ )
{
$diff |= ord( $a[ $i ] ) ^ ord( $b[ $i ] );
}
return $diff === 0;
}
/**
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
* Added support for the native PHP implementation by TheBlintOne
*
* @param string $algorithm the hash algorithm to use. Recommended: SHA256
* @param string $password the Password
* @param string $salt a salt that is unique to the password
* @param int $count iteration count. Higher is better, but slower. Recommended: At least 1000
* @param int $key_length the length of the derived key in bytes
* @param boolean $raw_output [optional] (default false) if true, the key is returned in raw binary format. Hex encoded otherwise
* @return string a $key_length-byte key derived from the password and salt,
* depending on $raw_output this is either Hex encoded or raw binary
* @throws Exception if the hash algorithm are not found or if there are invalid parameters
*/
public function hash( $algorithm, $password, $salt, $count, $key_length, $raw_output = false )
{
$algorithm = strtolower( $algorithm );
if( !in_array( $algorithm, hash_algos() , true ) )
throw new Exception( 'PBKDF2 ERROR: Invalid hash algorithm.' );
if( $count <= 0 || $key_length <= 0 )
throw new Exception( 'PBKDF2 ERROR: Invalid parameters.' );
// use the native implementation of the algorithm if available
if( function_exists( "hash_pbkdf2" ) )
{
return hash_pbkdf2( $algorithm, $password, $salt, $count, $key_length, $raw_output );
}
$hash_length = strlen( hash( $algorithm, "", true ) );
$block_count = ceil( $key_length / $hash_length );
$output = "";
for( $i = 1; $i <= $block_count; $i++ )
{
// $i encoded as 4 bytes, big endian.
$last = $salt . pack( "N", $i );
// first iteration
$last = $xorsum = hash_hmac( $algorithm, $last, $password, true );
// perform the other $count - 1 iterations
for( $j = 1; $j < $count; $j++ )
{
$xorsum ^= ( $last = hash_hmac( $algorithm, $last, $password, true ) );
}
$output .= $xorsum;
}
if( $raw_output )
return substr( $output, 0, $key_length );
else
return bin2hex( substr( $output, 0, $key_length ) );
}
}
?>