5

I have a PHP7 app which hashes users passwords like this

$hash = password_hash($password, PASSWORD_BCRYPT);

For example, if I pass test1234 to that, I've got:

$2y$10$aazE9OUKZlOQiM6axwxU/utpOURLQ58pluqtFZkkGE3R9ShtUxBOm

Now, I have a Python app, which has to update users passwords too. It uses something like this:

import bcrypt

hash = bcrypt.hashpw(password, bcrypt.gensalt())

As an example, the same password test1234 hashed as:

$2a$12$vsI9Vf9gWj/Au3McYradxuozyZychmlfqoCJcSacDWuMzUDVpv33m

As you can see, PHP generated $2y where Python did $2a - so they are a bit different versions of hashes.

Now, if I will try to verify, both Python and PHP hashes, in PHP like this:

$result = password_verify($password, $hash);

I have true in both cases. But, if I try to verify both on Python side:

bcrypt.checkpw(password, hash)

It only works for when I pass hash generated in Python. If I pass hash generated in PHP, I've got:

Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
ValueError: Invalid hashed_password salt

My question: is there anything I'm missing?

The bcrypt module is supplied by the py-bcrypt project, version 0.4, that I had installed using pip:

pip3 install py-bcrypt
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
barell
  • 1,470
  • 13
  • 19

1 Answers1

5

2y and 2a are two different versions of the bcrypt algorithm. As Wikipedia states, 2y is specific to PHP only:

In June 2011, a bug was discovered in crypt_blowfish, a PHP implementation of BCrypt. [...] They also suggested the idea of having crypt_blowfish emit $2y$ for hashes generated by the fixed algorithm.

Nobody else, including canonical OpenBSD, adopted the idea of 2x/2y. This version marker change was limited to crypt_blowfish.

The py-bcrypt module is behind the times here, as it only supports the 2a version! As of February 2014, version 2b is the current release (a bug hashing passwords longer than 255 characters was fixed). The current 0.4 release was made in 2013, so I'd consider the project dead at this point.

Instead, you should install the bcrypt project. While it only supports 2a and 2b to generate hashes, it explicitly supports normalising 2y hashes to treat them as 2b hashes, and so I had no problems verifying the 2y hash using that project:

>>> import bcrypt
>>> bcrypt.__version__
'3.1.4'
>>> hash = b'$2y$10$aazE9OUKZlOQiM6axwxU/utpOURLQ58pluqtFZkkGE3R9ShtUxBOm'
>>> bcrypt.checkpw(b'test1234', b'$2y$10$aazE9OUKZlOQiM6axwxU/utpOURLQ58pluqtFZkkGE3R9ShtUxBOm')
True
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343