How to properly store a PBKDF2 password hash
Asked Answered
V

3

6

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.

Veroniqueverras answered 31/7, 2012 at 6:41 Comment(2)
See these answers for a good understanding of how useless hiding a salt is here, using bcrypt (since in your link no implementation is given) here, and best practices on how to use hash to secure passwords here. Finally, never underestimate your opponent!Motivation
@Motivation This is the answer I was looking for. Made one for now but feel free to answer yourself.Veroniqueverras
V
2

Answering my own question with a comment from @Romain.

See these answers for a good understanding of how useless hiding a salt is here, using bcrypt (since in your link no implementation is given) here, and best practices on how to use hash to secure passwords here. Finally, never underestimate your opponent!

Also, (since this is a bit old), bcrypt is indeed "better" than pbkdf2, but scrypt is even better!

Veroniqueverras answered 31/7, 2012 at 6:41 Comment(0)
B
4

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.

The size of the resulting hash is completely unrelated to the size of the salt, the password, and the number of iterations. The output of a modern secure hash algorithm (like sha256) is always the same length regardless of the input. A zero length input has the same length output as a 25TB input.

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?

By separating the salt into two pieces, you cost yourself increased code complexity (generally a bad thing). Depending on how you store the salt pieces, you may gain a little benefit under some circumstances. As an example, if the static salt fragment is stored outside of the database, then a dump of the database will not give an attacker enough information to perform an offline attack on the password hashes in the database.

The gain if the salt fragments are stored separately from one-another is a small amount of defence in depth. Whether it outweighs the complexity cost is a judgement call, but I'd say the chances are good that the time would be better spent looking for XSS and SQL injection vulnerabilities (the way that attackers often get the database dumps mentioned above), and securing connections between the various components of your system with SSL and certificates or strong passwords.

Bennie answered 1/8, 2012 at 5:0 Comment(0)
V
2

Answering my own question with a comment from @Romain.

See these answers for a good understanding of how useless hiding a salt is here, using bcrypt (since in your link no implementation is given) here, and best practices on how to use hash to secure passwords here. Finally, never underestimate your opponent!

Also, (since this is a bit old), bcrypt is indeed "better" than pbkdf2, but scrypt is even better!

Veroniqueverras answered 31/7, 2012 at 6:41 Comment(0)
C
0

A couple of things, one, instead of SHA256, you should be using a slow hashing function such as bcrypt, two, you should probably not be storing passwords at all (instead use openId or something similar).

All of that said, you need a different salt for each user, you can store them in the same row or even (as you are doing) the same field, or in an entirely different db. How much effort you're willing to go to is really up to you and your performance requirements.

In addition to the individual salt for each user/password, you might also consider a per application salt, which is kept out of the any database.

Christner answered 1/8, 2012 at 5:11 Comment(6)
You should not use a per application salt without a password specific salt as an attacker could search for duplicate hashes and use that information to attack the most vulnerable passwords (and find two or more user accounts for the same amount of work as well).Hoffert
@owlstead: yes, I meant in addition to the salt for the password. Will edit to make that clear. Thanks.Christner
this answer is confusing. there's nothing wrong with use SHA256 when you are using PBKDF2. the PBKDF2 construction adds the slownesss (it's the equivalent of bcrypt; the "1000" part means that SHA256 was called 1000 times by PBKDF2).Bumkin
PBKDF2 is not the equivalent of bcrypt -- they have similarities, but running SHA256 1000 times is not the same as running bcrypt 1000 times.Christner
@jmoreno: if you compare PBKDF2 with bcrypt, then consider that those 1000x SHA256-calls (or whatever hash algo / iteration count you take) happen during 1 single call of PBKDF2.Secularism
@Robert: Yes, you can make them take equal amounts of time, on a given system, but not for the same number of iterations. SHA256 is less resource intensive than BCRYPT, and can be greatly speeded up using a GPU. Since you don't control the attackers machine, its better to use a hashing function which makes it harder for them to use hardware to accelerate it. BCRYPT is designed to be slow (memory access), SHA256 is designed to be fast (simple bit shift and xors). For passwords, BCRYPT is better (although it now might be better to use scrypt).Christner

© 2022 - 2024 — McMap. All rights reserved.