Why can bcrypt.hashpw be used both for hashing and verifying passwords?

10,473

Solution 1

In the expression bcrypt.hashpw(password, hashed) only the first couple of characters of hashed are used for the salt, not the entire string.

For instance, in this example how the output of hashpw() begins with the salt:

salt1 = b"$2a$12$w40nlebw3XyoZ5Cqke14M."

print "salt1:", salt1
print "hash1:", bcrypt.hashpw(password, salt1)

prints:

salt1: $2a$12$w40nlebw3XyoZ5Cqke14M.
hash1: $2a$12$w40nlebw3XyoZ5Cqke14M.d.7cdO2wJhr/K6ZSDjODIxLrPmYzY/a

so there is a convention where the salt only goes up the first period or the first 29 characters.

Solution 2

The hashpw function returns the salted hash (iterated many times, following bcyrpt spec), preceeded by the salt used (and with a dot as seperator).

In : salt = bcrypt.gensalt()
In : all(salt == bcrypt.hashpw(pw,salt)[:len(salt)] for pw in ('','12345','asdfgh'))
Out: True

If the second argument to bcrypt.hashpw is recognized as of the form VALID_SALT.VALID_HASH, then the salt is automagically set to VALID_SALT, thus producing the same salt-hash-pair as the original password on identical pw input.

Share:
10,473
Juan Carlos Coto
Author by

Juan Carlos Coto

Updated on July 18, 2022

Comments

  • Juan Carlos Coto
    Juan Carlos Coto almost 2 years

    Using bcrypt with Python 2.7, I can see that the example uses the bcrypt.hashpw to both hash a password for storage and verify that the given password matches a hashed one, like so:

    Hashing

    import bcrypt
    password = b"somepassword"
    hashed = bcrypt.hashpw(password, bcrypt.gensalt())
    

    Ok, so far so good. The given password is now hashed using bcrypt, so it is a string of hashed bytes.


    Verifying

    Now, here's the part that confuses me: to check that a plaintext password matches a hashed password, the same function is used, using the hashed password as a salt:

    if bcrypt.hashpw(password, hashed) == hashed:
        print("It Matches!")
    else:
        print("It Does not Match :(")
    


    What's happening?

    Shouldn't the results of both bcrypt.hashpw calls be different, since the input salts are different?

    The only reasonable answer I can think of is that the salt is truncated to a fixed length before being prepended to the hashed password. That way, when using the result of the hash, only the generated salt is left (after stripping off the trailing hashed password), and the result of hashing the password with the truncated salt is the same as the original. I don't have any evidence to support this, though.

    Why does this work?