Управление паролями в 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, так как требует сторонней библиотеки. Однако панель Password Hashing Competition рекомендует немедленное использование Argon2, а не других поддерживаемых Django алгоритмов.
Чтобы использовать Argon2 в качестве алгоритма хранения по умолчанию, выполните следующие действия:
- Установите библиотеку argon2-cffi. Это можно сделать, выполнив
pip install django[argon2], что эквивалентноpip install argon2-cffi, (вместе с любыми требованиями к версии изsetup.pyDjango). -
Измените
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. Это можно сделать, выполнив
pip install django[bcrypt], что эквивалентноpip install bcrypt(вместе с любыми требованиями к версии изsetup.pyDjango). -
Измените
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— количество KiB памяти, которое вы можете использовать. - Настройте
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.
Обратите внимание, что если все пароли в вашей базе данных не закодированы по умолчанию хешированием, вы можете быть уязвимы к временному атаке перебора пользователей из-за разницы во времени запроса входа в систему для пользователя с паролем, закодированным в нестандартном алгоритме, и времени запроса входа в систему для несуществующего пользователя (который запускает стандартный хеширователь). Вы можете смягчить это, обновив старые хеши паролей.
Обновление паролей без необходимости входа в систему
Если у вас есть существующая база данных со старым, слабым хешем, таким как 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)[source] -
Если вы хотите вручную аутентифицировать пользователя, сравнив текст пароля с хэшированным паролем в базе данных, используйте удобную функцию
check_password(). Она принимает два аргумента: текст пароля для проверки и полное значение поля пользователяpasswordв базе данных для проверки, и возвращаетTrue, если они совпадают,False, в противном случае.
-
make_password(password, salt=None, hasher='default')[source] -
Создаёт хэшированный пароль в формате, используемом этим приложением. Он принимает один обязательный аргумент: пароль в текстовом формате. По желанию, вы можете указать соль и алгоритм хэширования для использования, если не хотите использовать значения по умолчанию (первый элемент в настройке
PASSWORD_HASHERS). См. Включенные хэширующие функции для имени алгоритма каждой хэширующей функции. Если аргумент password имеет значениеNone, возвращается непригодный пароль (который никогда не будет принят функциейcheck_password()).
-
is_password_usable(encoded_password)[source] -
Возвращает
False, если пароль является результатомUser.set_unusable_password().Изменено в Django 2.1:В более ранних версиях эта функция также возвращала
False, если пароль равенNoneили пустой строке, или если пароль использует хэширующую функцию, которая не указана в настройкеPASSWORD_HASHERS. Это поведение считается ошибкой, поскольку оно препятствует пользователям с такими паролями запросить сброс пароля.
Проверка паролей
Пользователи часто выбирают слабые пароли. Чтобы помочь в решении этой проблемы, 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)[source] -
Проверяет, соответствует ли пароль минимальной длине. Минимальная длина может быть изменена параметром
min_length.
-
class UserAttributeSimilarityValidator(user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7)[source] -
Проверяет, достаточно ли пароль отличается от определённых атрибутов пользователя.
Параметр
user_attributesдолжен быть итерируемым набором имён атрибутов пользователя для сравнения. Если этот аргумент не указан, используется значение по умолчанию:'username', 'first_name', 'last_name', 'email'. Атрибуты, которых нет, игнорируются.Минимальное сходство отклоняемого пароля может быть установлено по шкале от 0 до 1 с помощью параметра
max_similarity. Значение 0 отклоняет все пароли, а значение 1 отклоняет только пароли, которые идентичны значению атрибута.
-
class CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH)[source] -
Проверяет, не является ли пароль общим паролем. Преобразует пароль к нижнему регистру (для выполнения сравнения без учета регистра) и проверяет его по списку из 20 000 общих паролей, созданных Royce Williams.
Значение
password_list_pathможно задать как путь к пользовательскому файлу с общими паролями. Этот файл должен содержать один пароль в нижнем регистре на каждой строке и может быть текстовым или сжатым в gzip.Изменено в Django 2.1:В более старых версиях используется список из 1000 общих паролей.
-
class NumericPasswordValidator[source] -
Проверяет, не является ли пароль полностью числовым.
Интеграция проверки
В django.contrib.auth.password_validation есть несколько функций, которые можно вызвать из собственных форм или другого кода для интеграции проверки паролей. Это может быть полезно, если вы используете пользовательские формы для установки пароля или у вас есть вызовы API, которые позволяют устанавливать пароли, например.
-
validate_password(password, user=None, password_validators=None)[source] -
Проверяет пароль. Если все валидаторы считают пароль действительным, возвращает
None. Если один или несколько валидаторов отклоняют пароль, вызывает исключениеValidationErrorсо всеми сообщениями об ошибках от валидаторов.Объект
userнеобязателен: если он не предоставлен, некоторые валидаторы могут не иметь возможности выполнить проверку и примут любой пароль.
-
password_changed(password, user=None, password_validators=None)[source] -
Сообщает всем валидаторам, что пароль был изменён. Это можно использовать валидаторами, например, тем, что предотвращает повторное использование пароля. Это следует вызывать после успешного изменения пароля.
Для подклассов
AbstractBaseUser, поле пароля будет помечено как «грязное» при вызовеset_password(), что запускает вызовpassword_changed()после сохранения пользователя.
-
password_validators_help_texts(password_validators=None)[source] -
Возвращает список сообщений-подсказок всех валидаторов. Они поясняют требования к паролю пользователю.
-
password_validators_help_text_html(password_validators=None) -
Возвращает строку HTML со всеми сообщениями-подсказками в
<ul>. Это полезно при добавлении проверки пароля в формы, так как вы можете передать вывод непосредственно параметруhelp_textполя формы.
-
get_password_validators(validator_config)[source] -
Возвращает набор объектов валидаторов на основе параметра
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/2.1/topics/auth/passwords/