python - 如何利用 werkzeug.security 的 check_password_hash 函数根据现有的加盐 sha1 密码哈希验证正确的密码
问题描述
我正在开发一个新的 python 应用程序,该应用程序需要验证我们数据库中现有的用户凭据。我们的许多遗留应用程序都是基于 PHP 的 Symfony 框架构建的,而这些密码哈希和盐正是源自该框架。在数据库中,算法(sha1)、盐和用户的哈希密码被保留。我试图弄清楚如何使用此信息与模块的check_password_hash
功能正确验证密码werkzeug.security
。然而,到目前为止,我一直没有成功。
出于演示目的,我创建了一个测试用户,并且数据库包含用户的以下值。
测试用户价值
algorithm
:sha1
salt
:e40e1e9addc186828a5554a71527342c
password
(散列):784517f57fbe61179960739e29d7ae925aa4fd5b
测试用户的实际密码为123456
我尝试了以下方法来验证密码。
注意:
1.哈希格式改编自werkzeug.security文档
2.比较哈希方法改编自这个 StackOverflow 答案
from werkzeug.security import generate_password_hash
from werkzeug.security import check_password_hash
# Attempt 1 - Results in False
check_password_hash('sha1$e40e1e9addc186828a5554a71527342c$784517f57fbe61179960739e29d7ae925aa4fd5b','123456')
# Attempt 2 - Results in False
check_password_hash('pbkdf2:sha1$e40e1e9addc186828a5554a71527342c$784517f57fbe61179960739e29d7ae925aa4fd5b','123456')
# Attempt 3
# Step 1: Append the salt_value to the given password and hash it using the same hash function.
generate_password_hash('123456$e40e1e9addc186828a5554a71527342c','sha1')
# sha1$9lUceSsd$60f7dcb3ff9c22d4613e59fcbfed0c463ee4189e
# Step 2: Compare the hash to the hash in the database
# 60f7dcb3ff9c22d4613e59fcbfed0c463ee4189e != 784517f57fbe61179960739e29d7ae925aa4fd5b
# Attempt 4 - Same steps as Attempt 3 except adding salt_length argument
# Step 1: Append the salt_value to the given password and hash it using the same hash function.
generate_password_hash('123456$e40e1e9addc186828a5554a71527342c','sha1',len('e40e1e9addc186828a5554a71527342c'))
# sha1$EbPv6DP0wMyu02UpA6ZYazFvvYvZTVI1$b8252583fea027d42af20c0d0f3eac3fbf468bd1
# Step 2: Compare the hash to the hash in the database
# b8252583fea027d42af20c0d0f3eac3fbf468bd1 != 784517f57fbe61179960739e29d7ae925aa4fd5b
谁能提供一些关于我做错了什么的见解?我应该尝试使用不同的库passlib
吗?我希望这个模块就足够了,因为我熟悉它,创建了一些我们现有的 python 应用程序,将它用于新用户注册,但在一个单独的数据库中。
编辑 - 显示使用函数werkzeug.security
的替代方法check_password_hash
import hashlib
# hashlib.sha1('{salt}{pw}'.encode()).hexdigest() == {pw_hash}
hashlib.sha1('e40e1e9addc186828a5554a71527342c123456'.encode()).hexdigest() == '784517f57fbe61179960739e29d7ae925aa4fd5b'
# True
解决方案
werkzeug.generate_password_hash
想要生成一个盐值。查看源代码,我们可以看到_hash_internal
使用生成的盐值调用。
def generate_password_hash(password, method="pbkdf2:sha256", salt_length=8):
"""Hash a password with the given method and salt with a string of
the given length. The format of the string returned includes the method
that was used so that :func:`check_password_hash` can check the hash.
The format for the hashed string looks like this::
method$salt$hash
This method can **not** generate unsalted passwords but it is possible
to set param method='plain' in order to enforce plaintext passwords.
If a salt is used, hmac is used internally to salt the password.
If PBKDF2 is wanted it can be enabled by setting the method to
``pbkdf2:method:iterations`` where iterations is optional::
pbkdf2:sha256:80000$salt$hash
pbkdf2:sha256$salt$hash
:param password: the password to hash.
:param method: the hash method to use (one that hashlib supports). Can
optionally be in the format ``pbkdf2:<method>[:iterations]``
to enable PBKDF2.
:param salt_length: the length of the salt in letters.
"""
salt = gen_salt(salt_length) if method != "plain" else ""
h, actual_method = _hash_internal(method, salt, password)
return "%s$%s$%s" % (actual_method, salt, h)
如果我们_hash_internal
使用您的sha1
方法调用并提供盐/密码,我们会得到不同的哈希
In [83]: import werkzeug.security
In [84]: h, method = werkzeug.security._hash_internal('sha1', 'e40e1e9addc186828a5554a71527342c', '123456')
In [86]: h == '784517f57fbe61179960739e29d7ae925aa4fd5b'
Out[86]: False
In [85]: h
Out[85]: 'e8c2de9bdc1ab92479e3e55b608a040dad7bf656'
我认为您需要重新访问您的 PHP 代码以查看这些值是如何生成的。
编辑:根据您的评论
In [138]: werkzeug.security._hash_internal('sha1', '', 'e40e1e9addc186828a5554a71527342c123456')
Out[138]: ('784517f57fbe61179960739e29d7ae925aa4fd5b', 'sha1')
如果您想使用check_password_hash
,则不必在哈希上设置盐,而是将其添加到您的密码中:
In [148]: werkzeug.check_password_hash('sha1$$784517f57fbe61179960739e29d7ae925aa4fd5b', 'e40e1e9addc186828a5554a71527342c123456')
Out[148]: True
如果我们查看源代码,我们可以看到从 中check_password_hash
提取method
、salt
和hashval
,pwhash
然后验证散列password
匹配hashval
。我不熟悉 PHP 中的散列,但似乎密码被“加盐”,然后没有加盐(就 werkzeug.security 而言)。
def check_password_hash(pwhash, password):
"""check a password against a given salted and hashed password value.
In order to support unsalted legacy passwords this method supports
plain text passwords, md5 and sha1 hashes (both salted and unsalted).
Returns `True` if the password matched, `False` otherwise.
:param pwhash: a hashed string like returned by
:func:`generate_password_hash`.
:param password: the plaintext password to compare against the hash.
"""
if pwhash.count("$") < 2:
return False
method, salt, hashval = pwhash.split("$", 2)
return safe_str_cmp(_hash_internal(method, salt, password)[0], hashval)
推荐阅读
- ros - 为什么我一直收到此错误消息?xxx.so 未定义对 `absl::StrCat[abi:cxx11](absl::AlphaNum const& ...)` 的引用
- spring - 如何在spring boot中jwt过期时记录
- java - 如何检查字符串中的字符是否包含在JAVA的括号中?
- android - 构造函数中的变量在 Kotlin 中应该是私有的或公有的
- php - Laravel 测试响应是否包含错误?
- c# - 以编程方式安装 ISO 不起作用
- webhooks - 如何在不使用 ifttt 的情况下将 webhook 与谷歌助手集成?
- python - Django 使用欧几里得距离过滤对象
- python - 使用 GTK3 套接字在另一个窗口中嵌入窗口(Python)
- ios - 我想将模态 tableView 单元格数据返回到主视图