Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BCrypt. How to store salt with python3?

We have code, that work for python 2.

@password.setter
def password(self, value):
    self.salt = bcrypt.gensalt()
    self.passwd = bcrypt.hashpw(value.encode('utf-8'), self.salt)

def check_password(self, value):
    return bcrypt.hashpw(value.encode('utf-8'), self.salt.encode('utf-8')) == self.passwd

However, when I try to convert it to python3, we meet following problems:

Error one happens on cassandra driver level:

cassandra.cqlengine.ValidationError: passwd <class 'bytes'> is not a string

Ok. Casting salt and passwd to string:

@password.setter
def password(self, value):
    salt = bcrypt.gensalt()
    self.salt = str(salt)
    self.passwd = str(bcrypt.hashpw(value.encode('utf-8'), salt))

Now salt saves. But in check_password we get ValueError: Invalid salt. If we change check password code to:

def check_password(self, value):
    return bcrypt.hashpw(value, self.salt) == self.passwd

We get error TypeError: Unicode-objects must be encoded before hashing.

Where to dig?

UPD Salt values in password and check password look same, for example:

b'$2b$12$cb03angGsu91KLj7xoh3Zu'                                                                        
b'$2b$12$cb03angGsu91KLj7xoh3Zu'
like image 947
Nikolay Fominyh Avatar asked Mar 17 '16 10:03

Nikolay Fominyh


1 Answers

Update

As of version 3.1.0 bcrypt provides the convenience function

checkpw(password, hashed_password)

to perform password checking against a hashed password. This should be used instead of:

bcrypt.hashpw(passwd_to_check, hashed_passwd) == hashed_passwd

which is shown below. There is still no need to store the hash separately.


First of all, you don't need to store the salt because it is part of the hash produced by bcrypt.hashpw(). You just need to store the hash. E.g.

>>> salt = bcrypt.gensalt()
>>> salt
b'$2b$12$ge7ZjwywBd5r5KG.tcznne'
>>> passwd = b'p@ssw0rd'
>>> hashed_passwd = bcrypt.hashpw(passwd, salt)
b'$2b$12$ge7ZjwywBd5r5KG.tcznnez8pEYcE1QvKshpqh3rrmwNTQIaDWWvO'
>>> hashed_passwd.startswith(salt)
True

So you can see that the salt is included in the hash.

You can also use bcrypt.hashpw() to check that a password matches a hashed password:

>>> passwd_to_check = b'p@ssw0rd'
>>> matched = bcrypt.hashpw(passwd_to_check, hashed_passwd) == hashed_passwd
>>> matched
True
>>> bcrypt.hashpw(b'thewrongpassword', hashed_passwd) == hashed_passwd
False

No need to store the salt separately.


So you could write the setter like this (Python 3):

@password.setter
def password(self, passwd):
    if isinstance(passwd, str):
        passwd = bytes(passwd, 'utf-8')
    self.passwd = str(bcrypt.hashpw(passwd, bcrypt.gensalt()), 'utf8')

And the checker like this:

def check_password(self, passwd_to_check):
    if isinstance(passwd_to_check, str):
        passwd_to_check = bytes(passwd_to_check, 'utf-8')
    passwd = bytes(self.passwd, 'utf8')
    return bcrypt.hashpw(passwd_to_check, passwd) == passwd
like image 161
mhawke Avatar answered Sep 26 '22 09:09

mhawke