using bcrypt password hashing for user authentication

4.8k Views Asked by At

I am trying to use hashed passwords in my app, like this:

class UserService():

    def register_user(self, username, email, password):
        if self.__checkIfUserExists(username) is True:
            return False
        else:

            hashed_password = self.__hash_password(password.encode('utf-8'), bcrypt.gensalt())

            user = User(username=username, email=email, password_hash=hashed_password)

            db.session.add(user)
            db.session.commit()
            db.session.close()

        return True

    def authenticate_user(self, email, password):
        user = User.query.filter_by(email=email).first()
        hashed_pw = self.__hash_password(password.encode("utf-8"), bcrypt.gensalt())
        print(hashed_pw == user.password_hash)
        if user and hashed_pw == user.password_hash:
            return True
        return False


    def __checkIfUserExists(self, username):
        exists = db.session.query(db.exists().where(User.username== username)).scalar()
        return exists

    def __hash_password(self, password, salt):
        return bcrypt.hashpw(password, salt)

Well, the passwords never match.

How do I get this to work? Where is my mistake? I thought I had to compare the hash of the provided password with the hash stored in the database..?

3

There are 3 best solutions below

1
On BEST ANSWER

From https://pypi.python.org/pypi/bcrypt/2.0.0:

>>> import bcrypt
>>> password = b"super secret password"
>>> # Hash a password for the first time, with a randomly-generated salt
>>> hashed = bcrypt.hashpw(password, bcrypt.gensalt())
>>> # Check that a unhashed password matches one that has previously been
>>> #   hashed
>>> if bcrypt.hashpw(password, hashed) == hashed:
...     print("It Matches!")
... else:
...     print("It Does not Match :(")

Note that hashpw takes in either a salt or a previously hashed password (which includes the salt that was used to create it) allowing proper comparison.

You've used bcrypt.gensalt() twice which creates two different random salts, so the hashes will be different.

0
On

This answer might help someone who uses DB to save the encrypted password.

Problem: I saved the encoded password to string column.

Solution: The hashed password is of binary type which must be saved as Binary not as a string or as JSON.

Column properties of password column from my code, I used Postgresql, Flask and sqlalchemy here which worked after I changed the column datatype

from sqlalchemy import Column, String, Integer, Boolean, Date, LargeBinary
from db import Base

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True, autoincrement=True)
    userId = Column(String(50), nullable=False)
    firstName = Column(String(50), nullable=False)
    password = Column(LargeBinary(100), nullable=False)
    recovery = Column(String(25))
    phone = Column(String(10), nullable=False, unique=True)
    phoneVerified = Column(Boolean, nullable=False)
    email = Column(String(50), nullable=False, unique=True)
    emailVerified = Column(Boolean, nullable=False)

    def __init__(self, userId, firstName, password, phone, phoneVerified, email, emailVerified, recovery):
        self.userId = userId
        self.firstName = firstName
        self.password = password
        self.phone = phone
        self.email = email
        self.phoneVerified = phoneVerified
        self.emailVerified = emailVerified
        self.recovery = recovery

code for Resource:

import bcrypt
import shortuuid
from flask_restful import Resource, reqparse
from entities.User import User

class Register(Resource):
    @staticmethod
    def post():
        data = Register.parser.parse_args()
        hashed = bcrypt.hashpw(data.password.encode('utf-8'), bcrypt.gensalt())

        user = User(userId=shortuuid.ShortUUID().random(length=10), firstName=data.firstName,
                                    password=hashed, phone=data.phone, phoneVerified=False, email=data.email,
                                    emailVerified=False, recovery=shortuuid.ShortUUID().random(length=25))
        session.add(user)
        session.commit()
        session.close()
        return {'description': 'You are registered'}, 201
        

class Login(Resource):
    @staticmethod
    def post():
        data = Login.parser.parse_args()
        user = find_by_email(data.email)
        if user:
            pw_check = bcrypt.checkpw(password=data.password.encode('utf-8'), hashed_password=user.password)
            access_token = create_access_token(identity=user.id, fresh=True)
            refresh_token = create_refresh_token(identity=user.id)
            return {'access_token': access_token, 'refresh_token': refresh_token}, 200
        return {'description': 'Invalid credentials'}, 401
0
On

Alex is right, adding gensalt second time is the issue. But you dont have to compute the hash again.

From version 3.1.0, checkpw has been added.

To keep it pythonic, you can use checkpw rather than comparing with the hashed password again.

>>> import bcrypt
>>> password= 'cantguessme'
>>>hashed= bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(12))
>>>if bcrypt.checkpw(password, hashed):
   ...     print("Yaay, It Matches!")
   ... else:
   ...     print("Oops, It Does not Match :(")