Управление паролями в 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.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
Это означает, что Django будет использовать PBKDF2 для хранения всех паролей, но будет поддерживать проверку паролей, хранящихся с помощью PBKDF2SHA1, argon2 и bcrypt.
В следующих разделах описаны несколько распространенных способов, которыми продвинутые пользователи могут изменить эту настройку.
Использование Argon2 с Django
Argon2 — победитель конкурса Password Hashing Competition 2015 года, открытого соревнования для выбора алгоритма хэширования следующего поколения. Он разработан таким образом, чтобы его вычисление на специализированном оборудовании не было легче, чем на обычном процессоре.
Argon2 не является значением по умолчанию для Django, поскольку он требует сторонней библиотеки. Однако экспертный совет по хэшированию паролей рекомендует немедленное использование Argon2 вместо других поддерживаемых Django алгоритмов.
Чтобы использовать Argon2 в качестве алгоритма хранения по умолчанию, выполните следующие действия:
- Установите библиотеку argon2-cffi. Это можно сделать, выполнив
python -m pip install django[argon2], что эквивалентноpython -m pip install argon2-cffi, а также любой версии, необходимой изsetup.cfg. -
Измените
PASSWORD_HASHERS, указавArgon2PasswordHasherпервым. То есть, в вашем файле настроек вы напишите:PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.Argon2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ]Сохраните и/или добавьте любые записи в этот список, если вам необходимо, чтобы Django обновлял пароли.
Использование bcrypt с Django
Bcrypt — популярный алгоритм хранения паролей, специально разработанный для долгосрочного хранения паролей. Он не используется по умолчанию в Django, так как требует использования сторонних библиотек, но так как многие пользователи могут захотеть его использовать, Django поддерживает bcrypt с минимальными усилиями.
Чтобы использовать Bcrypt в качестве алгоритма хранения по умолчанию, выполните следующие действия:
- Установите библиотеку bcrypt. Это можно сделать, выполнив
python -m pip install django[bcrypt], что эквивалентноpython -m pip install bcrypt, а также любой версии, необходимой изsetup.cfg. -
Измените
PASSWORD_HASHERS, указавBCryptSHA256PasswordHasherпервым. То есть, в вашем файле настроек вы напишите:PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.Argon2PasswordHasher', ]Сохраните и/или добавьте любые записи в этот список, если вам необходимо, чтобы Django обновлял пароли.
Всё — теперь ваша установка Django будет использовать Bcrypt в качестве алгоритма хранения по умолчанию.
Увеличение фактора сложности алгоритма паролей
PBKDF2 и bcrypt
Алгоритмы 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.Argon2PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ]
Всё — теперь ваша установка Django будет использовать больше итераций при хранении паролей с помощью PBKDF2.
Argon2
Argon2 имеет три атрибута, которые можно настроить:
-
time_costуправляет количеством итераций внутри хэша. -
memory_costуправляет размером памяти, которая должна быть использована во время вычисления хэша. -
parallelismуправляет тем, на скольких процессорах вычисление хэша может быть распараллелено.
Значения этих атрибутов по умолчанию, вероятно, подходят вам. Если вы определите, что хэш пароля слишком быстрый или слишком медленный, вы можете настроить его следующим образом:
- Выберите
parallelismв качестве количества потоков, которые вы можете выделить для вычисления хэша. - Выберите
memory_costв качестве количества КБ памяти, которые вы можете выделить. - Настройте
time_costи измерьте время, которое требуется для хэширования пароля. Выберите значениеtime_cost, которое займет приемлемое для вас время. Еслиtime_cost, установленное в 1, неприемлемо медленно, уменьшитеmemory_cost.
memory_cost интерпретация
Утилита argon2 командной строки и некоторые другие библиотеки интерпретируют параметр memory_cost по-другому, чем значение, используемое Django. Преобразование дается выражением memory_cost == 2 ** memory_cost_commandline.
Обновление паролей
При входе в систему, если их пароли хранятся с использованием алгоритма, отличного от предпочтительного, Django автоматически обновит алгоритм до предпочтительного. Это означает, что старые установки Django станут более безопасными по мере входа пользователей, а также означает, что вы можете переключаться на новые (и лучшие) алгоритмы хранения по мере их изобретения.
Однако Django может обновлять только пароли, использующие алгоритмы, упомянутые в PASSWORD_HASHERS, поэтому при обновлении до новых систем вы должны убедиться, что никогда не удаляете записи из этого списка. Если вы это сделаете, пользователи, использующие неуказанные алгоритмы, не смогут обновить. Хэшированные пароли будут обновляться при увеличении (или уменьшении) числа итераций PBKDF2, раундов bcrypt или атрибутов argon2.
Обратите внимание, что если все пароли в вашей базе данных не закодированы в алгоритме по умолчанию хэшировщика, вы можете быть уязвимы для атаки перечисления пользователей с помощью анализа времени, из-за разницы между временем запроса входа в систему для пользователя с паролем, закодированным в нестандартном алгоритме, и временем запроса входа в систему для несуществующего пользователя (который использует стандартный хэшировщик). Вы можете смягчить это, обновив старые хэши паролей.
Обновление паролей без требования входа в систему
Если у вас есть существующая база данных со старым слабым хэшем, таким как MD5 или SHA1, вы можете обновить эти хэши самостоятельно, вместо того, чтобы ждать обновления, когда пользователь войдёт в систему (что может никогда не произойти, если пользователь не возвращается на ваш сайт). В этом случае вы можете использовать «обёрнутый» хэшировщик паролей.
END_OF_DOCUMENT_MARKER ```В этом примере мы мигрируем коллекцию хэшей 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().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',
]
В этот список включите любые другие хеш-функции, используемые вашим сайтом.
Включённые хеш-функции
Полный список хеш-функций, включённых в Django:
[
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'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',
]
Соответствующие имена алгоритмов:
pbkdf2_sha256pbkdf2_sha1argon2bcrypt_sha256bcryptsha1md5unsalted_sha1unsalted_md5crypt
Написание собственной хеш-функции пароля
Если вы пишете собственную хеш-функцию пароля, которая содержит коэффициент работы, такой как количество итераций, вы должны реализовать метод harden_runtime(self, password, encoded), чтобы устранить разрыв во времени выполнения между коэффициентом работы, указанным в encoded пароле, и коэффициентом работы по умолчанию хеш-функции. Это предотвращает атаку на перечисление пользователей из-за разницы между запросом входа в систему для пользователя с паролем, закодированным в старом количестве итераций, и несуществующего пользователя (который выполняет заданное количество итераций хеш-функции по умолчанию).
Взяв PBKDF2 в качестве примера, если encoded содержит 20 000 итераций, а значение по умолчанию iterations хеш-функции составляет 30 000, метод должен выполнить password ещё 10 000 итераций PBKDF2.
Если ваша хеш-функция не имеет коэффициента работы, реализуйте метод как бесполезную операцию (pass).
Ручное управление паролем пользователя
Модуль django.contrib.auth.hashers предоставляет набор функций для создания и проверки хэшированных паролей. Вы можете использовать их независимо от модели User.
-
check_password(password, encoded) -
Если вы хотите вручную аутентифицировать пользователя, сравнивая текстовый пароль с хэшированным паролем в базе данных, используйте удобную функцию
check_password(). Она принимает два аргумента: текстовый пароль для проверки и полное значение поляpasswordпользователя в базе данных для проверки, и возвращаетTrueпри совпадении,Falseв противном случае.
-
make_password(password, salt=None, hasher='default') -
Создаёт хэшированный пароль в формате, используемом этим приложением. Она принимает один обязательный аргумент: пароль в текстовом виде (строка или байты). Дополнительно вы можете указать соль и алгоритм хэширования для использования, если не хотите использовать значения по умолчанию (первый элемент в настройке
PASSWORD_HASHERS). См. Включённые хеш-функции для имени алгоритма каждой хеш-функции. Если аргумент пароляNone, возвращается непригодный пароль (который никогда не будет принятcheck_password()).Изменено в Django 3.1:Параметр
passwordдолжен быть строкой или байтами, если это неNone.
-
is_password_usable(encoded_password) -
Возвращает
Falseесли пароль является результатомUser.set_unusable_password().
Проверка паролей
Пользователи часто выбирают слабые пароли. Чтобы помочь смягчить эту проблему, Django предлагает подключаемые модули для проверки паролей. Вы можете настроить несколько валидаторов паролей одновременно. В Django включены несколько валидаторов, но вы также можете написать свои собственные.
Каждый валидатор паролей должен предоставить текст справки, чтобы объяснить требования пользователю, проверить заданный пароль и вернуть сообщение об ошибке, если он не отвечает требованиям, и (необязательно) получить пароли, которые были установлены. Валидаторы также могут иметь необязательные настройки для тонкой настройки их поведения.
Проверка контролируется настройкой AUTH_PASSWORD_VALIDATORS. По умолчанию для настройки является пустой список, что означает, что никакие валидаторы не применяются. В новых проектах, созданных с помощью шаблона по умолчанию startproject, набор валидаторов включён по умолчанию.
По умолчанию валидаторы используются в формах для сброса или изменения паролей и в командных утилитах управления createsuperuser и changepassword. Валидаторы не применяются на уровне модели, например, в User.objects.create_user() и create_superuser(), потому что мы предполагаем, что разработчики, а не пользователи, взаимодействуют с Django на этом уровне, а также потому, что валидация модели не запускается автоматически в рамках создания моделей.
Примечание
Проверка паролей может предотвратить использование многих типов слабых паролей. Однако тот факт, что пароль прошёл все валидаторы, не гарантирует, что он является надёжным паролем. Существует множество факторов, которые могут ослабить пароль, которые не могут быть обнаружены даже самыми продвинутыми валидаторами паролей.
Включение проверки паролей
Проверка паролей настраивается в настройке AUTH_PASSWORD_VALIDATORS:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 9,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
В этом примере включены все четыре включенных валидатора:
-
UserAttributeSimilarityValidator, который проверяет сходство пароля и набора атрибутов пользователя. -
MinimumLengthValidator, который проверяет, соответствует ли пароль минимальной длине. Этот валидатор настроен с помощью настраиваемого параметра: теперь минимальная длина требуется девять символов, а не восемь по умолчанию. -
CommonPasswordValidator, который проверяет, встречается ли пароль в списке общих паролей. По умолчанию он сравнивается со списком 20 000 общих паролей. -
NumericPasswordValidator, который проверяет, что пароль не является полностью цифровым.
Для UserAttributeSimilarityValidator и CommonPasswordValidator в этом примере используются значения по умолчанию. NumericPasswordValidator не имеет настроек.
Тексты справки и любые ошибки от валидаторов паролей всегда возвращаются в том порядке, в котором они перечислены в настройке AUTH_PASSWORD_VALIDATORS.
Включённые валидаторы
Django включает четыре валидатора:
-
class MinimumLengthValidator(min_length=8) -
Проверяет, соответствует ли пароль минимальной длине. Минимальная длина может быть настроена с помощью параметра
min_length.
-
class UserAttributeSimilarityValidator(user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7) -
Проверяет, достаточно ли пароль отличается от определённых атрибутов пользователя.
Параметр
user_attributesдолжен быть итерируемым объектом имён атрибутов пользователя для сравнения. Если этот аргумент не указан, используется значение по умолчанию:'username', 'first_name', 'last_name', 'email'. Атрибуты, которые не существуют, игнорируются.Минимальное сходство отвергаемого пароля может быть установлено в диапазоне от 0 до 1 с помощью параметра
max_similarity.. Значение 0 отклоняет все пароли, а значение 1 отклоняет только пароли, которые идентичны значению атрибута.
-
class CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH) -
Проверяет, не является ли пароль общим паролем. Это приводит пароль к нижнему регистру (для выполнения сравнения без учёта регистра) и проверяет его в списке 20 000 общих паролей, созданных Royce Williams.
Параметр
password_list_pathможет быть установлен в путь к настраиваемому файлу общих паролей. Этот файл должен содержать один пароль в нижнем регистре на строке и может быть текстовым или сжатым в формате gzip.
-
class NumericPasswordValidator -
Проверяет, что пароль не состоит только из цифр.
Интеграция проверки
В django.contrib.auth.password_validation есть несколько функций, которые вы можете вызывать из собственных форм или другого кода для интеграции проверки паролей. Это может быть полезно, если вы используете пользовательские формы для установки паролей или если у вас есть вызовы API, которые позволяют устанавливать пароли, например.
-
validate_password(password, user=None, password_validators=None) -
Проверяет пароль. Если все валидаторы находят пароль допустимым, возвращает
None. Если один или несколько валидаторов отклоняют пароль, генерируетValidationErrorсо всеми сообщениями об ошибках от валидаторов.Объект
userнеобязателен: если он не предоставлен, некоторые валидаторы могут не иметь возможности выполнить проверку и примут любой пароль.
-
password_changed(password, user=None, password_validators=None) -
Сообщает всем валидаторам, что пароль был изменён. Это может использоваться валидаторами, например, для предотвращения повторного использования пароля. Это необходимо вызвать после успешной смены пароля.
Для подклассов
AbstractBaseUser, поле пароля будет помечено как «изменённое» при вызовеset_password(), что инициирует вызовpassword_changed()после сохранения пользователя.
-
password_validators_help_texts(password_validators=None) -
Возвращает список сообщений-подсказок всех валидаторов. Они объясняют требования к паролю пользователю.
-
password_validators_help_text_html(password_validators=None) -
Возвращает HTML-строку со всеми сообщениями-подсказками в
<ul>. Это полезно при добавлении проверки пароля в формы, так как вы можете передать вывод непосредственно в параметрhelp_textполя формы.
-
get_password_validators(validator_config) -
Возвращает набор объектов валидаторов на основе параметра
validator_config. По умолчанию все функции используют валидаторы, определённые вAUTH_PASSWORD_VALIDATORS, но вызвав эту функцию с альтернативным набором валидаторов и передав результат в параметрpassword_validatorsдругих функций, вместо этого будет использован ваш настраиваемый набор валидаторов. Это полезно, когда у вас есть типичный набор валидаторов для большинства сценариев, но также есть специальный случай, требующий настраиваемого набора. Если вы всегда используете один и тот же набор валидаторов, нет необходимости использовать эту функцию, так как конфигурация изAUTH_PASSWORD_VALIDATORSиспользуется по умолчанию.Структура
validator_configидентична структуреAUTH_PASSWORD_VALIDATORS. Результат этой функции можно передать в параметрpassword_validatorsперечисленных выше функций.
Обратите внимание, что где пароль передаётся одной из этих функций, это всегда должен быть обычный текст пароля — а не хэшированный пароль.
Написание собственного валидатора
Если встроенных валидаторов Django недостаточно, вы можете написать свои собственные валидаторы паролей. У валидаторов довольно простой интерфейс. Они должны реализовать два метода:
-
validate(self, password, user=None): проверяет пароль. ВозвращаетNoneесли пароль допустим, или генерируетValidationErrorс сообщением об ошибке, если пароль недопустим. Вы должны уметь обрабатывать случай, когдаuserявляетсяNone, — если это означает, что ваш валидатор не может работать, возвращайтеNoneдля отсутствия ошибки. -
get_help_text(): предоставляет сообщение-подсказку для объяснения требований пользователю.
Любые элементы в OPTIONS в AUTH_PASSWORD_VALIDATORS для вашего валидатора будут переданы в конструктор. Все аргументы конструктора должны иметь значение по умолчанию.
Вот простой пример валидатора с одним необязательным параметром:
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
class MinimumLengthValidator:
def __init__(self, min_length=8):
self.min_length = min_length
def validate(self, password, user=None):
if len(password) < self.min_length:
raise ValidationError(
_("This password must contain at least %(min_length)d characters."),
code='password_too_short',
params={'min_length': self.min_length},
)
def get_help_text(self):
return _(
"Your password must contain at least %(min_length)d characters."
% {'min_length': self.min_length}
)
Вы также можете реализовать password_changed(password, user=None), который будет вызван после успешной смены пароля. Это может использоваться для предотвращения повторного использования пароля, например. Однако, если вы решите хранить предыдущие пароли пользователя, никогда не делайте это в виде обычного текста.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.1/topics/auth/passwords/