Являются ли MD5-хэшированные пароли из крипты PHP() переносными в поле пароля Django?
я портирую кучу учетных записей пользователей с устаревшего PHP-сайта на новый и блестящий сайт на основе Django. Куча паролей хранится как хэш-выход MD5 из PHP crypt()
3 ответов
к сожалению, невозможно преобразовать их в формат Django (хотя есть возможный маршрут, который вы можете взять, который получит ваши хэши импортированы, подробно ниже).
соленый алгоритм md5 Django использует очень простой алгоритм:md5(salt + password)
, который затем кодируется в шестнадцатеричном.
С другой стороны, хэши, выводимые PHP crypt()
, которые начинаются с $
- это не просто MD5-хэшей. Вместо этого они используют алгоритм хэширования паролей, известный как MD5-это склеп. Это намного сложнее (и безопасно), чем простой хэш md5. На связанной странице есть раздел, который описывает формат и алгоритм MD5-Crypt. Невозможно перевести его в формат Django, так как он не поддерживает алгоритм в его коде.
В То Время Как Django тут есть код, который называется stdlib Python crypt()
функция, то, как Django искажает хэши, означает, что нет простого способа получить начало хэша с $
весь путь через Джанго и в 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... хотя он отлично работает для них :) (есть некоторая бета-документация в источник) редактировать: начиная с 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, поэтому ваши хэши должны быть приняты как есть.
Я в процессе перехода с Wordpress 2.8 на Django 1.8. Как я узнал, Wordpress 2.8 (и, вероятно, будущие версии) хранит пароль в криптоформате MD5 (библиотека phpass). Я попробовал расширение passlib для Django 1.8, но это не сработало для меня. Поэтому я закончил писать пользовательский hasher с криптографическим алгоритмом 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)),
])