Django 升级未加盐的 MD5 密码不匹配
Django upgrading unsalted MD5 password not matching
我正在迁移一个使用未加盐 MD5 密码的旧系统(恐怖!)。
我知道 Django handles automatically password upgrading,当用户登录时,通过向 settings.py
中的 PASSWORD_HASHERS
列表添加额外的散列器。
但是,我想升级密码而不需要用户登录,also explained in the docs。
所以,我按照文档中的示例实现了自定义哈希器,legacy/hasher.py
:
import secrets
from django.contrib.auth.hashers import PBKDF2PasswordHasher, UnsaltedMD5PasswordHasher
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash):
salt = secrets.token_hex(16)
return super().encode(md5_hash, salt)
def encode(self, password, salt, iterations=None):
md5_hash = UnsaltedMD5PasswordHasher().encode(password, salt="")
return self.encode_md5_hash(md5_hash)
并将其添加到 settings.py
:
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
"legacy.hashers.PBKDF2WrappedMD5PasswordHasher",
]
但是,在 Django shell check_password
中对此进行测试,升级后的密码返回 False。
>>> from django.contrib.auth.hashers import check_password, UnsaltedMD5PasswordHasher
>>> from legacy.hashers import PBKDF2WrappedMD5PasswordHasher
>>> hasher = PBKDF2WrappedMD5PasswordHasher()
>>> test_pwd = '123456'
>>> test_pwd_unsalted_md5 = UnsaltedMD5PasswordHasher().encode(test_pwd, salt='')
>>> print(test_pwd_unsalted_md5)
'827ccb0eea8a706c4c34a16891f84e7b' # this is an example of a password I want to upgrade
>>> upgraded_test_pwd = hasher.encode_md5_hash(test_pwd)
>>> print(upgraded_test_pwd)
pbkdf2_wrapped_md50000$f3aae83b02e8727a2477644eb0aa6560$brqCWW5QuGUoSQ28YNPGUwTLEwZOuMNheN2RxVZGtHQ=
>>> check_password(test_pwd, upgraded_test_pwd)
False
我已经研究了 other similar 个 SO 问题,但也没有找到合适的解决方案。
简答:通过不考虑提供的salt
,在验证 Django 时不能(可能)得出相同的编码密码。
之所以会出现这种情况,是因为你凭空生成了"salt",而忽略了传入的salt
。事实上,如果我们看一下您的实施,我们会看到:
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash):
<b>salt</b> = secrets.token_hex(16) # generating random salt
return super().encode(md5_hash, salt)
def encode(self, password, <b>salt</b>, iterations=None):
md5_hash = UnsaltedMD5PasswordHasher().encode(password, salt='')
return self.encode_md5_hash(md5_hash)
传递给encode(..)
方法的salt
因此被忽略。
这意味着如果您稍后想要验证密码,Django 将使用它存储的salt
调用encode(..)
(在您的情况下,那是编码密码的第二部分,所以 f3aae83b02e8727a2477644eb0aa6560
),但你决定扔掉它,并用不同的盐生成密码,因此编码密码不再与你存储的密码匹配在数据库中。
我建议使用盐,例如:
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash, <b>salt</b>):
return super().encode(md5_hash, <b>salt</b>)
def encode(self, password, <b>salt</b>, iterations=None):
md5_hash = UnsaltedMD5PasswordHasher().encode(password, salt='')
return self.encode_md5_hash(md5_hash<b>, salt</b>)
我正在迁移一个使用未加盐 MD5 密码的旧系统(恐怖!)。
我知道 Django handles automatically password upgrading,当用户登录时,通过向 settings.py
中的 PASSWORD_HASHERS
列表添加额外的散列器。
但是,我想升级密码而不需要用户登录,also explained in the docs。
所以,我按照文档中的示例实现了自定义哈希器,legacy/hasher.py
:
import secrets
from django.contrib.auth.hashers import PBKDF2PasswordHasher, UnsaltedMD5PasswordHasher
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash):
salt = secrets.token_hex(16)
return super().encode(md5_hash, salt)
def encode(self, password, salt, iterations=None):
md5_hash = UnsaltedMD5PasswordHasher().encode(password, salt="")
return self.encode_md5_hash(md5_hash)
并将其添加到 settings.py
:
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
"legacy.hashers.PBKDF2WrappedMD5PasswordHasher",
]
但是,在 Django shell check_password
中对此进行测试,升级后的密码返回 False。
>>> from django.contrib.auth.hashers import check_password, UnsaltedMD5PasswordHasher
>>> from legacy.hashers import PBKDF2WrappedMD5PasswordHasher
>>> hasher = PBKDF2WrappedMD5PasswordHasher()
>>> test_pwd = '123456'
>>> test_pwd_unsalted_md5 = UnsaltedMD5PasswordHasher().encode(test_pwd, salt='')
>>> print(test_pwd_unsalted_md5)
'827ccb0eea8a706c4c34a16891f84e7b' # this is an example of a password I want to upgrade
>>> upgraded_test_pwd = hasher.encode_md5_hash(test_pwd)
>>> print(upgraded_test_pwd)
pbkdf2_wrapped_md50000$f3aae83b02e8727a2477644eb0aa6560$brqCWW5QuGUoSQ28YNPGUwTLEwZOuMNheN2RxVZGtHQ=
>>> check_password(test_pwd, upgraded_test_pwd)
False
我已经研究了 other similar 个 SO 问题,但也没有找到合适的解决方案。
简答:通过不考虑提供的salt
,在验证 Django 时不能(可能)得出相同的编码密码。
之所以会出现这种情况,是因为你凭空生成了"salt",而忽略了传入的salt
。事实上,如果我们看一下您的实施,我们会看到:
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash):
<b>salt</b> = secrets.token_hex(16) # generating random salt
return super().encode(md5_hash, salt)
def encode(self, password, <b>salt</b>, iterations=None):
md5_hash = UnsaltedMD5PasswordHasher().encode(password, salt='')
return self.encode_md5_hash(md5_hash)
传递给encode(..)
方法的salt
因此被忽略。
这意味着如果您稍后想要验证密码,Django 将使用它存储的salt
调用encode(..)
(在您的情况下,那是编码密码的第二部分,所以 f3aae83b02e8727a2477644eb0aa6560
),但你决定扔掉它,并用不同的盐生成密码,因此编码密码不再与你存储的密码匹配在数据库中。
我建议使用盐,例如:
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash, <b>salt</b>):
return super().encode(md5_hash, <b>salt</b>)
def encode(self, password, <b>salt</b>, iterations=None):
md5_hash = UnsaltedMD5PasswordHasher().encode(password, salt='')
return self.encode_md5_hash(md5_hash<b>, salt</b>)