I have been doing some research for proper ways to hash/encrypt a password and store it in a database. I knew about Salt and Hashing so I looked around and PBKDF2 seemed to be a good choice. So I've found this website that gave a good tutorial on it as well as an adaptation of PBKDF2 for PHP (which is what I am using for my website).
So I've set up my website to use these functions to generate/create passwords, but as you can see in the following code:
function create_hash($password) { // format: algorithm:iterations:salt:hash $salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM)); return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" . $salt . ":" . base64_encode(pbkdf2( PBKDF2_HASH_ALGORITHM, $password, $salt, PBKDF2_ITERATIONS, PBKDF2_HASH_BYTES, true )); }
The salt is generated in the create_hash function, and is stored in the resulting hash which ends up looking like sha256:1000:salt:hashed_password. This is what I had to store in my database, and since the salt was included in the resulting hash, I didn't need to add it to my database. However, after generating a few test users with this, I wondered if having the PBKDF2 settings inside my hashed password in the database was actually a good thing. They way my newbie self see it is a hacker, after cracking my database, would see these bunch of sha256:1000:salt:password things, and figure out what each part would stand for, which would help him greatly in his attempts, no?
So I modified it a bit to have an external salt that I generate and store in my database, and include the salt in the password before running it through PBKDF2. I then do the same thing to compare the given password with what I have in my database for the login, and it works. My only concern is that a with 128bit salt, the resulting password hash is barely 50 characters long, that doesn't seem right to me.
Here is my current code:
define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 10000);
define("PBKDF2_SALT_BYTES", 128);
define("PBKDF2_HASH_BYTES", 24);
define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);
function create_hash($password, $salt)
{
// format: salthash
return
base64_encode(pbkdf2(
PBKDF2_HASH_ALGORITHM,
$password,
$salt,
PBKDF2_ITERATIONS,
PBKDF2_HASH_BYTES,
true
));
}
function validate_password($password, $salt, $good_hash)
{
$pbkdf2 = base64_decode($good_hash);
return slow_equals(
$pbkdf2,
pbkdf2(
PBKDF2_HASH_ALGORITHM,
$password,
$salt,
PBKDF2_ITERATIONS,
PBKDF2_HASH_BYTES,
true
)
);
}
// Compares two strings $a and $b in length-constant time.
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;
}
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true))
die('PBKDF2 ERROR: Invalid hash algorithm.');
if($count <= 0 || $key_length <= 0)
die('PBKDF2 ERROR: Invalid parameters.');
$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));
}
Is my concern for that format of password saving makes sense? Or will it just amount to the same thing anyway, since by having my salt in my database, once it's cracked, the hacker can still brute-force his way through?
Thanks, and sorry for the long question.