Могут ли пароли с хэшем MD5 из PHP crypt () переноситься в поле пароля Django? - PullRequest
7 голосов
/ 07 сентября 2011

Я портирую кучу учетных записей пользователей с устаревшего сайта PHP на новый блестящий сайт на Django. Набор паролей хранится в виде хеша MD5 из функции PHP crypt() (см. Третий пример).

С учетом этого хэша пароля из устаревшего приложения:

$1$f1KtBi.v$nWwBN8CP3igfC3Emo0OB8/

Как я могу преобразовать его в форму Django md5$<salt>$<hash>? Вывод crypt() MD5, по-видимому, использует алфавит, отличный от поддержки MD5 в Django (которая, похоже, использует hexdigest).

Обновление:

Существует похожий (и без ответа) вопрос с интересным потенциальным решением для преобразования хеша PHP в кодировку base-16, но, основываясь на некоторых начальных поисках, он, похоже, не дает полезного MD5 hexdigest. (

Конкретный пример:

Конкретный пример может помочь.

Дано:

  • пароль foo
  • соль $1$aofigrjlh

В PHP crypt('foo', '$1$aofigrjlh') создает хэш $1$aofigrjl$xLnO.D8x064D1kDUKWwbX..

crypt() работает в режиме MD5, но это какой-то дурацкий перевод с датского алгоритма MD5 ( Обновление: Это MD5-Crypt). Поскольку Python является голландским языком , модуль Python crypt поддерживает только хеширование в стиле DES.

В Python мне нужно иметь возможность воспроизводить этот хэш или какой-либо его регулярный вывод, учитывая исходный пароль и соль.

Ответы [ 3 ]

7 голосов
/ 07 сентября 2011

К сожалению, невозможно преобразовать их в формат Django (хотя есть возможный маршрут, по которому вы можете импортировать ваши хэши, подробно описано ниже).

Солевой алгоритм Django md5 используеточень простой алгоритм: md5(salt + password), который затем кодируется в шестнадцатеричном формате.

С другой стороны, хэши, выводимые PHP crypt(), которые начинаются с $1$, не являются простыми хешами md5.Вместо этого они используют алгоритм хеширования пароля, известный как MD5-Crypt .Это намного сложнее (и безопаснее), чем простой хэш md5.На связанной странице есть раздел, который описывает формат и алгоритм MD5-Crypt.Нет способа перевести его в формат Django, так как он не предлагает поддержку алгоритма в своем коде.

В то время как Django имеет код, который вызывает функцию Python stdlib crypt(), способ, которым Django искажает хэши, означает, что нет простого способа получить хеш, начинающийся с $1$ на всем протяженииДжанго и в crypt();и это единственный способ сообщить crypt(), что вы хотите использовать MD5-Crypt вместо старого DES-Crypt.


Однако возможен маршрут: вы можете получить monkeypatch django.contrib.auth.models.Userтак что он поддерживает как обычные хеши Django, так и формат MD5-Crypt.Таким образом, вы можете импортировать хэши без изменений.Один из способов - сделать это вручную, переопределив методы User.set_password и User.check_password.

Другой альтернативой является использование библиотеки Passlib , которая содержит приложение Django, разработанное для решения всех этих задач, а также обеспечивает межплатформенную поддержку md5-crypt и др., (Отказ от ответственности: я автор этой библиотеки) . К сожалению, этот плагин Django недокументирован, потому что я не тестировал его вне моих собственных развертываний django ... хотя он прекрасно работает для них :) (В источнике есть некоторая бета-документация) edit : Начиная с 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_password и User.check_password для использования Passlib вместо встроенного кода.Приведенная выше строка конфигурации настраивает passlib для имитации встроенных хэшей Django, но затем добавляет поддержку md5_crypt, поэтому ваши хэши должны быть приняты как есть.

2 голосов
/ 07 сентября 2011

Извлечение passlib.hash.md5_crypt , замечательным проектом passlib .

1 голос
/ 12 июля 2015

Я нахожусь в процессе перехода с Wordpress 2.8 на Django 1.8.Как я выяснил, Wordpress 2.8 (и, возможно, будущие версии) хранит пароль в криптографическом формате MD5 (библиотека phpass).Я попробовал расширение passlib для Django 1.8, но у меня оно не сработало.В итоге я написал собственный хеш с криптографическим алгоритмом 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)),
        ])
...