【问题标题】:flask-bcrypt - ValueError: Invalid saltflask-bcrypt - ValueError:无效的盐
【发布时间】:2015-12-31 16:09:09
【问题描述】:

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

【问题讨论】:

  • 嗯,我觉得您的哈希没有正确存储在您的数据库中。看了一些东西,你似乎正确地使用了 Flask-Bcrypt。 user.password 的值是多少?
  • @RyanO'Donnell 这是user.password'\\x24326224313224483352757749766438764134333757365142464f4f4f464959664d66673575467873754e466250716f3166375753696955556b2e36' 的值

标签: python flask bcrypt


【解决方案1】:

我的问题类似于@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==3.1.7 以下对我有用:flask_bcrypt.generate_password_hash(password).decode('utf8')
【解决方案2】:

就我而言,问题与密码存储期间的类型转换有关。使用bcrypt.generate_password_hash(plaintext) 会返回一个二进制值,例如b'$2b$12$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 类型转换起作用,那么您将从用于比较的数据库。

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

【讨论】:

  • 很好的答案,解决了我的问题!你知道为什么你必须使用 postgres 将值存储为二进制文件,但可以将其保留为一个字符,例如SQLite?
  • @Dominique Paul 你也可以将它存储为 postgres 的字符串,你只需要进行正确的转换,这样当你通过 ORM 来回返回值时,你就会得到正确的东西。
【解决方案3】:

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

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

【讨论】:

  • 你先生是个传奇。谢谢。我正在使用带有 Python 3.5 和 postgresql 的饼干切割烧瓶应用程序。不能使用上面建议的二进制列,因为它会导致 unicode 错误。您在models.py 中建议的更改 set_password 很有效。
  • 您的解决方案效果很好
【解决方案4】:

如果在散列密码时任何事情出错,似乎也会返回此异常。

来自bcrypthashpw() 的来源:

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

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


在我的情况下,错误结果与在 virtualenv 中的 Apache mod_wsgi 下运行 Flask 有关。我可以毫无问题地直接运行flask(使用flask-cli),但是在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 原来不能解决问题。最后我用 nginx 切换到 uWSGI

【讨论】:

  • 重新生成哈希对我有用,但我忘记保存原始哈希以查看差异。可能是我使用不同版本的库从另一台机器上获取了原始哈希。
【解决方案5】:

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

【讨论】:

  • 读取flask-bcrypt的当前源码,输入根据当前Python版本自动编码。所以这不再是必要的......
  • @SamuelJaeschke 那么你做了什么让它发挥作用呢?我遇到了同样的问题
  • @ralphie9224 我用 nginx 从 Apache mod_wsgi 切换到 uWSGI。但这取决于你的问题。你试过什么?
  • 下面 smewp 的答案是对一行的一个非常小的更改(如果您使用的是典型的烧瓶应用程序,则在用户蓝图的 models.py 中)。
  • 对我来说,答案非常简单。该文档指出,对于 Python 3,当您在存储之前对密码进行哈希处理时,您需要使用方法 decode pw_hash = bcrypt.generate_password_hash(‘hunter2’).decode(‘utf-8’)flask-bcrypt.readthedocs.io/en/latest
【解决方案6】:

我相信您使用的是 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 之前从表中删除用户

【讨论】:

  • 您不能只从表中删除用户。这就像说:“删除您的应用程序并创建一个新应用程序”。但是你对 python 和 bcrypt 版本是对的。
【解决方案7】:

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

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

当我把顺序颠倒到:

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

效果很好。

【讨论】:

    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

      我遇到了类似的问题 - 得到一个:ValueError: Invalid salt - 结果是在我的模型中,我的列中的字符太少了:

      password = Column(String(20))
      

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

      password = Column(String(100))
      

      它成功了。

      【讨论】:

        【解决方案10】:

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

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

        2. 将密码写入db时,使用convert_to

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

        【讨论】:

          【解决方案11】:

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

          【讨论】:

            【解决方案12】:

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

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

            发生“转换”是因为事实证明,据我所知,SQLAlchemy 不需要分配给StringUnicode 列的值是字符串*。相反,该值最终被传递给 DBAPI 连接器 - 在这种情况下假设它是 psycopg2 - 连接器尝试调整该值以适应 SQL 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 列声明为 LargeBinarysqlalchemy.dialects.postgresql.BYTEA

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

            如果您将密码列作为String 卡住,那么在写入数据库之前解码散列是有意义的。解码为 ASCII 可能就足够了。


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

            【讨论】:

              【解决方案13】:

              我遇到了类似的问题(无效的盐),但这里没有人提到这个解决方案。 在创建新的 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.
              

              【讨论】:

                【解决方案14】:

                在我的例子中,我有一些用户的未哈希密码,当我尝试使用未哈希密码的用户登录时,应用程序崩溃了。只需查看您的数据库或使用其他用户。

                【讨论】:

                  【解决方案15】:

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

                  【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-02-21
                  • 2014-01-31
                  • 2018-07-08
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-10-13
                  相关资源
                  最近更新 更多