无法使用自定义PASSWORD_HASHERS进行身份验证

时间:2012-12-29 22:19:15

标签: django django-authentication

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

习惯了特定的哈希密码算法,所以我不得不写:

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

#hashers.py

import hashlib

from django.contrib.auth.hashers import (BasePasswordHasher, mask_hash)
from django.utils.datastructures import SortedDict
from django.utils.encoding import force_bytes
from django.utils.crypto import constant_time_compare
from django.utils.translation import ugettext_noop as _


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):
        return hashlib.sha1(force_bytes(salt + password)).hexdigest()[:8]

    def verify(self, password, encoded):
        encoded_2 = self.encode(password, '')
        return constant_time_compare(encoded, encoded_2)

    def safe_summary(self, encoded):
        return SortedDict([
            (_('algorithm'), self.algorithm),
            (_('hash'), mask_hash(encoded, show=3)),
            ])

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'

可能是什么问题?感谢。


更新:好的,问题变得更加明确了。当我从这里删除切片时:

return hashlib.sha1(force_bytes(salt + password)).hexdigest()[:8]
一切正常。我不明白为什么......

2 个答案:

答案 0 :(得分:1)

虽然岁月已过,但我已经过去了。我将我的解决方案放在这里供将来参考

弗拉德是部分正确的;来自django.contrib.auth.hashers的以下方法似乎强迫您使用包含美元$符号的哈希格式来标记用于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)

有一种方法可以使用&#34;技巧&#34; django没有黑客你的django安装。您必须创建用于身份验证的身份验证后端。在那里,您将覆盖django的check_password方法。我有一个数据库,其中哈希是{SSHA512}hash,我无法改变这一点,因为我必须能够与鸽舍交流。所以我在我的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}hash获取哈希 - 在开头暂时添加SSHA512$字符串,然后检查

因此,当您在数据库中拥有{SSHA512}hash时,当django使用此后端时,它会看到SSHA512${SSHA512}hash

通过这种方式,您可以在hashers.py中设置您的班级algorithm = "SSHA512",这将提示django在这种情况下使用此哈希。

def encode(self, password, salt, iterations=None)中的hashers.py方法会像dovecot需要{SSHA512}散列一样保存哈希值(你不必在编码方法中做任何奇怪的事情)。

你的def verify(self, password, encoded)方法虽然必须剥离SSHA512 $&#34;技巧&#34;从传递的编码字符串中将其与编码的字符串进行比较。

所以你有它! Django将使用你的哈希来检查不包含美元$符号的哈希值,你不必在django中破坏任何东西:)

答案 1 :(得分:0)

只有未加保留的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)),
            ])

>>> from django.contrib.auth import authenticate
>>> x = authenticate(username='root', password='test')
>>> x
<User: root>
>>> x.password
u'unsalted_and_trimmed_sha1$$a94a8fe5'