Django Can';t使用自定义密码\u哈希器进行身份验证

Django Can';t使用自定义密码\u哈希器进行身份验证,django,django-authentication,Django,Django Authentication,我正在使用php将一个网站迁移到Django框架 有一个特定的哈希密码算法,所以我不得不写: #settings.py PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'project.hashers.SHA1ProjPasswordHasher', # that's mine 'django.contrib.auth.hashers.PBKDF2SHA1Pas

我正在使用php将一个网站迁移到Django框架

有一个特定的哈希密码算法,所以我不得不写:

#settings.py
PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'project.hashers.SHA1ProjPasswordHasher',        # that's mine
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
 ...
)
以及:

PBKDF2PasswordHasher
是第一个时,它工作得很好:

>>> from django.contrib.auth import authenticate
>>> u = authenticate(username='root', password='test')
>>> u.password
u'pbkdf2_sha256$10000$EX8BcgPFjygx$HvB6NmZ7uX1rWOOPbHRKd8GLYD3cAsQtlprXUq1KGMk='
>>> exit()
然后我把我的
SHA1ProjPasswordHasher
放在第一位,第一次身份验证非常有效。哈希已更改:

>>> from django.contrib.auth import authenticate
>>> u = authenticate(username='root', password='test')
>>> u.password
'a94a8fe5'
>>> exit()
第二次身份验证失败。无法使用新哈希进行身份验证

>>> from django.contrib.auth import authenticate
>>> u = authenticate(username='root', password='test')
>>> u.password
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'password'

一切正常。我不明白为什么..

只有未加盐的md5哈希不能包含美元符号:

# django/contrib/auth/hashers.py

def identify_hasher(encoded):
    """
    Returns an instance of a loaded password hasher.

    Identifies hasher algorithm by examining encoded hash, and calls
    get_hasher() to return hasher. Raises ValueError if
    algorithm cannot be identified, or if hasher is not loaded.
    """
    if len(encoded) == 32 and '$' not in encoded:
        algorithm = 'unsalted_md5'
    else:
        algorithm = encoded.split('$', 1)[0]
    return get_hasher(algorithm)
因此,最好的方法是将当前密码哈希转换为以下格式:
alg$salt$hash

class SHA1ProjPasswordHasher(BasePasswordHasher):
    """
    Special snowflake algorithm from the first version.
    php code: $pass=substr(sha1(trim($_POST['password'])),0,8);
    """
    algorithm = "unsalted_and_trimmed_sha1"

    def salt(self):
        return ''

    def encode(self, password, salt):
        assert password
        assert '$' not in salt
        hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()[:8]
        return "%s$%s$%s" % (self.algorithm, salt, hash)

    def verify(self, password, encoded):
        algorithm, salt, hash = encoded.split('$', 2)
        assert algorithm == self.algorithm
        encoded_2 = self.encode(password, salt)
        return constant_time_compare(encoded, encoded_2)

    def safe_summary(self, encoded):
        algorithm, salt, hash = encoded.split('$', 2)
        assert algorithm == self.algorithm
        return SortedDict([
            (_('algorithm'), algorithm),
            (_('salt'), mask_hash(salt, show=2)),
            (_('hash'), mask_hash(hash)),
            ])

>来自django.contrib.auth导入验证
>>>x=验证(用户名='root',密码='test')
>>>x
>>>密码
u'unsalted_和_trimmed_sha1$$a94a8fe5'

只有未加盐的md5哈希不能包含美元符号:

# django/contrib/auth/hashers.py

def identify_hasher(encoded):
    """
    Returns an instance of a loaded password hasher.

    Identifies hasher algorithm by examining encoded hash, and calls
    get_hasher() to return hasher. Raises ValueError if
    algorithm cannot be identified, or if hasher is not loaded.
    """
    if len(encoded) == 32 and '$' not in encoded:
        algorithm = 'unsalted_md5'
    else:
        algorithm = encoded.split('$', 1)[0]
    return get_hasher(algorithm)
因此,最好的方法是将当前密码哈希转换为以下格式:
alg$salt$hash

class SHA1ProjPasswordHasher(BasePasswordHasher):
    """
    Special snowflake algorithm from the first version.
    php code: $pass=substr(sha1(trim($_POST['password'])),0,8);
    """
    algorithm = "unsalted_and_trimmed_sha1"

    def salt(self):
        return ''

    def encode(self, password, salt):
        assert password
        assert '$' not in salt
        hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()[:8]
        return "%s$%s$%s" % (self.algorithm, salt, hash)

    def verify(self, password, encoded):
        algorithm, salt, hash = encoded.split('$', 2)
        assert algorithm == self.algorithm
        encoded_2 = self.encode(password, salt)
        return constant_time_compare(encoded, encoded_2)

    def safe_summary(self, encoded):
        algorithm, salt, hash = encoded.split('$', 2)
        assert algorithm == self.algorithm
        return SortedDict([
            (_('algorithm'), algorithm),
            (_('salt'), mask_hash(salt, show=2)),
            (_('hash'), mask_hash(hash)),
            ])

>来自django.contrib.auth导入验证
>>>x=验证(用户名='root',密码='test')
>>>x
>>>密码
u'unsalted_和_trimmed_sha1$$a94a8fe5'

虽然几年过去了,但我还是把我的解决方案放在这里以备将来参考

弗拉德部分正确;django.contrib.auth.hasher中的以下方法似乎迫使您使用包含美元符号的哈希格式来标记django用于决定使用哪个哈希程序的算法

