Kyle Holmberg
Kyle Holmberg

Reputation: 1146

Bcrypt salt treating byte object as string and wont hash password

Answered

I have a Flask web app with a Postgres DB. I'm trying to implement database salting with bcrypt, but it seems as though my byte representation is being viewed as a string. To be clear: I'm creating the hashed password with no problem, but when authorizing a login attempt, generating the comparison hash with the passed salt isn't working. Here's the error stack that occurs when I attempt to login:

lost/app/views/login.py", line 37, in login
authorized = helpers.authorize(username, password)

lost/app/helpers.py", line 108, in authorize
return _check_hash_for_user(username, password)

lost/app/helpers.py", line 93, in _check_hash_for_user
generated_hash = _recreate_hash(password, _get_salt_for_user(username))

lost/app/helpers.py", line 100, in _recreate_hash
hash_pass = bcrypt.hashpw(password.encode('utf-8'), salt)

lost/venv/lib/python3.6/site-packages/bcrypt/__init__.py", line 62, in hashpw
raise TypeError("Unicode-objects must be encoded before hashing")


How my users table is created:

CREATE TABLE users (
    user_pk     SERIAL PRIMARY KEY,
    username    VARCHAR(16) UNIQUE NOT NULL,
    salt        VARCHAR(72) NOT NULL,
    password    VARCHAR(256) NOT NULL


Relevant aspects of my login view

username = request.form.get('username', None)
password = request.form.get('password', None)

# If user exists...
authorized = helpers.authorize(username, password)

if authorized:
    # login...


Here's how I created the user's password and salt

salt = bcrypt.gensalt(12)
password = bcrypt.hashpw(password.encode('utf-8'), salt)


The auth aspect of helpers.py

def _get_hash_for_user(username):
    password = db_query("SELECT password FROM users WHERE username=%s;", [username])[0][0]
    return password


def _get_salt_for_user(username):
    salt = db_query("SELECT salt FROM users WHERE username=%s;", [username])[0][0]
    return salt


def _create_password_hash(password):
    salt = bcrypt.gensalt(16)
    hashed_pass = bcrypt.hashpw(password, salt)
    return hashed_pass, salt


def _check_hash_for_user(username, password):
    stored_hash = _get_hash_for_user(username)
    generated_hash = _recreate_hash(password, _get_salt_for_user(username))
    return stored_hash == generated_hash


def _recreate_hash(password, salt):
    hash_pass = bcrypt.hashpw(password.encode('utf-8'), salt)
    return hash_pass


def authorize(username, password):
    return _check_hash_for_user(username, password)

edit: At the behest of somebody trying to help me through this issue, I am now creating the users table with password and salt using BYTEA data type instead of VARCHAR() - other issues ensued, but I'll be working through them.

Upvotes: 4

Views: 5010

Answers (2)

Miles Erickson
Miles Erickson

Reputation: 2595

As user2357112 indicated: you're getting your salt from the database as a str and it needs to be cast to bytes.

MCVE to reproduce error:

import bcrypt
salt = str(bcrypt.gensalt())
pw = "Dagg Durneden Co. Mfgrs. Green Hood Shirts"
pw_hash = bcrypt.hashpw(pw.encode('utf-8'), salt)

Fix:

pw_hash = bcrypt.hashpw(pw.encode('utf-8'), bytes(salt))

Upvotes: 4

Kyle Holmberg
Kyle Holmberg

Reputation: 1146

OP answering his own question - thanks to everybody's help

It wasn't intelligent to store hashes as varchars in my database and continue flipping them within my code. Instead, I changed the users table to store the hashed password and the salt as BYTEA data types.

After that, I changed my password and salt getter functions (in auth.py) by casting the return type as byte()

so...

def _get_hash_for_user(username):
    password = byte(db_query("SELECT password FROM users WHERE username=%s;", [username])[0][0])
    return password


def _get_salt_for_user(username):
    salt = byte(db_query("SELECT salt FROM users WHERE username=%s;", [username])[0][0])
    return salt

Upvotes: 0

Related Questions