我正在使用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()])
答案 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_wsgi
或httpd.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系统,编码始终 已编码的字符串。第二个编码过程创建一个无效的哈希,如下所示:
'\\x24326224313224483352757749766438764134333757365142464f4f4f464959664d66673575467873754e466250716f3166375753696955556b2e36'
正确的哈希看起来像这样:
$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)
thclark 的 answer - 将密码列声明为二进制类型 - 是最正确的,但我想我会深入研究正在发生的事情,特别是使用 Postgresql 后端。>
问题在于,flask-bcrypt 生成的密码哈希,当保存在 SQLAlchemy String
列中时,会在某个时候神秘地转换,这样当从数据库中检索到的值传递给flask-bcrypt 的check_password_hash
函数,我们收到 Invalid Salt
错误。
发生“转换”是因为据我所知,SQLAlchemy 并不要求分配给 String
或 Unicode
列的值是字符串*。相反,该值最终会传递给 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 ...