繁体   English   中英

来自PHP的crypt()的MD5哈希密码是否可移植到Django密码字段?

[英]Are MD5-hashed passwords from PHP's crypt() portable to the Django password field?

我正在将一堆用户帐户从遗留的PHP网站移植到一个新的,闪亮的基于Django的网站。 一堆密码存储为PHP的crypt()函数的MD5哈希输出(参见那里的第三个例子)。

从遗留应用程序中获取此密码哈希值:

$1$f1KtBi.v$nWwBN8CP3igfC3Emo0OB8/

我怎么能把它转换为md5$<salt>$<hash>的Django形式? crypt() MD5输出似乎使用与Django的MD5支持(似乎使用hexdigest)不同的字母表。

更新:

有一个类似(并且没有答案)的问题,有一个有趣的潜在解决方案,可以将PHP哈希转换为base-16编码,但基于一些初步的戳,它似乎没有产生可用的MD5 hexdigest。 :(

具体例子:

一个具体的例子可能有所帮

鉴于:

  • foo的密码
  • $1$aofigrjlh的盐

在PHP中, crypt('foo', '$1$aofigrjlh')产生$1$aofigrjl$xLnO.D8x064D1kDUKWwbX.的哈希值$1$aofigrjl$xLnO.D8x064D1kDUKWwbX.

crypt()在MD5模式下运行,但它是MD5算法的一些古怪的丹麦语翻译更新:它是MD5-Crypt)。 由于Python是一种源自荷兰语的语言 ,因此Python的crypt模块仅支持DES样式的散列。

在Python中,我需要能够在给定原始密码和salt的情况下重现该哈希或其定期派生。

不幸的是,将它们转换为Django的格式是不可能的(尽管你可以采取一种可能的方法来获取你的哈希值,详见下文)。

Django的salted md5算法使用了一个非常简单的算法: md5(salt + password) ,然后编码为十六进制。

另一方面,PHP的crypt()输出的哈希值以$1$开头并不是简单的md5哈希值。 相反,他们使用称为MD5-Crypt的密码散列算法。 这比简单的md5哈希要复杂得多(而且安全)。 链接页面中有一节描述了MD5-Crypt格式和算法。 没有办法将其转换为Django的格式,因为它没有在其代码中提供对算法的支持。

虽然Django 确实拥有调用Python的stdlib crypt()函数的代码,但是Django破坏哈希的方式意味着没有简单的方法可以获得从$1$开始一直到Django并进入crypt()的哈希; 这是向crypt()发出信号的唯一方法,你想使用MD5-Crypt而不是旧的DES-Crypt。


但是,有一条可能的路径:你可以monkeypatch django.contrib.auth.models.User这样它既支持普通的Django哈希,也支持MD5-Crypt格式。 这样你可以不加改变地导入哈希。 一种方法是通过重写User.set_passwordUser.check_password方法手动完成此操作。

另一种方法是使用Passlib库,它包含一个专门用于处理所有这些的Django应用程序,并为md5-crypt等提供跨平台支持。 (免责声明:我是该图书馆的作者) 不幸的是,Django插件没有文档,因为我没有在我自己的django部署之外测试它...虽然它适用于他们:)( 源代码中有一些测试文档) 编辑 :截至Passlib 1.6,这个是扩展现在正式发布和记录

要使用它,请安装passlib,并将passlib.ext.django添加到已安装的应用程序列表中。 然后,在settings.py ,添加以下内容:

PASSLIB_CONFIG = """
[passlib]
schemes =
    md5_crypt,
    django_salted_sha1, django_salted_md5,
    django_des_crypt, hex_md5,
    django_disabled

default = md5_crypt

deprecated = django_des_crypt, hex_md5
"""

这将覆盖User.set_passwordUser.check_password以使用Passlib而不是内置代码。 上面的配置字符串将passlib配置为模仿Django的内置哈希值,但随后添加了对md5_crypt的支持,因此您的哈希值应该按原样接受。

退房passlib.hash.md5_crypt ,由真棒passlib项目。

我正在从Wordpress 2.8迁移到Django 1.8。 正如我发现Wordpress 2.8(以及可能的未来版本)以MD5加密格式(phpass库)存储密码。 我为Django 1.8尝试了passlib扩展,但它对我不起作用。 所以我最终用MD5加密算法编写了自定义哈希。

注意:在迁移期间,将“md5_crypt”添加到密码哈希(user_pass字段)

我将MD5CryptPasswordHasher添加到列表顶部以使其成为默认值(为了不混淆不同的哈希算法,如果我将再次迁移到另一个平台怎么办?)但如果只是一个,它可以添加到列表的底部想要为现有用户添加对算法的支持,但强制新用户迁移到PBKDF2PasswordHasher hasher或其他用户。

settings.py

PASSWORD_HASHERS = (
    'your_project_name.hashers.MD5CryptPasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    'django.contrib.auth.hashers.BCryptPasswordHasher',
    'django.contrib.auth.hashers.SHA1PasswordHasher',
    'django.contrib.auth.hashers.MD5PasswordHasher',
    'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher',
    'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
    'django.contrib.auth.hashers.CryptPasswordHasher',
)

hashers.py

import math
import hashlib
from django.contrib.auth.hashers import BasePasswordHasher
from django.utils.crypto import get_random_string
from django.contrib.auth.hashers import mask_hash
from collections import OrderedDict
from django.utils.translation import ugettext, ugettext_lazy as _

itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
def encode64(inp, count):
    outp = ''
    cur = 0
    while cur < count:
        value = inp[cur]
        cur += 1
        outp += itoa64[value & 0x3f]
        if cur < count:
            value |= (inp[cur] << 8)
        outp += itoa64[(value >> 6) & 0x3f]
        if cur >= count:
            break
        cur += 1
        if cur < count:
            value |= (inp[cur] << 16)
        outp += itoa64[(value >> 12) & 0x3f]
        if cur >= count:
            break
        cur += 1
        outp += itoa64[(value >> 18) & 0x3f]
    return outp.encode()

def crypt_private(pw, algorithm, code, salt, iterations):
    header = "%s$%s$%s%s" % (algorithm, code, itoa64[int(math.log(iterations, 2))], salt)
    pw = pw.encode()
    salt = salt.encode()
    hx = hashlib.md5(salt + pw).digest()
    while iterations:
        hx = hashlib.md5(hx + pw).digest()
        iterations -= 1
    return header + encode64(hx, 16).decode()


def get_md5_crypto_hash_params(encoded):
    algorithm, code, rest = encoded.split('$', 2)
    count_log2 = itoa64.find(rest[0])
    iterations = 1 << count_log2
    salt = rest[1:9]
    return (algorithm, salt, iterations)

class MD5CryptPasswordHasher(BasePasswordHasher):
    """
    The Salted MD5 Crypt password hashing algorithm that is used by Wordpress 2.8
    WARNING!
    The algorithm is not robust enough to handle any kind of MD5 crypt variations
    It was stripped and refactored based on passlib implementations especially for Wordpress 2.8 format
    """
    algorithm = "md5_crypt"

    iterations = 8192
    code = "P" # Modular Crypt prefix for phpass
    salt_len = 8

    def salt(self):
        return get_random_string(salt_len)

    def encode(self, password, salt):
        assert password is not None
        assert salt != ''
        return crypt_private(password, self.algorithm, self.code, salt, self.iterations)
        pass

    def verify(self, password, encoded):
        algorithm, salt, iterations = get_md5_crypto_hash_params(encoded)
        assert algorithm == self.algorithm
        return crypt_private(password, algorithm, self.code, salt, iterations) == encoded


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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM