What python code was used to generate this pbkdf2-sha512 password entry?

412 Views Asked by At

I have the following hashed entry in a password file:

$pbkdf2-sha512$25000$K0XonfPe29vbW0up9X5vDQ$3scRqpOxF29.tqPWpKJmcFvpb8/SFtbAiI2UlrM473B3tD.Mw8xzamNaE0KpZApTc7N1stlK/vvdUl9xna6wIA

Now, I know that the password used to generate this entry was "foobar". Per this URL, I also know the following:

  • pbkdf2-sha512 identifies the cryptographic hash used to generate the hash.
  • 25000 identifies the iterations performed.
  • K0XonfPe29vbW0up9X5vDQ is the adapted base64 encoding of the raw salt bytes passed into the PBKDF2 function
  • 3scRqpOxF29.tqPWpKJmcFvpb8/SFtbAiI2UlrM473B3tD.Mw8xzamNaE0KpZApTc7N1stlK/vvdUl9xna6wIA is the raw derived key bytes returned from the PBKDF2 function

So my code proceeds from this as follows:

from passlib.utils.binary import ab64_decode
from passlib.hash import pbkdf2_sha512

salt = 'K0XonfPe29vbW0up9X5vDQ'
salt_decoded = ab64_decode(salt)
hashed_string = pbkdf2_sha512.hash('foobar', rounds=25000, salt=salt_decoded)

My problem: I expect hashed_string to always return the same value and to match the password file entry. But instead, I always get the following value for hashed_string:

$pbkdf2-sha512$25000$K0XonfPe29vbW0up9X5vDQ$ekYyFII.tyf0kWX1BBkICleOTCWIjDbKtGQ4iAU/qvGSmWQf.SAcFcJu6ZGwFFQMe4Kws2ngw.pgGaVe7F/I2g

Where am I going wrong?

I've tried various combinations of encoding and decoding values, without success. I would guess that if I knew to derive the salt correctly from the entry in the password file and/or knew how to call pbkdf2_sha512.hash() correctly, my problem would be solved.

2

There are 2 best solutions below

5
Xecrets On

Just reading the docs, but it appears that:

Passing PasswordHash.setting_kwds such as rounds and salt_size directly into the hash() method is deprecated. Callers should instead use handler.using(**settings).hash(secret). Support for the old method is is tentatively scheduled for removal in Passlib 2.0.

So, you should probably switch to use the 'using()' paradigm. Still, your code should work.

I can see two obvious problem sources. The encoding the string password (Unicode/UTF-8(/ASCII)), the documentation is not entirely clear on that point and it depends on how it was done on the source system. The second possibility I can see is that the original system used big-endian encoding.

0
Alex On

This issue relates to my attempt to manage pgAdmin users programmatically. To do that, I had to understand how pgAdmin hashes user passwords.

Under the hood, pgAdmin uses Flask, which in turn uses passlib. The step I was missing became clear when I went through passlib documentation and looked again at the SQlite database that pgAdmin uses to store user information. That database has a keys table that stores a value for SECURITY_PASSWORD_SALT. It is this value, together with the salt, that passlib uses to create the pbkdf2-sha512 hash string that pgAdmin then stores in the password column for each user in SQlite.

Here's Python3 code to generate a usable password entry:

import hashlib
import hmac
import os

salt = os.urandom(16)
h = hmac.new(str.encode(security_password_salt),
             password.encode("utf-8"),
             hashlib.sha512)
user_hash = pbkdf2_sha512.hash(b64encode(h.digest()),
                               rounds=25000,
                               salt=salt)

To verify a password, use the salt stored in the password string and SECURITY_PASSWORD_SALT. Apply the algorithm shown here and the resulting user_hash string will match that stored in SQlite.

Following Xecrets' suggestion, the last line could probably be updated to something like the following when hashing the password:

user_hash = pbkdf2_sha512.using(rounds=25000,salt_size=16).hash(b64encode(h.digest()))

But I haven't tried this yet.