Search code examples
javapythonhashcryptographypbkdf2

PasswordHash.java not generating matching PBKDF2-HMAC-SHA1 hash


I am writing a Django app that needs to work with an existing Java Play framework app. The Play app uses PasswordHash.java to store passwords. It stores passwords in a colon separated format. Each hash is stored as iterations:salt:pbkdf2_hash.

For example, here is an entry for the password "test": 1000:f7fe4d511bcd33321747a778dd21097f4c0ff98f1e0eba39:b69139f51bc4098afc36b4ff804291b0bc697f87be9c1ced

Here we can split the string by : and find:

Iterations: 1000

Salt: f7fe4d511bcd33321747a778dd21097f4c0ff98f1e0eba39

PBKDF2 Hash: b69139f51bc4098afc36b4ff804291b0bc697f87be9c1ced.

I modified Django's check_password mechanism to be compatible with this format, but found that it didn't think the password was correct. I used Django's crypto.py to regenerate a hash for "test" using the same salt that Play used, and came up with this:

hash = crypto.pbkdf2('test', 'f7fe4d511bcd33321747a778dd21097f4c0ff98f1e0eba39', 1000, 24)
base64.b16encode(hash)
'9A8725BA1025803028ED5B92748DD61DFC2625CC39E45B91'

The PBKDF2 hash from play does not match this hash. (For those wondering, I used 24 as the fourth parameter because that is what is used in PasswordHash.java).

After I was unable to make Django's generated hash match Java's, I tried it on a website that does it for you.

I plugged in the same salt, used SHA-1 with 1000 iterations and a 24-bit key size and found that the website matched what Django had created!

I am not sure what is going on with PasswordHash.java, but I desperately need to get Django and Play to "play nicely" (couldn't help myself haha). Does anyone have an idea as to what is going on here?


Solution

  • Try salt = base64.b16decode(salt.upper()).

    I did and I got the hash in your initial example, albeit uppercased B69139F5...

    Explanation:

    The hash and salt are both being stored in Base16 (hex) in your initial example. So you decode the salt to use it and then encode the resulting hash to compare it with the stored one.

    The upper() is because python's b16decode is strict about uppercase Base16. It will error if you give it the lowercase one.