flask-bcrypt - ValueError:无效的盐

时间:2015-12-31 16:09:10

标签: python flask bcrypt

我正在使用Flask和flask-Bcrypt完成一个简单的用户登录。但是,当尝试使用存储在我的数据库中的用户登录时,我不断收到此错误

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()])

12 个答案:

答案 0 :(得分:9)

如果在散列密码时出现任何错误,也会返回此异常。

来自bcrypt的{​​{1}}来源:

hashpw()

只要对OS的bcrypt lib的调用返回错误,hashed = _bcrypt.ffi.new("unsigned char[]", 128) retval = _bcrypt.lib.crypt_rn(password, salt, hashed, len(hashed)) if not retval: raise ValueError("Invalid salt") 包(bcrypt用于完成工作)就会返回Flask-Bcrypt。因此,如果由于某种原因它根本无法调用bcrypt lib,它仍将(错误地)返回ValueError: Invalid salt错误。

似乎是Invalid salt包实现中的一个缺陷 - 它应该检查bcrypt的具体值。

在我的情况下,错误结果与retval中的Apache mod_wsgi下的Flask相关。我可以直接运行flask而没有问题(使用virtualenv),但在flask-cli下运行时,完全相同的应用实例无法成功使用bcrypt

通过修改我的Apache配置以使用virtualenv作为mod_wsgi的主要Python环境来解决问题。

mod_wsgihttpd.conf下添加:

/etc/httpd/conf.d/...

有关此配置的更多信息,请访问:Virtual Environments — mod_wsgi documentation

我仍然怀疑我的特定问题与我的系统的python网站包或其他与python包含相关的东西有关。

修改:设置WSGIPythonHome /path/to/my/application-virtualenv 并未解决问题。最后,我使用 nginx 切换到 uWSGI

答案 1 :(得分:9)

我的问题类似于@tomClark

所描述的

我使用Postgres作为我的DDBB和他的驱动程序,或DDBB系统,编码始终 已编码的字符串。第二个编码过程创建一个无效的哈希,如下所示:

'\\x24326224313224483352757749766438764134333757365142464f4f4f464959664d66673575‌​467873754e466250716f3166375753696955556b2e36'

正确的哈希看起来像这样:

$2b$12$Wh/sgyuhro5ofqy2.5znc.35AjHwTTZzabz.uUOya8ChDpdwvROnm

要解决此问题,我首先解码哈希到 utf8 ,然后将其保存到DDBB。

示例代码:

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

答案 2 :(得分:7)

就我而言,问题与密码存储期间进行的类型转换有关。使用bcrypt.generate_password_hash(plaintext)会返回二进制值,例如b'$2b$12$zf/TxXZ4JJZ/lFX/BWALaeo0M.wNXrQXLqIFjmZ0WebqfVo9NES56'

与我的一样,您的密码列设置为字符串:

password = db.Column(db.String, nullable=False)

我发现上面生成哈希值,将二进制值存储在我的字符串密码列中,然后只需检索它就会因SQLAlchemy的类型转换而产生不同的值 - 与bcrypt完全无关! / p>

A question on correct column type帮助我意识到,为了正确的往返,我必须将密码存储为二进制文件。尝试使用以下代码替换列定义:

password = db.Column(db.Binary(60), nullable=False)

我不确定,但建议不同的制作环境和数据库可以不同地处理这种类型的转换(在某些情况下是可逆的,而不是在其他情况下),或许可以解释@Samuel Jaeschke所带来的混合成功。

这也解释了为什么将输入字符串编码为约束字符集(较早的解决方案)可能在某些情况下有所帮助而不是其他情况 - 如果它导致转换类型转换工作,那么您将恢复正确来自数据库的哈希值进行比较。

无论如何,这为我解决了这个问题。

答案 3 :(得分:3)

