Управление паролями в Django
Управление паролями — это то, что обычно не стоит изобретать заново, и Django стремится предоставить безопасный и гибкий набор инструментов для управления паролями пользователей. В этом документе описывается, как Django хранит пароли, как можно настроить хэширование хранения и некоторые утилиты для работы с хэшированными паролями.
См. также
Даже если пользователи используют сильные пароли, злоумышленники могут перехватывать их соединения. Используйте HTTPS, чтобы избежать отправки паролей (или любых других конфиденциальных данных) по незащищённым HTTP-соединениям, так как они будут уязвимы для перехвата паролей.
Как Django хранит пароли
Django предоставляет гибкую систему хранения паролей и по умолчанию использует PBKDF2.
Атрибут password объекта User представляет собой строку в этом формате:
<algorithm>$<iterations>$<salt>$<hash>
Это компоненты, используемые для хранения пароля пользователя, разделенные символом «доллар»: алгоритм хэширования, количество итераций алгоритма (фактор сложности), случайный соль и результирующий хэш пароля. Алгоритм — это один из ряда односторонних алгоритмов хэширования или хранения паролей, которые может использовать Django; см. ниже. Итерации описывают количество раз, когда алгоритм выполняется над хэшем. Соль — это случайное начальное значение, а хэш — результат односторонней функции.
По умолчанию Django использует алгоритм PBKDF2 с хэшем SHA256, механизм расширения пароля, рекомендуемый NIST. Это должно быть достаточно для большинства пользователей: это достаточно безопасно, и для взлома требуется огромное количество вычислительного времени.
Однако, в зависимости от ваших требований, вы можете выбрать другой алгоритм или даже использовать пользовательский алгоритм, чтобы соответствовать вашей конкретной ситуации безопасности. Опять же, большинству пользователей это не потребуется — если вы не уверены, вероятно, и не нужно. Если вы все же хотите, прочитайте дальше:
Django выбирает используемый алгоритм, обращаясь к настройке PASSWORD_HASHERS. Это список классов алгоритмов хэширования, которые поддерживает данная установка Django. Первая запись в этом списке (то есть settings.PASSWORD_HASHERS[0]) будет использоваться для хранения паролей, а все остальные записи — это допустимые хэширователи, которые можно использовать для проверки существующих паролей. Это означает, что если вы хотите использовать другой алгоритм, вам нужно изменить PASSWORD_HASHERS, чтобы ваш предпочтительный алгоритм был первым в списке.
Значение по умолчанию для PASSWORD_HASHERS:
PASSWORD_HASHERS = (
'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.CryptPasswordHasher',
)
Это означает, что Django будет использовать PBKDF2 для хранения всех паролей, но будет поддерживать проверку паролей, хранящихся с помощью PBKDF2SHA1, bcrypt, SHA1 и т. д. В следующих разделах описаны несколько распространённых способов, которыми продвинутые пользователи могут изменить эту настройку.
Использование bcrypt с Django
Bcrypt — это популярный алгоритм хранения паролей, специально разработанный для долгосрочного хранения паролей. Он не используется по умолчанию в Django, так как требует использования сторонних библиотек, но поскольку многие люди хотят его использовать, Django поддерживает bcrypt с минимальными усилиями.
Чтобы использовать Bcrypt в качестве алгоритма хранения по умолчанию, выполните следующие действия:
- Установите библиотеку bcrypt. Это можно сделать, выполнив
pip install django[bcrypt], или загрузив библиотеку и установив её с помощьюpython setup.py install. -
Измените
PASSWORD_HASHERS, чтобы поместитьBCryptSHA256PasswordHasherна первое место. То есть в файле настроек вы напишите:PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', 'django.contrib.auth.hashers.BCryptPasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.SHA1PasswordHasher', 'django.contrib.auth.hashers.MD5PasswordHasher', 'django.contrib.auth.hashers.CryptPasswordHasher', )(Вам необходимо сохранить остальные записи в этом списке, иначе Django не сможет обновить пароли; см. ниже).
Всё — теперь ваша установка Django будет использовать Bcrypt в качестве алгоритма хранения паролей по умолчанию.
Обрезка паролей с BCryptPasswordHasher
Разработчики bcrypt обрезают все пароли до 72 символов, что означает, что bcrypt(password_with_100_chars) == bcrypt(password_with_100_chars[:72]). Исходный BCryptPasswordHasher не имеет специальной обработки и поэтому также подвержен этому скрытому ограничению длины пароля. BCryptSHA256PasswordHasher решает эту проблему, сначала хэшируя пароль с помощью sha256. Это предотвращает обрезку пароля, поэтому его следует предпочесть BCryptPasswordHasher. Практическое последствие этой обрезки довольно незначительно, так как у среднестатистического пользователя длина пароля не превышает 72 символа, а даже при обрезке до 72 символов, вычислительная мощность, необходимая для подбора bcrypt за разумное время, всё равно астрономическая. Тем не менее, мы рекомендуем вам использовать BCryptSHA256PasswordHasher по принципу «лучше перестраховаться».
Другие реализации bcrypt
Существует несколько других реализаций, позволяющих использовать bcrypt с Django. Поддержка bcrypt в Django НЕ совместима напрямую с ними. Чтобы обновить, вам необходимо изменить хэши в вашей базе данных, чтобы они были в формате bcrypt$(raw bcrypt
output). Например: bcrypt$$2a$12$NT0I31Sa7ihGEWpka9ASYrEFkhuTNeBQ2xfZskIiiJeyFXhRgS.Sy.
Увеличение фактора сложности алгоритма паролей
Алгоритмы PBKDF2 и bcrypt используют определённое количество итераций или раундов хэширования. Это намеренно замедляет злоумышленников, делая атаки на хэшированные пароли сложнее. Однако, по мере увеличения вычислительной мощности, количество итераций необходимо увеличивать. Мы выбрали разумное значение по умолчанию (и будем увеличивать его с каждым выпуском Django), но вы можете настроить его вверх или вниз в зависимости от ваших потребностей в безопасности и доступной вычислительной мощности. Для этого вы будете создавать подкласс соответствующего алгоритма и переопределять параметры iterations. Например, чтобы увеличить количество итераций, используемых по умолчанию алгоритмом PBKDF2:
-
Создайте подкласс
django.contrib.auth.hashers.PBKDF2PasswordHasher:from django.contrib.auth.hashers import PBKDF2PasswordHasher class MyPBKDF2PasswordHasher(PBKDF2PasswordHasher): """ A subclass of PBKDF2PasswordHasher that uses 100 times more iterations. """ iterations = PBKDF2PasswordHasher.iterations * 100Сохраните его где-нибудь в вашем проекте. Например, вы можете поместить его в файл, такой как
myproject/hashers.py. -
Добавьте ваш новый хэширователь как первую запись в
PASSWORD_HASHERS:PASSWORD_HASHERS = ( 'myproject.hashers.MyPBKDF2PasswordHasher', '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.CryptPasswordHasher', )
Всё — теперь ваша установка Django будет использовать больше итераций при хранении паролей с помощью PBKDF2.
Обновление паролей
При входе пользователей в систему, если их пароли хранятся с помощью любого алгоритма, отличного от предпочтительного, Django автоматически обновит алгоритм до предпочтительного. Это означает, что старые установки Django будут автоматически становиться более безопасными при входе пользователей, а также означает, что вы можете переключиться на новые (и лучшие) алгоритмы хранения паролей по мере их появления.
Однако Django может обновлять только пароли, использующие алгоритмы, упомянутые в PASSWORD_HASHERS, поэтому, обновляя систему, вы должны убедиться, что никогда не удаляете записи из этого списка. Если вы это сделаете, пользователи, использующие неупомянутые алгоритмы, не смогут обновить свои пароли. Пароли будут обновляться при изменении количества итераций PBKDF2.
Обратите внимание, что если все пароли в вашей базе данных не закодированы в алгоритме по умолчанию, вы можете быть уязвимы для атаки по времени перечисления пользователей из-за разницы между продолжительностью запроса входа в систему для пользователя с паролем, закодированным в нестандартном алгоритме, и продолжительностью запроса входа в систему для несуществующего пользователя (который выполняет хэширование по умолчанию). Вы можете смягчить это, обновляя старые хэши паролей.
Обновление паролей без требования входа в систему
Если у вас есть существующая база данных со старым слабым хэшем, таким как MD5 или SHA1, вы можете сами обновить эти хэши вместо ожидания обновления при входе пользователя (что может никогда не произойти, если пользователь не возвращается на ваш сайт). В этом случае вы можете использовать «обернутый» хэширователь паролей.
В этом примере мы переведём набор хэшей SHA1 на использование PBKDF2(SHA1(пароль)) и добавим соответствующий хэширователь паролей для проверки, ввёл ли пользователь правильный пароль при входе. Предполагается, что мы используем встроенную модель User и что наш проект имеет приложение accounts. Вы можете изменить шаблон, чтобы он работал с любым алгоритмом или с пользовательской моделью.
Сначала мы добавим пользовательский хэширователь:
from django.contrib.auth.hashers import (
PBKDF2PasswordHasher, SHA1PasswordHasher,
)
class PBKDF2WrappedSHA1PasswordHasher(PBKDF2PasswordHasher):
algorithm = 'pbkdf2_wrapped_sha1'
def encode_sha1_hash(self, sha1_hash, salt, iterations=None):
return super(PBKDF2WrappedSHA1PasswordHasher, self).encode(sha1_hash, salt, iterations)
def encode(self, password, salt, iterations=None):
_, _, sha1_hash = SHA1PasswordHasher().encode(password, salt).split('$', 2)
return self.encode_sha1_hash(sha1_hash, salt, iterations)
Миграция данных может выглядеть примерно так:
from django.db import migrations
from ..hashers import PBKDF2WrappedSHA1PasswordHasher
def forwards_func(apps, schema_editor):
User = apps.get_model('auth', 'User')
users = User.objects.filter(password__startswith='sha1$')
hasher = PBKDF2WrappedSHA1PasswordHasher()
for user in users:
algorithm, salt, sha1_hash = user.password.split('$', 2)
user.password = hasher.encode_sha1_hash(sha1_hash, salt)
user.save(update_fields=['password'])
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
# replace this with the latest migration in contrib.auth
('auth', '####_migration_name'),
]
operations = [
migrations.RunPython(forwards_func),
]
Обратите внимание, что эта миграция может занять несколько минут для нескольких тысяч пользователей, в зависимости от скорости вашего оборудования.
Наконец, мы добавим настройку PASSWORD_HASHERS:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'accounts.hashers.PBKDF2WrappedSHA1PasswordHasher',
]
В этот список включите все другие хэширователи, используемые вашим сайтом.
Написание собственного хэширователя паролей
Если вы пишете собственный хэширователь паролей, содержащий фактор сложности, такой как количество итераций, вам следует реализовать метод harden_runtime(self, password, encoded) для устранения разницы во времени выполнения между фактором сложности, заданным в хэше encoded пароля, и фактором сложности по умолчанию хэширователя. Это предотвращает атаку по времени перечисления пользователей из-за разницы между запросом входа в систему для пользователя с паролем, закодированным в более старом количестве итераций, и несуществующим пользователем (который выполняет хэширование с использованием количества итераций по умолчанию).
Принимая PBKDF2 в качестве примера, если encoded содержит 20 000 итераций, а по умолчанию в хэширователе iterations установлено 30 000 итераций, метод должен выполнить password ещё 10 000 итераций PBKDF2.
Если ваш хэширователь не имеет фактора сложности, реализуйте метод как no-op (pass).
Ручное управление паролем пользователя
Модуль django.contrib.auth.hashers предоставляет набор функций для создания и проверки хешированных паролей. Вы можете использовать их независимо от модели User.
-
check_password(password, encoded)[source] -
Если вы хотите вручную аутентифицировать пользователя, сравнив обычный пароль с хешированным паролем в базе данных, используйте удобную функцию
check_password(). Она принимает два аргумента: обычный текст пароля для проверки и полное значение поляpasswordпользователя в базе данных для проверки, и возвращаетTrueесли они совпадают,Falseв противном случае.
-
make_password(password, salt=None, hasher='default')[source] -
Создает хешированный пароль в формате, используемом этим приложением. Она принимает один обязательный аргумент: пароль в виде обычного текста. По желанию, вы можете указать соль и алгоритм хеширования, если не хотите использовать значения по умолчанию (первый элемент в настройке
PASSWORD_HASHERS). В настоящее время поддерживаются следующие алгоритмы:'pbkdf2_sha256','pbkdf2_sha1','bcrypt_sha256'(см. Использование bcrypt с Django),'bcrypt','sha1','md5','unsalted_md5'(только для обратной совместимости) и'crypt'при установленной библиотекеcrypt. Если аргумент пароляNone, возвращается непригодный пароль (который никогда не будет принят функциейcheck_password()).
-
is_password_usable(encoded_password)[source] -
Проверяет, является ли заданная строка хешированным паролем, который может быть проверен с помощью функции
check_password().
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/topics/auth/passwords/