1

我正在将一个使用 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]

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

4

2 回答 2

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)

有一种方法可以“欺骗”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 必须检查密码是否正确时,它将执行以下操作: - 从数据库中获取哈希 - 在开头{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$ “技巧”,以将其与编码将创建的字符串进行比较。

所以你有它!Django 将使用您的散列器检查不包含美元符号的散列,并且您不必破坏 django 中的任何内容:)

于 2015-01-06T08:31:37.327 回答
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'
于 2012-12-30T05:37:30.617 回答