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
<I had a similar problem - got an: ValueError: Invalid salt - it turned out that in my models I had too few characters in my column:
password = Column(String(20))
In my database and models I had to change it to:
password = Column(String(100))
and it worked.
It appears that this exception will also be returned if anything goes wrong while hashing a password.
From the bcrypt
source for hashpw()
:
hashed = _bcrypt.ffi.new("unsigned char[]", 128)
retval = _bcrypt.lib.crypt_rn(password, salt, hashed, len(hashed))
if not retval:
raise ValueError("Invalid salt")
The bcrypt
package (which Flask-Bcrypt
uses to get the work done) returns ValueError: Invalid salt
whenever the call to the OS's bcrypt lib returns an error. So if for some reason it is unable to invoke the bcrypt lib at all, it will still (incorrectly) return the Invalid salt
error.
Seems to be a flaw in the bcrypt
package implementation - it should check for specific values of retval
.
In my case, the error turned out to be related running Flask under Apache mod_wsgi
in a virtualenv
. I could run flask directly without problems (using flask-cli
), but the exact same app instance wouldn't successfully use bcrypt
when running under mod_wsgi
.
The problem was solved by modifying my Apache config to use the virtualenv as the main Python environment for mod_wsgi
.
In httpd.conf
or under /etc/httpd/conf.d/...
add:
WSGIPythonHome /path/to/my/application-virtualenv
More information about this configuration can be found here: Virtual Environments — mod_wsgi documentation
I still suspect that my particular problem is related to something being shadowed by my system's python site-packages, or something else related to python includes.
Edit: Setting WSGIPythonHome
turned out not to fix the problem. In the end I switched to uWSGI with nginx.
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.
i found my own solution (postgresql):
use bytea data type for password.
when write password to db, use convert_to
when read password from db, use convert_from
I believe you are using python 3 and bcrypt0.7.1. first you have to delete the users in your database, then go to your models and add .decode('utf-8') to the generate_password_hash() method like so:
pw_hash = bcrypt.generate_password_hash(‘hunter2’).decode('utf-8')
Alternatively you can uninstall flask-bcrypt==0.7.1 and install flask-bcrypt==0.62. Make sure you delete the users from the tables before installing flask-bcrypt==0.62