基本上,您希望在哈希值之前对数据进行编码:password.encode(&#39; utf-8&#39;)。如果它作为unicode,它可能会引发错误。 请看这里:https://github.com/maxcountryman/flask-bcrypt/issues/9

答案 4 :(得分:3)

您需要将.decode('utf-8')应用于self.password

def set_password(self, password):
    """Set password."""
    self.password = bcrypt.generate_password_hash(password).decode('utf-8')

答案 5 :(得分:3)

我相信你使用的是python 3和bcrypt0.7.1。首先,您必须删除数据库中的用户,然后转到模型并将.decode(&#39; utf-8&#39;)添加到generate_password_hash()方法,如下所示:

pw_hash = bcrypt.generate_password_hash(‘hunter2’).decode('utf-8')

或者你可以卸载flask-bcrypt == 0.7.1并安装flask-bcrypt == 0.62。在安装flask-bcrypt == 0.62

之前,请确保从表中删除用户

答案 6 :(得分:1)

我有类似的问题。我检查密码的代码如下:

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

当我将订单撤销到:

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

效果很好。

答案 7 :(得分:1)

我遇到了类似的问题-出现了一个:ValueError:无效的盐-事实证明,在我的模型中,我的列中的字符太少了:

password = Column(String(20))

在我的数据库和模型中,我不得不将其更改为:

password = Column(String(100))

它奏效了。

答案 8 :(得分:0)

使用flask-bcrypt完全不需要bcrypt

做这样的事情:

class User(Base):
    _password = db.Column("password", db.String, nullable=False)

    @hybrid_property
    def password(self):
        return self._password

    @password.setter
    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'))

答案 9 :(得分:0)

我有同样的问题。 原来,我尝试检查的用户名和密码组合没有首先哈希。 确保您要检查的用户名的密码已经被哈希处理,而不是纯文本。 如果密码以纯文本格式保存而不进行哈希处理,则会出现此错误。

答案 10 :(得分:0)

thclarkanswer - 将密码列声明为二进制类型 - 是最正确的,但我想我会深入研究正在发生的事情,特别是使用 Postgresql 后端。

问题在于,flask-bcrypt 生成的密码哈希,当保存在 SQLAlchemy String 列中时,会在某个时候神秘地转换,这样当从数据库中检索到的值传递给flask-bcrypt 的check_password_hash 函数,我们收到 Invalid Salt 错误。

发生“转换”是因为据我所知,SQLAlchemy 并不要求分配给 StringUnicode 列的值是字符串*。相反,该值最终会传递给 DBAPI 连接器——在本例中我们假设它是 psycopg2——并且连接器尝试调整该值以适应 SQLAlchemy 生成的任何内容。

Psycopg2 adapts binary values such as bytes 通过将它们转换为 Postgresql binary string representation。如果密码列被声明为 LargeBinary,那么该值将正确地往返。实际上,二进制字符串表示存储在 String 列中。因此,b'$2b$10$0Sfngi1XzpgxDkZPVcaolOHYu3h6IcN.ZHE4E8lWj0RuMGuVUvkHO' 在数据库中变成了 '\x243262243130243053666e676931587a706778446b5a505663616f6c4f48597533683649634e2e5a48453445386c576a3052754d47755655766b484f'

二进制字符串表示本质上是将字节转换为十六进制,因此在两种表示之间进行转换并不太困难:

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

>>> bytes.fromhex(s[2:]) == bs
True

因此,哈希值被转换为适合插入 Postgresql BYTEA 列的值,因此我们应该将模型的 password 列声明为 LargeBinary 或 {{1} }.

在散列之前对密码进行编码是多余的——flask-bcrypt 这样做automatically

如果您坚持使用密码列作为 sqlalchemy.dialects.postgresql.BYTEA,那么在写入数据库之前解码散列是有意义的。解码为 ASCII 可能就足够了。


* 我不知道为什么 SQLAlchemy 采取这种宽松的方法。猜测它基于实用主义:如果您可以使用 psycopg2 将字节插入到 String 列中,那么 SQLAlchemy 为什么要阻止您?如果您在 VARCHAR 列上尝试,至少会收到警告。也许 SQLAlchemy 2.0 中类型提示的到来会改变这种行为。

答案 11 :(得分:0)

我有一个类似的问题(无效的盐),但这里没有人提到这个解决方案。 创建新的 bcrypt 对象时注意命名:

如文档所述:

out <- scan(text = gsub("[^.0-9]+", ",", x), what = numeric(), 
    sep=",", quiet = TRUE)

str(out)
#num [1:25] 0.989 0.975 0.964 0.937 0.877 0.771 0.962 0.903 0.971 0.867 ...