I was finishing up a simple user login with Flask and flask-Bcrypt. However, when trying to login with a user that is stored in my database, I keep getting this error
ValueError: Invalid salt
models.py
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
email = db.Column(db.String, nullable=False)
password = db.Column(db.String, nullable=False)
posts = db.relationship("Post", backref="author", lazy="dynamic")
def __init__(self, name, email, password):
self.name = name
self.email = email
self.password = bcrypt.generate_password_hash(password)
def __repr__(self):
return '<User {}>'.format(self.name)
views.py
@app.route("/login", methods=["GET", "POST"])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter(User.name == form.username.data).first()
if user and bcrypt.check_password_hash(user.password, form.password.data):
flash("you were just logged in!")
login_user(user)
return redirect(url_for("home"))
else:
flash("bad username or password")
return render_template("login.html", form=form)
forms.py
class LoginForm(Form):
username = StringField('username', validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired()])
My problem is similar to described by @tomClark
I use Postgres as my DDBB and his driver, or the DDBB system, encode always an already encoded string. The second encode process create an invalid hash like this:
'\\x24326224313224483352757749766438764134333757365142464f4f4f464959664d66673575467873754e466250716f3166375753696955556b2e36'
A correct hash looks like this:
$2b$12$Wh/sgyuhro5ofqy2.5znc.35AjHwTTZzabz.uUOya8ChDpdwvROnm
To resolve it, I decode the hash to utf8 first than save it to the DDBB.
Example code:
def set_password(self, pw):
pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt())
self.password_hash = pwhash.decode('utf8') # decode the hash to prevent is encoded twice
In my case, the problem was related to a type conversion going on during password storage. Using bcrypt.generate_password_hash(plaintext)
returns a binary value, like b'$2b$12$zf/TxXZ4JJZ/lFX/BWALaeo0M.wNXrQXLqIFjmZ0WebqfVo9NES56'
.
Like mine was, your password column is set up as a string:
password = db.Column(db.String, nullable=False)
I found that generating the hash above, storing that binary value it in my string password column, then simply retrieving it resulted in a different value due to SQLAlchemy's type conversion - nothing to do with bcrypt at all!
A question on correct column type helped me realise that for correct roundtrip I had to store passwords as binary. Try replacing your column definition with:
password = db.Column(db.Binary(60), nullable=False)
I don't know for certain but suggest that different production environments and databases might handle this type conversion differently (reversibly in some cases, not in others), perhaps explaining the mixed success @Samuel Jaeschke has had.
This also explains why encoding the input string to a constrained character set (an earlier solution) might help in some cases and not others - if it causes the to/from type conversion to work then you'll recover the correct hash from the database for comparison.
At any rate, that solved this problem for me.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With