添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

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()])
                Hmm, I feel your hash is not getting stored correctly in your DB. Looked at a few things, and you seem to be using Flask-Bcrypt correctly. What's the value of user.password?
– Ryan O'Donnell
                Dec 31, 2015 at 17:37
                @RyanO'Donnell this is the value of user.password   '\\x24326224313224483352757749766438764134333757365142464f4f4f464959664d66673575467873754e466250716f3166375753696955556b2e36' 
– Ali Faki
                Dec 31, 2015 at 18:21

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:

'\\x24326224313224483352757749766438764134333757365142464f4f4f464959664d66673575‌​467873754e466250716f3166375753696955556b2e36'

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
                Great answer! With bcrypt==3.1.7 the following worked for me: flask_bcrypt.generate_password_hash(password).decode('utf8')
– ryuzakyl
                Aug 8, 2019 at 16:59

You need to apply .decode('utf-8') to your self.password:

def set_password(self, password):
    """Set password."""
    self.password = bcrypt.generate_password_hash(password).decode('utf-8')
                You Sir are a legend. Thanks. I am using the cookie cutter flask app with Python 3.5 and postgresql. Can't use a binary column as suggested above because it causes unicode errors. your suggestd change in models.py where set_password is works a charm.
– Tim Richardson
                Nov 15, 2016 at 4:33

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.

Great answer, solved my problem! Do you know why you have to store the value as a binary with postgres but can keep it a character with e.g. SQLite? – Dominique Paul Nov 3, 2020 at 22:47 @Dominique Paul you could also store it as a string with postgres, you just have to do the right conversions so that when you roundtrip the value through the ORM you get the right thing back. – thclark Nov 4, 2020 at 8:49

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.

Regenerating the hash worked for me, but I forgot to save the original hash to see the difference. It may be that I got the original hash from another machine using different versions of libraries. – eel ghEEz Feb 15, 2017 at 17:06

Basically you would like to encode your data before the hash: password.encode('utf-8'). If it comes as unicode it may raise errors. Have a look here also: https://github.com/maxcountryman/flask-bcrypt/issues/9

Reading the current source of flask-bcrypt, the input is encoded automatically based on the current version of Python. So this is no longer necessary... – Samuel Jaeschke Apr 7, 2016 at 1:31 @ralphie9224 I switched to a different worker supervisor, from Apache mod_wsgi to uWSGI with nginx. But it depends on your problem. What have you tried? – Samuel Jaeschke May 6, 2016 at 2:36 the answer from smewp below is a very small change to one line (in models.py of the user blueprint if you are using a typical flask app). – Tim Richardson Nov 15, 2016 at 4:36 For me the answer was surprisingly simple. The documentation states that for Python 3 you need to use method decode pw_hash = bcrypt.generate_password_hash(‘hunter2’).decode(‘utf-8’) when you hash the password prior to storage. flask-bcrypt.readthedocs.io/en/latest – yeamusic21 Oct 20, 2020 at 3:22

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

You cannot just delete the users from the table. It's like saying: "delete your application and create a new one". But you are right about the python and bcrypt versions. – kodeart Dec 25, 2017 at 7:45

I had a similar problem. My code for checking the password was as follows:

if check_password_hash(form.password.data, user.pw_hashed):

When i reversed the order to:

if check_password_hash(user.pw_hashed, form.password.data):

It worked well.

I had the same problem. It turned out that the username and password combination I was trying to check was not hashed in the first place. Make sure that the password for the username you are trying to check is already hashed and not plain text. If the password is saved in plain text not hashed, you will get 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.

def password(self, value): bvalue = bytes(value, 'utf-8') temp_hash = bcrypt.hashpw(bvalue, bcrypt.gensalt()) self._password = temp_hash.decode('utf-8') def check_password(self, value): return bcrypt.checkpw(value.encode('utf-8'), self._password.encode('utf-8'))

thclark's answer - declare the password column as a binary type - is the most correct, but I thought I'd dig into what is happening, specifically with a Postgresql backend.

The problem is that the password hash generated by flask-bcrypt, when saved in a SQLAlchemy String column, is mysteriously transformed at some point, such that when the value retrieved from the database is passed to flask-bcrypt's check_password_hash function we get an Invalid Salt error.

The "transformation" occurs because it turns out that SQLAlchemy, as far as I can tell, does not require the values assigned to String or Unicode columns to be strings*. Instead, the value is ultimately passed to the DBAPI connector - let's assume it psycopg2 in this case - and the connector tries to adapt the value to fit into whatever SQL SQLAlchemy has generated.

Psycopg2 adapts binary values such as bytes by converting them to the Postgresql binary string representation. If the password column was declared as LargeBinary then the value would be round-tripped correctly. As it is, the binary string representation is stored in the String column. Thus b'$2b$10$0Sfngi1XzpgxDkZPVcaolOHYu3h6IcN.ZHE4E8lWj0RuMGuVUvkHO' becomes '\x243262243130243053666e676931587a706778446b5a505663616f6c4f48597533683649634e2e5a48453445386c576a3052754d47755655766b484f' in the database.

The binary string representation is essentially the bytes converted to hex, so converting between the two representations isn't too difficult:

>>> bs = b'$2b$10$0Sfngi1XzpgxDkZPVcaolOHYu3h6IcN.ZHE4E8lWj0RuMGuVUvkHO'
>>> s = '\\x243262243130243053666e676931587a706778446b5a505663616f6c4f48597533683649634e2e5a48453445386c576a3052754d47755655766b484f'
>>> bs.hex() == s[2:]
>>> bytes.fromhex(s[2:]) == bs

So, the hash is being converted into a value suitable for insertion into a Postgresql BYTEA column, so we should declare our model's password column as LargeBinary, or sqlalchemy.dialects.postgresql.BYTEA.

Encoding the password prior to hashing is redundant - flask-bcrypt does this automatically.

If you are stuck with the password column as a String then decoding the hash before writing to the database makes sense. It's probably sufficient to decode as ASCII.

* I don't know why SQLAlchemy takes this lenient approach. At a guess it's based on pragmatism: if you can insert bytes into VARCHAR columns using psycopg2, why should SQLAlchemy try to stop you? At least you get a warning if you try it on a Unicode column. Perhaps the arrival of type hints in SQLAlchemy 2.0 will change this behaviour.

I had a similar issue (invalid salt), but nobody here mentioned this solution. Be aware with naming when creating a new bcrypt object:

As documentation states:

Namespacing Issues
It’s worth noting that if you use the format, bcrypt = Bcrypt(app) you are effectively overriding the bcrypt module. Though it’s unlikely you would need to access the module outside of the scope of the extension be aware that it’s overriden.
Alternatively consider using a different name, such as flask_bcrypt = Bcrypt(app) to prevent naming collisions.
                This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
– user13524876
                Mar 22, 2022 at 0:02

For me, I was trying to explore flask, and ran into the same issue when validating the hash - I was passing the raw sql to db to get the hashed password, and then check it. I realized after reading through here that sqlalchemy will return a tuple of values on query. So just looped through it and then passed the array index to. It works, may be the issue was that we passed the tuple values to check_password. I am new to python so do let me know if I can improve or I have thought it correctly.

def post(self):
    name = request.form['EmailField1']
    secpass = request.form['password1']
    src_session = initialize_db()[0]
    query= ('select "Password" from login where "Email" =' )+("'%s'" ";") %(name)
    user = src_session.execute(query)
    users=user.fetchall()
    for user in users:
      authorized = password_hash.check_password(user[0],secpass)
      if not authorized:
         return {'error': 'Email or password invalid'}, 401
        

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.