Bcrypt哈希返回TypeError("Unicode-objects must be encoded before hashing")和Invalid Salt。

4

我已经查看了所有与此相关的StackOverflow问题,但似乎无法解决。当我对密码进行哈希处理并检查其是否与自身匹配时,使用当前代码会返回TypeError“Unicode-objects must be encoded before hashing”。

from scripts import tabledef
from flask import session
from sqlalchemy.orm import sessionmaker
from contextlib import contextmanager
import bcrypt

(Unrelated Python code...)

def hash_password(password):
     return bcrypt.hashpw(password.encode('utf8'), bcrypt.gensalt())


def credentials_valid(username, password):
    with session_scope() as s:
        user = s.query(tabledef.User).filter(
            tabledef.User.username.in_([username])).first()
        if user:

            return bcrypt.checkpw(password.encode('utf8'), user.password)
        else:
            return False

尝试通过设置user.password= user.password.encode('utf8')来解决此错误时,我收到了“无效的Salt”。

这段代码有什么问题?

更新: 我通过Flask从用户输入中存储密码:

import json
import sys
import os
import plotly
import pandas as pd
import numpy as np
import plotly.graph_objs as go


from scripts import tabledef
from scripts import forms
from scripts import helpers
from flask import Flask, redirect, url_for, render_template, request, session, flash, Markup
from flask_socketio import SocketIO, emit

@app.route('/', methods=['GET', 'POST'])
def login():
    if not session.get('logged_in'):
        form = forms.LoginForm(request.form)
        if request.method == 'POST':
            username = request.form['username'].lower()
            password = request.form['password']
            if form.validate():
                if helpers.credentials_valid(username, password):
                    session['logged_in'] = True
                    session['username'] = username
                    session['email'] = request.form['email']
                    session['password'] = request.form['password']
                    return json.dumps({'status': 'Login successful'})
                return json.dumps({'status': 'Invalid user/pass'})
            return json.dumps({'status': 'Both fields required'})
        return render_template('login.html', form=form)
    user = helpers.get_user()
    return render_template('home.html', user=user)

@app.route('/signup', methods=['GET', 'POST'])
def signup():
    if not session.get('logged_in'):
        form = forms.LoginForm(request.form)
        if request.method == 'POST':
            username = request.form['username'].lower()
            password = helpers.hash_password(request.form['password'])
            email = request.form['email']
            if form.validate():
                if not helpers.username_taken(username):
                    helpers.add_user(username, password, email)
                    session['logged_in'] = True
                    session['username'] = username
                    session['email'] = request.form['email']
                    session['password'] = request.form['password']
                    return json.dumps({'status': 'Signup successful'})
                return json.dumps({'status': 'Username taken'})
            return json.dumps({'status': 'User/Pass required'})
        return render_template('login.html', form=form)
    return redirect(url_for('login'))

这是我遇到的错误信息:

/lib/python3.5/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/suraj/Documents/Programming/current-projects/GW_Dining_Tracker/env/lib/python3.5/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/home/suraj/Documents/Programming/current-projects/GW_Dining_Tracker/env/lib/python3.5/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/suraj/Documents/Programming/current-projects/GW_Dining_Tracker/env/lib/python3.5/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/suraj/Documents/Programming/current-projects/GW_Dining_Tracker/Flaskex-master/app.py", line 34, in login
    if helpers.credentials_valid(username, password):
  File "/home/suraj/Documents/Programming/current-projects/GW_Dining_Tracker/Flaskex-master/scripts/helpers.py", line 64, in credentials_valid
    return bcrypt.checkpw(password.encode('utf8'), user.password)
  File "/home/suraj/Documents/Programming/current-projects/GW_Dining_Tracker/env/lib/python3.5/site-packages/bcrypt/__init__.py", line 101, in checkpw
    raise TypeError("Unicode-objects must be encoded before checking")
TypeError: Unicode-objects must be encoded before checking

第一个函数应该有一个用于返回的标签。打字时出了错误,我道歉。 - sshah98
另外,您没有向我们展示如何存储user.password。但是,它不是bytes的事实(否则您尝试的encode将引发AttributeError)意味着它显然不是从hash_password返回的结果,那么它是什么? - abarnert
你是否将实际密码存储为 user.password,而不是其 bcrypt 值?如果是这样的话:你绝不想这样做;使用 bcrypt(或任何密码哈希器)的整个目的是您无需在某个地方保留实际用户密码,以防泄漏。 - abarnert
我添加了编辑以展示我如何存储密码。我还包括了我收到的错误。但是我将密码作为哈希值存储到数据库中。如果有帮助的话,密码的数据库类型是简单的Column(String())。 - sshah98
我回到电脑前会写一个答案。 - abarnert
显示剩余5条评论
1个回答

8
问题在于你正在从SQLAlchemy的String列中取值并将其传递给bcrypt.checkpwString用于Unicode字符串,它提供的值为str。但是bcrypt仅适用于字节字符串,因此它期望bytes。这就是“Unicode对象必须在哈希之前进行编码”的TypeError告诉你的内容。
根据您使用的数据库后端和DB-API库(对于某些后端,还取决于您的数据库配置),当您将bytess保存到String列时,它可能会保存s.decode(),在这种情况下,您可以只使用user.password.encode()获取相同的字节,但它可能不会这样做。例如,它也可以保存str(s)。在这种情况下,如果哈希是bytesb'abcd',则列值将是字符串"b'abcd'",因此调用encode会得到b"b'abcd'"而不是b'abcd'
处理这个问题的最简洁的方法是使用Binary1 - 或者,也许更好的是Binary(60)2 - 来存储哈希,而不是使用String列。支持Binary的任何DB-API都将按原样存储bytes并将其作为bytes返回,这正是你想要的。

1. Binary是一种可选类型。如果您的DB-ABI中没有它,则相同类型可能可用作BINARY。如果没有,请查看类型列表并尝试其他从_Binary继承的类型。没有“large”在名称或缩写中的那些类型可能会更有效,但否则它们中的任何一个都应该可以工作。

2. 使用默认设置,bcrypt可打印摘要的大小始终为60个字节。数据库通常可以更紧凑地存储类似BINARY(60)的固定宽度字段,并比可变宽度字段(如VARBINARY)更快地搜索它们。仅使用普通的BINARY可能是可以的,但它也可能像VARBINARY一样工作,或者它可能浪费空间并像BINARY(255)一样工作,等等。


1
我也遇到了完全相同的问题——编码错误导致盐值错误。很奇怪,因为我完全按照文档操作,并且在非常基本的用例上没有进行任何自定义。我对bcrypt和sqlAlchemy感到很恼火,因为我不得不将我的数据库列更改为二进制格式,这样一个基本的用例就出现了这个错误。我感谢这个问题和答案!否则调试起来会很困难。 - Mote Zart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接