4

I have a problem, because I have a database with users and theirs passwords were secured with Django (pbkdf2). So '123' looks like this:

pbkdf2_sha256$20000$MflWfLXbejfO$tNrjk42YE9ZXkg7IvXY5fikbC+H52Ipd2mf7m0azttk=

Now I need to use this passwords in PHP project and I don't have any idea how to compare them.

1
  • I strongly disagree @Sayse - it sounds like he's replacing django with PHP, so the answer shouldn't be "run both" Commented Sep 3, 2016 at 21:32

1 Answer 1

14

pbkdf2_sha256$20000$MflWfLXbejfO$tNrjk42YE9ZXkg7IvXY5fikbC+H52Ipd2mf7m0azttk=

Let's break this down. The $ are separators:

  • pbkdf2_sh256 means PBKDF2-SHA256, i.e. hash_pbkf2('sha256', ...)
  • 20000 is the iteration count
  • MflWfLXbejfO is the salt
  • tNrjk42YE9ZXkg7IvXY5fikbC+H52Ipd2mf7m0azttk= is likely the hash.

This is all the information you need to validate the hash from PHP. You just need:

  1. hash_pbkdf2() to generate a new hash from the password provided by the user
  2. hash_equals() to compare the generated hash with the stored one

This function should work (PHP 7+):

/**
 * Verify a Django password (PBKDF2-SHA256)
 *
 * @ref http://stackoverflow.com/a/39311299/2224584
 * @param string $password   The password provided by the user
 * @param string $djangoHash The hash stored in the Django app
 * @return bool
 * @throws Exception
 */
function django_password_verify(string $password, string $djangoHash): bool
{
    $pieces = explode('$', $djangoHash);
    if (count($pieces) !== 4) {
        throw new Exception("Illegal hash format");
    }
    list($header, $iter, $salt, $hash) = $pieces;
    // Get the hash algorithm used:
    if (preg_match('#^pbkdf2_([a-z0-9A-Z]+)$#', $header, $m)) {
        $algo = $m[1];
    } else {
        throw new Exception(sprintf("Bad header (%s)", $header));
    }
    if (!in_array($algo, hash_algos())) {
        throw new Exception(sprintf("Illegal hash algorithm (%s)", $algo));
    }

    $calc = hash_pbkdf2(
        $algo,
        $password,
        $salt,
        (int) $iter,
        32,
        true
    );
    return hash_equals($calc, base64_decode($hash));
}

Demo: https://3v4l.org/WbTpW

If you need legacy PHP 5 support, removing the string prefixes and the : bool from the function definition will make it work on PHP 5.6. I don't advise trying to add backward compatibility for versions of PHP earlier than 5.6; if you find yourself in this situation, you should update your server software instead.

Sign up to request clarification or add additional context in comments.

3 Comments

Props for using hash_equals - constant time comparisons should always be used for comparing things in a secure way. I think in this case though you can safely use a normal string comparison, since the output of PBKDF2 isn't guessable from it's input, so there's no way to influence the timing attack (and therefore shouldn't be a side-channel).
Correct, there's no practical attacks on password hashing functions that don't use hash_equals(); I do so for consistency and to establish good habits in people who reference my code snippets. :)
Which is definitely the way to go about things :). Never hurts to do a constant time comparison always (sans a few cycles for hashing), and then you never screw it up!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.