【发布时间】:2020-01-03 17:03:57
【问题描述】:
我必须添加一个向后兼容的 Django 应用程序,它支持保存在使用 PHP 函数 password_hash() 创建的数据库中的旧密码,其输出类似于
$2y$10$puZfZbp0UGMYeUiyZjdfB.4RN9frEMy8ENpih9.jOEngy1FJWUAHy
(10 轮散列的咸河豚加密算法)
Django 支持以算法名称为前缀的格式,所以如果我使用 BCryptPasswordHasher 作为主要的哈希输出将如下所示:
bcrypt$$2y$10$puZfZbp0UGMYeUiyZjdfB.4RN9frEMy8ENpih9.jOEngy1FJWUAHy
我已经创建了自定义 BCryptPasswordHasher,例如:
class BCryptPasswordHasher(BasePasswordHasher):
algorithm = "bcrypt_php"
library = ("bcrypt", "bcrypt")
rounds = 10
def salt(self):
bcrypt = self._load_library()
return bcrypt.gensalt(self.rounds)
def encode(self, password, salt):
bcrypt = self._load_library()
password = password.encode()
data = bcrypt.hashpw(password, salt)
return f"{data.decode('ascii')}"
def verify(self, incoming_password, encoded_db_password):
algorithm, data = encoded_db_password.split('$', 1)
assert algorithm == self.algorithm
db_password_salt = data.encode('ascii')
encoded_incoming_password = self.encode(incoming_password, db_password_salt)
# Compare of `data` should only be done because in database we don't persist alg prefix like `bcrypt$`
return constant_time_compare(data, encoded_incoming_password)
def safe_summary(self, encoded):
empty, algostr, work_factor, data = encoded.split('$', 3)
salt, checksum = data[:22], data[22:]
return OrderedDict([
('algorithm', self.algorithm),
('work factor', work_factor),
('salt', mask_hash(salt)),
('checksum', mask_hash(checksum)),
])
def must_update(self, encoded):
return False
def harden_runtime(self, password, encoded):
data = encoded.split('$')
salt = data[:29] # Length of the salt in bcrypt.
rounds = data.split('$')[2]
# work factor is logarithmic, adding one doubles the load.
diff = 2 ** (self.rounds - int(rounds)) - 1
while diff > 0:
self.encode(password, salt.encode('ascii'))
diff -= 1
和 AUTH_USER_MODEL 一样:
from django.contrib.auth.hashers import check_password
from django.db import models
class User(models.Model):
id = models.BigAutoField(primary_key=True)
email = models.EmailField(unique=True)
password = models.CharField(max_length=120, blank=True, null=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
EMAIL_FIELD = 'email'
def check_password(self, raw_password):
def setter():
pass
alg_prefix = "bcrypt_php$"
password_with_alg_prefix = alg_prefix + self.password
return check_password(raw_password, password_with_alg_prefix, setter)
设置base.py:
...
AUTH_USER_MODEL = 'custom.User'
PASSWORD_HASHERS = [
'custom.auth.hashers.BCryptPasswordHasher',
]
...
在这种情况下,在验证密码之前,我添加了bcrypt$前缀然后进行验证,但是在数据库中,密码保存在没有bcrypt$的情况下。
它有效,但我想知道是否有其他更简单的方法可以做到这一点,或者可能有人遇到同样的问题?
我想补充一点,PHP 应用程序和新的 Django 都应该支持这两种格式,我不能对旧的 PHP 进行更改。只能在新的 Django 服务器上进行更改。
【问题讨论】:
-
check_password()函数内部的某处,您可以将计算的哈希值与有效值进行比较,您可以执行calculated_hash[7:] == valid_hash_without_prefix。 -
您是说
User.check_password()还是django.contrib.auth.hasher.check_password()?第一个我可以修改但django.contrib.auth.hasher.check_password()我认为我不应该
标签: python django bcrypt django-authentication backwards-compatibility