This is a follow-up question to Iteration of password hashing in PHP
I've gone trough a lot of documentations and stuff to get a feel for this hashing jungle, and done some adjustments to my code along the way.
PBKDF2 seemed like a good fit, so that's wath I went for.
Also, it's important to know that I don't have access to PHP 5.5 with my current host. Only 5.2, 5.3 and 5.4 is available. And thy won't let me update it either...
I've created a function - which is just a stripped down modification of the pbkdf2
suggested multiple times on Stack Overflow. (please see links at the bottom).
function pbkdf2($algorithm,$salt,$password,$iterations,$hash_bytes){
/* I've removed the checks at the beginning, along with the if (function_exists("hash_pbkdf2")) */
$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($hash_bytes/$hash_length);
$output = '';
for($i=1; $i<=$block_count; $i++){
$last = $salt.pack("N", $i); // $i encoded as 4 bytes, big endian.
$last = $xorsum = hash_hmac($algorithm, $last, $password, true); // first iteration
# perform the other $iterations - 1 iterations
for($j=1; $j<$iterations; $j++){
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
//
$output .= $xorsum;
}
/* I also removed the $raw_format variable */
return base64_encode(substr($output, 0, $hash_bytes));
}
In addition, I'm taking advantage of this one as well when comparing the hashes on login:
well explained here
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;
}
The adjustments I've made (original question here) is basically that I've switched
$hash = hash('sha256',$salt.$_POST['secret']); // create a 64 character hash based on the salt and the password
out with
$hash = pbkdf2($algorithm, $salt, $_POST['secret'], $iterations, $hash_bytes); // create a hash based on the salt and the password
when adding a new user (first script).
Then, inside the second script, where the user is logging in, I'm matching the password/hashes using slow_equals()
instead of just is this string equal to the other one?
(==)
...
if(slow_equals(pbkdf2($algorithm, $get['salt'], $_POST['secret'], $iterations, $hash_bytes), $get['hash'])){
/* ... */
}
My "settings" for the pbkdf2()
-function is as follows:
$algorithm = 'sha512'; // don't really know which algorithm to use
$iterations = 5000; // seemed like a good fit
$hash_bytes = 48; // produces a 64 character long string
Here are the two scipts with the mentioned adjustments:
/**
* This is the script that is executed when I add a new user - after the input has passed the validation.
*/
$salt = base64_encode(mcrypt_create_iv($hash_bytes,MCRYPT_DEV_URANDOM)); // generate a random 32 character salt
$hash = pbkdf2($algorithm, $salt, $_POST['secret'], $iterations, $hash_bytes); // create a hash based on the salt and the password
$ins = $con['site']->prepare('INSERT INTO users (handle, salt, hash) VALUES (:handle, :salt, :hash)');
$ins->execute(array(':handle'=>$_POST['handle'], ':salt'=>$salt, ':hash'=>$hash));
if($ins){
KD::addNotice('success','...message...');
} else {
KD::addNotice('error','...message...');
}
/**
* This is the script that is executed when the user is logging in - also after the input has passed the validation.
* Changed to "early returns" as suggested by @tim
*/
$qry = $con['site']->prepare('SELECT handle, salt, hash FROM _site_users WHERE BINARY handle = :handle');
$qry->execute(array(':username'=>$_POST['handle']));
if($qry->rowCount()!=1){
KD::addNotice('error','...message...');
return;
}
$get = $qry->fetch();
if(slow_equals(pbkdf2($algorithm, $get['salt'], $_POST['secret'], $iterations, $hash_bytes), $get['hash'])){
KD::addNotice('success','...message...');
session_regenerate_id();
$_SESSION['backend']->login($get['handle']);
} else {
KD::addNotice('error','...message...');
}
Any opinions regarding this updated code? Also, have I in any way made the pbkdf2 vulnerable with the modifications I've done?
Links: