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

发布于 2024-12-03 13:40:16 字数 1349 浏览 7 评论 0原文

我正在将一堆用户帐户从旧的 PHP 网站移植到一个新的、基于 Django 的网站。一堆密码存储为 PHP crypt() 函数(参见第三个示例)。

给定来自遗留应用程序的密码哈希:

$1$f1KtBi.v$nWwBN8CP3igfC3Emo0OB8/

我如何将其转换为 md5$<盐>$<哈希>crypt() MD5 输出似乎使用与 Django 的 MD5 支持不同的字母表(它似乎使用十六进制摘要)。

更新:

有一个类似(且未回答)的问题有一个有趣的潜在解决方案,可以将 PHP 哈希转换为 base-16 编码,但基于一些初步的研究,它似乎没有产生可用的 MD5 十六进制摘要。 :(

具体示例:

具体示例可能会有所帮助。

给定:

  • foo 的密码
  • $1$aofigrjlh

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

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

在 Python 中,我需要能够在给定原始密码和盐的情况下重现该散列或它的一些常规推导。

I'm porting a bunch of user accounts from a legacy PHP website to a new and shiny Django-based site. A bunch of the passwords are stored as the MD5 hash output from PHP's crypt() function (see the third example there).

Given this password hash from the legacy application:

$1$f1KtBi.v$nWwBN8CP3igfC3Emo0OB8/

How might I convert it to the Django form of md5$<salt>$<hash>? The crypt() MD5 output seems to use a different alphabet than Django's MD5 support (which appears to be using a hexdigest).

Update:

There's a similar (and unanswered) question with an interesting potential solution to convert the PHP hash to a base-16 encoding, but based on some initial poking, it doesn't seem to produce a usable MD5 hexdigest. :(

Concrete example:

A concrete example might help.

Given:

  • a password of foo
  • a salt of $1$aofigrjlh

In PHP, crypt('foo', '$1$aofigrjlh') produces a hash of $1$aofigrjl$xLnO.D8x064D1kDUKWwbX..

crypt() is operating in MD5 mode, but it's some wacky Danish translation of the MD5 algorithm (Update: It's MD5-Crypt). Since Python is a Dutch-derived language, Python's crypt module only supports the DES-style of hashing.

In Python, I need to be able to reproduce that hash, or some regular derivation of it, given the original password and salt.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

樱花坊 2024-12-10 13:40:16

不幸的是,不可能将它们转换为 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 的唯一方法。


但是,有一个可能的途径:您可以对 django.contrib.auth.models.User 进行 Monkeypatch,以便它支持普通的 Django 哈希值以及 MD5-Crypt 格式。这样您就可以原样导入哈希值。一种方法是通过重写 User.set_password 和 User.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 的支持,因此您的哈希值应该按原样接受。

Unfortunately, it isn't possible to convert those over to Django's format (though there is a possible route you can take that will get your hashes imported, detailed below).

Django's salted md5 algorithm uses a very simple algorithm: md5(salt + password), which is then encoded to hexidecimal.

On the other hand, the hashes output by PHP's crypt() which begin with $1$ are not simple md5 hashes. Instead, they use a password hashing algorithm known as MD5-Crypt. This is much more complex (and secure) than a simple md5 hash. There's a section in the linked page which describes the MD5-Crypt format & algorithm. There is no way to translate it into Django's format, as it doesn't offer support for the algorithm within it's code.

While Django does have code which called Python's stdlib crypt() function, the way Django mangles the hashes means there's no easy way to get a hash beginning with $1$ all the way through Django and into crypt(); and that's the only way to signal to crypt() that you want to use MD5-Crypt instead of the older DES-Crypt.


However, there is a possible route: you can monkeypatch django.contrib.auth.models.User so that it supports both the normal Django hashes, as well as the MD5-Crypt format. That way you can import the hashes unchanged. One way is to do this manually, by overriding the User.set_password and User.check_password methods.

Another alternative is to use the Passlib library, which contains a Django app that was designed to take care of all this, as well as provide cross-platform support for md5-crypt et al. (Disclaimer: I'm the author of that library). Unfortunately that Django plugin is undocumented, because I haven't tested it much outside of my own django deploys... though it works fine for them :) (There is some beta documentation in the source) edit: As of Passlib 1.6, this is extension is now officially released and documented.

In order to use it, install passlib, and add passlib.ext.django to your list of installed apps. Then, within settings.py, add the following:

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
"""

This will override User.set_password and User.check_password to use Passlib instead of the builtin code. The configuration string above configures passlib to mimic Django's builtin hashes, but then adds support for md5_crypt, so your hashes should then be accepted as-is.

囍笑 2024-12-10 13:40:16

查看 passlib.hash.md5_crypt,作者:passlib 项目。

Check out passlib.hash.md5_crypt, by the awesome passlib project.

揽清风入怀 2024-12-10 13:40:16

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

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

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

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)),
        ])

I am in process of migrating from Wordpress 2.8 to Django 1.8. As I found out Wordpress 2.8 (and probably future versions as well) stores password in MD5 crypto format (phpass library). I tried passlib extension for Django 1.8 but it didn't work for me. So I ended up writing custom hasher with MD5 crypto algorithm.

NOTE: During migration add "md5_crypt" to password hash (user_pass field)

I added MD5CryptPasswordHasher to the top of the list to make it default (in order not to mix up different hashing algorithms, what if I will migrate once again to another platform?) but it can be added to the bottom of the list if one just want to add support for the algorithm for existing users but force new users to migrate to PBKDF2PasswordHasher hasher or other.

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)),
        ])
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文