flask-bcrypt - ValueError: Invalid salt

flask-bcrypt - ValueError: Invalid salt

我正在使用 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()])

基本上您希望在散列之前对您的数据进行编码:password.encode('utf-8')。如果它以 unicode 形式出现,则可能会引发错误。 也看看这里:https://github.com/maxcountryman/flask-bcrypt/issues/9

如果在散列密码时任何出错,似乎也会return编辑此异常。

来自 hashpw()bcrypt 来源:

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 用来完成工作)returns ValueError: Invalid salt 每当调用 OS 的 bcrypt lib returns 一个错误。因此,如果由于某种原因它根本无法调用 bcrypt 库,它仍然会(错误地)return Invalid salt 错误。

似乎是 bcrypt 包实现中的一个缺陷 - 它应该检查 retval.

的特定值

在我的例子中,错误结果与 运行ning Flask under Apache mod_wsgi in a virtualenv 有关。我可以直接 运行 flask 没有问题(使用 flask-cli),但是当 运行ning 在 mod_wsgi 下时,完全相同的应用程序实例不会成功使用 bcrypt

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

httpd.conf/etc/httpd/conf.d/... 下添加:

WSGIPythonHome /path/to/my/application-virtualenv

有关此配置的更多信息可在此处找到:Virtual Environments — mod_wsgi documentation

我仍然怀疑我的特定问题与我系统的 python 站点包所隐藏的某些内容有关,或者与 python 包含的其他内容有关。


编辑: 设置 WSGIPythonHome 结果并没有解决问题。最后我切换到 uWSGInginx.

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

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

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

我发现生成上面的哈希值,将该二进制值存储在我的字符串密码列中,然后简单地检索它会由于 SQLAlchemy 的类型转换而产生不同的值——与 bcrypt 完全无关!

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

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

我不确定,但建议不同的生产环境和数据库可能会以不同方式处理这种类型转换(在某些情况下可逆,在其他情况下不可逆),也许可以解释@Samuel Jaeschke 取得的混合成功。

这也解释了为什么将输入字符串编码为受限字符集(较早的解决方案)在某些情况下可能有所帮助,而在其他情况下则无济于事 - 如果它导致 to/from 类型转换起作用,那么您将恢复来自数据库的正确哈希值用于比较。

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

我的问题与@tomClark

描述的类似

我使用 Postgres 作为我的 DDBB 和他的 驱动程序,或者 DDBB 系统,编码 always 一个 already encoded字符串。第二个编码过程创建一个无效的散列,如下所示:

'\x24326224313224483352757749766438764134333757365142464f4f4f464959664d66673575‌​467873754e466250716f3166375753696955556b2e36'

正确的散列如下所示:

b$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

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

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

我相信您正在使用 python 3 和 bcrypt0.7.1。首先你必须删除数据库中的用户,然后转到你的模型并将 .decode('utf-8') 添加到 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

之前从表中删除用户

我遇到了类似的问题。我检查密码的代码如下:

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

当我将订单反转为:

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

效果很好。

您完全不需要 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'))

我遇到了同样的问题。 事实证明,我试图检查的用户名和密码组合一开始并没有经过哈希处理。 确保您尝试检查的用户名的密码已经过哈希处理,而不是纯文本。 如果密码以未散列的纯文本形式保存,您将收到此错误。

我有一个类似的问题 - 得到一个:ValueError:无效的盐 - 事实证明,在我的模型中,我的专栏中的字符太少了:

password = Column(String(20))

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

password = Column(String(100))

成功了。

我找到了自己的解决方案 (postgresql):

  1. 密码使用 bytea 数据类型。

  2. 写密码到数据库时,使用convert_to

  3. 从数据库读取密码时,使用convert_from

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

问题在于,当保存在 SQLAlchemy String 列中时,由 flask-bcrypt 生成的密码哈希值在某些时候神秘地发生了转换,因此当从数据库传递给 flask-bcrypt 的 check_password_hash 函数,我们得到一个 Invalid Salt 错误。

发生“转换”是因为据我所知,SQLAlchemy 不需要分配给 StringUnicode 列的值字符串*。相反,该值最终会传递给 DBAPI 连接器 - 在这种情况下我们假设它是 psycopg2 - 连接器会尝试调整该值以适应 SQL SQLAlchemy 生成的任何内容。

Psycopg2 adapts binary values such as bytes by converting them to the Postgresql binary string representation。如果密码列声明为 LargeBinary,则该值将正确往返。实际上,二进制字符串表示形式存储在 String 列中。因此 b'b[=18=]Sfngi1XzpgxDkZPVcaolOHYu3h6IcN.ZHE4E8lWj0RuMGuVUvkHO' 在数据库中变为 '\x243262243130243053666e676931587a706778446b5a505663616f6c4f48597533683649634e2e5a48453445386c576a3052754d47755655766b484f'

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

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

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

因此,散列被转换为适合插入 Postgresql BYTEA 列的值,因此我们应该将模型的 password 列声明为 LargeBinary,或 sqlalchemy.dialects.postgresql.BYTEA.

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

如果您坚持将密码列作为 String,那么在写入数据库之前对哈希进行解码是有意义的。解码为 ASCII 可能就足够了。


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

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

如文档所述:

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.

就我而言,我有一些用户的密码未经哈希处理,当我尝试使用未经哈希处理的用户登录时,应用程序崩溃了。只需查看您的数据库或使用其他用户。

确保您尝试登录的用户的密码在数据库中经过哈希处理 如果他的密码以纯文本形式存储,则会引发该错误