繁体   English   中英

flask-bcrypt - ValueError:无效的盐

[英]flask-bcrypt - ValueError: Invalid salt

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

ValueError: Invalid salt

模型.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)

视图.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)

表格.py

class LoginForm(Form):
    username = StringField('username', validators=[DataRequired()])
    password = PasswordField('password', validators=[DataRequired()])

我的问题类似于@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

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

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

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

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

关于正确列类型的问题帮助我意识到,为了正确的往返,我必须将密码存储为二进制。 尝试将您的列定义替换为:

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

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

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

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

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

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

如果在散列密码时出现任何问题,似乎也会返回此异常。

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用来完成工作)返回ValueError: Invalid salt ,只要调用操作系统的 bcrypt 库返回错误。 因此,如果由于某种原因它根本无法调用 bcrypt 库,它仍然会(错误地)返回Invalid salt错误。

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


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

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

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

WSGIPythonHome /path/to/my/application-virtualenv

有关此配置的更多信息可以在这里找到: 虚拟环境 — mod_wsgi 文档

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


编辑:设置WSGIPythonHome原来不能解决问题。 最后我用nginx切换到uWSGI

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

我相信您使用的是 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):

它运作良好。

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

我有一个类似的问题 - 得到一个:ValueError:无效的盐 - 结果在我的模型中我的列中的字符太少:

password = Column(String(20))

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

password = Column(String(100))

它奏效了。

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

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

  2. 将密码写入数据库时​​,使用convert_to

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

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

只需删除User类中的init方法即可。 效果很好。

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

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

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

Psycopg2 通过将二进制值(例如字节)转换为 Postgresql 二进制字符串表示来适应二进制值。 如果密码列被声明为LargeBinary ,那么该值将被正确地往返。 实际上,二进制字符串表示形式存储在String列中。 Thus b'$2b$10$0Sfngi1XzpgxDkZPVcaolOHYu3h6IcN.ZHE4E8lWj0RuMGuVUvkHO' becomes '\x243262243130243053666e676931587a706778446b5a505663616f6c4f48597533683649634e2e5a48453445386c576a3052754d47755655766b484f' in the database.

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

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

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

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

在散列之前对密码进行编码是多余的——flask-bcrypt 会自动执行此操作。

如果您将密码列作为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.

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

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

对我来说,我试图探索烧瓶,并在验证哈希时遇到了同样的问题——我将原始 sql 传递给 db 以获取哈希密码,然后检查它。 读完这里后,我意识到 sqlalchemy 将在查询中返回一个值元组。 所以我只是循环遍历它,然后将数组索引传递给。 它有效,可能问题是我们将元组值传递给了 check_password。 我是 python 新手,所以如果我可以改进或者我认为正确,请告诉我。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM