0

I am trying to compare two passwords using bcrypt. The hashed password is stored in the database as a string. When I compare the two hashed passwords (the one from the database and the one from the user), I make sure they are both encoded, but the passwords still do not match.

Why do the hashed passwords not match when I store it in a database? Is it because the salt is different than when the hash password was originally generated? If so, how am I supposed to check that a password matches what is stored in a database?

code:

import os
import bcrypt
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base, sessionmaker

Base = declarative_base()

os.remove("users.db") if os.path.exists("users.db") else None
engine = create_engine("sqlite:///users.db")
Session = sessionmaker(bind=engine)
session = Session()


class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    username = Column(String(50), unique=True, nullable=False)
    password_hash = Column(String(100), nullable=False)


Base.metadata.create_all(engine)


def hash_password(password: str) -> str:
    hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
    return hashed.decode("utf-8")


def create_user(username: str, password: str) -> None:
    hashed_password = hash_password(password)
    user = User(username=username, password_hash=hashed_password)
    session.add(user)
    session.commit()
    print(f"User '{username}' created.")
    print(f'password: {password}')
    print(f'password_hash: {hashed_password}')
    print()


def verify_password(username: str, password_to_check: str) -> bool:
    user = session.query(User).filter_by(username=username).first()
    if not user:
        print("User not found.")
        return False
    
    match = bcrypt.checkpw(
        hash_password(password_to_check).encode('utf-8'), user.password_hash.encode("utf-8")
    )
    if match:
        print("Password match!")
    else:
        print("Password does not match.")
        print('password_to_check:', password_to_check)
        print('password_to_check (encode):', hash_password(password_to_check).encode('utf-8'))
        print('user.password_hash:', user.password_hash)
        print('user.password_hash (encode):', user.password_hash.encode('utf-8'))
    return match


# Create a user
create_user("alice", "my_secure_password")

# Try to verify password
verify_password("alice", "my_secure_password")  # Should match
print()
verify_password("alice", "wrong_password")  # Should not match
print()

output:

User 'alice' created.
password: my_secure_password
password_hash: $2b$12$Q3NmS7nQ.hvg7NnUk8bONO3.tVHmB0IC758BBKXXzT2omFuBp4xd2

Password does not match.
password_to_check: my_secure_password
password_to_check (encode): b'$2b$12$veGsFll.mjNjK10GXYYLqe9at/9FN.2Ue12T.l9BedjbHIQmEVWDe'
user.password_hash: $2b$12$Q3NmS7nQ.hvg7NnUk8bONO3.tVHmB0IC758BBKXXzT2omFuBp4xd2
user.password_hash (encode): b'$2b$12$Q3NmS7nQ.hvg7NnUk8bONO3.tVHmB0IC758BBKXXzT2omFuBp4xd2'

Password does not match.
password_to_check: wrong_password
password_to_check (encode): b'$2b$12$x6CdcXKwfIjgwcAWiAkU8.1ukRrKUVmgmBFY5Sw438odzK6X7D4fG'
user.password_hash: $2b$12$Q3NmS7nQ.hvg7NnUk8bONO3.tVHmB0IC758BBKXXzT2omFuBp4xd2
user.password_hash (encode): b'$2b$12$Q3NmS7nQ.hvg7NnUk8bONO3.tVHmB0IC758BBKXXzT2omFuBp4xd2'

Even in this most basic example, they still do not match:

match = bcrypt.checkpw(
        hash_password('my_secure_password').encode('utf-8'),
        hash_password('my_secure_password').encode("utf-8"))

> False

1 Answer 1

0

I just realized my issue. In order to compare the passwords, the first argument to checkpw() is a str converted into bytes. The second argument is also a str converted into bytes, but checkpw() must do something behind the scenes to remove the salt when it was generated.

match = bcrypt.checkpw(
    password_to_check.encode('utf-8'),
    user.password_hash.encode("utf-8")
)
Sign up to request clarification or add additional context in comments.

2 Comments

Look at the wikipedia on article on bcrypt to see how the output from bcrypt encodes the algorithm, the cost, the salt, and the "hash". You have to pull out all of the fields, pass them as input to bcrypt and include what the user types as the password, and confirm that it equals the same output.
it's more likely that checkpw takes the original salt used, because it's just right there stored in the password hash from wikipedia $ algo$ cost$ salt hash then just hashes the to check password with that salt and compares (yes, it's just fine to store salts in the database because they only exist to break rainbow tables)

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.