Reputation: 1146
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
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
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