def identify_hasher(encoded):
"""
Returns an instance of a loaded password hasher.
Identifies hasher algorithm by examining encoded hash, and calls
get_hasher() to return hasher. Raises ValueError if
algorithm cannot be identified, or if hasher is not loaded.
"""
# Ancient versions of Django created plain MD5 passwords and accepted
# MD5 passwords with an empty salt.
if ((len(encoded) == 32 and '$' not in encoded) or
(len(encoded) == 37 and encoded.startswith('md5$$'))):
algorithm = 'unsalted_md5'
# Ancient versions of Django accepted SHA1 passwords with an empty salt.
elif len(encoded) == 46 and encoded.startswith('sha1$$'):
algorithm = 'unsalted_sha1'
else:
algorithm = encoded.split('$', 1)[0]
return get_hasher(algorithm)
不过,有一种方法可以“欺骗”django,而不必对django安装进行黑客攻击。您必须创建用于身份验证的身份验证后端。在这里,您将覆盖django的check_password方法。我有一个数据库,其中散列是
{SSHA512}hash
,我无法更改它,因为我必须能够与dovecot通信。因此,我在我的
backends.py
类中添加了以下内容:

def check_password(self, raw_password, user):
        """
        Returns a boolean of whether the raw_password was correct. Handles
        hashing formats behind the scenes.
        """
        def setter(raw_password):
            user.set_password(raw_password)
            user.save(update_fields=["password"])
        return check_password(raw_password, "SSHA512$" + user.password, setter)
这样,当django必须检查密码是否正确时,它将执行以下操作: -从db
{SSHA512}散列中获取散列
-在开头临时附加一个
SSHA512$
字符串,然后进行检查

因此,当您的数据库中有
{SSHA512}散列时
,当django使用此后端时,它将看到
SSHA512${SSHA512}散列

这样,在您的
hasher.py
中,您可以在类中设置
algorithm=“SSHA512”
,这将提示django在这种情况下使用此hasher

您的
def encode(self、password、salt、iterations=None)
方法在
hasher.py
中将以dovecot需要{SSHA512}散列的方式保存散列(您不必在编码方法中做任何奇怪的事情)

但是,您的
def verify(self,password,encoded)
方法必须从传递的编码字符串中去掉SSHA512$“技巧”,以将其与编码将创建的字符串进行比较


好了,给你!Django将使用您的哈希程序来检查不包含美元$符号的哈希,并且您不必在Django内部破坏任何内容:)

虽然几年过去了,但我将我的解决方案放在这里以供将来参考

弗拉德部分正确;django.contrib.auth.hasher中的以下方法似乎迫使您使用包含美元符号的哈希格式来标记django用于决定使用哪个哈希程序的算法

def identify_hasher(encoded):
"""
Returns an instance of a loaded password hasher.
Identifies hasher algorithm by examining encoded hash, and calls
get_hasher() to return hasher. Raises ValueError if
algorithm cannot be identified, or if hasher is not loaded.
"""
# Ancient versions of Django created plain MD5 passwords and accepted
# MD5 passwords with an empty salt.
if ((len(encoded) == 32 and '$' not in encoded) or
(len(encoded) == 37 and encoded.startswith('md5$$'))):
algorithm = 'unsalted_md5'
# Ancient versions of Django accepted SHA1 passwords with an empty salt.
elif len(encoded) == 46 and encoded.startswith('sha1$$'):
algorithm = 'unsalted_sha1'
else:
algorithm = encoded.split('$', 1)[0]
return get_hasher(algorithm)
不过,有一种方法可以“欺骗”django,而不必对django安装进行黑客攻击。您必须创建用于身份验证的身份验证后端。在这里,您将覆盖django的check_password方法。我有一个数据库,其中散列是
{SSHA512}hash
,我无法更改它,因为我必须能够与dovecot通信。因此,我在我的
backends.py
类中添加了以下内容:

def check_password(self, raw_password, user):
        """
        Returns a boolean of whether the raw_password was correct. Handles
        hashing formats behind the scenes.
        """
        def setter(raw_password):
            user.set_password(raw_password)
            user.save(update_fields=["password"])
        return check_password(raw_password, "SSHA512$" + user.password, setter)
这样,当django必须检查密码是否正确时,它将执行以下操作: -从db
{SSHA512}散列中获取散列
-在开头临时附加一个
SSHA512$
字符串,然后进行检查

因此,当您的数据库中有
{SSHA512}散列时
,当django使用此后端时,它将看到
SSHA512${SSHA512}散列

这样,在您的
hasher.py
中,您可以在类中设置
algorithm=“SSHA512”
,这将提示django在这种情况下使用此hasher

您的
def encode(self、password、salt、iterations=None)
方法在
hasher.py
中将以dovecot需要{SSHA512}散列的方式保存散列(您不必在编码方法中做任何奇怪的事情)

但是,您的
def verify(self,password,encoded)
方法必须从传递的编码字符串中去掉SSHA512$“技巧”,以将其与编码将创建的字符串进行比较


好了,给你!Django将使用您的哈希程序检查不包含美元$符号的哈希,并且您不必在Django内部破坏任何内容:)

danihp谢谢,但我在该线程中找不到身份验证和自定义pasword哈希程序的问题。我认为,自定义pasword哈希程序应该在没有自定义身份验证的情况下透明地使用身份验证。因此,我认为问题出在我的代码中的某个地方。你能重复同样的操作吗,但在验证前后都要打印u.password吗?谢谢,但我在该线程中找不到验证和自定义pasword哈希程序的问题。我认为,自定义pasword哈希程序应该在没有自定义身份验证的情况下透明地使用身份验证。因此,我认为问题出在我的代码中的某个地方。请您重复相同的操作,但在验证前后是否打印u.password?