Управление паролями в 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 или раундов bcrypt.
Обратите внимание, что если все пароли в вашей базе данных не закодированы по умолчанию в алгоритме хэшера, вы можете быть уязвимы к атаке на перечисление пользователей, основанной на задержке, из-за разницы во времени выполнения запроса входа в систему для пользователя с паролем, закодированным в нестандартном алгоритме, и времени выполнения запроса входа в систему для несуществующего пользователя (который запускает хэшер по умолчанию). Вы можете смягчить это, обновляя более старые хэши паролей.
Обновление паролей при изменении числа раундов bcrypt было добавлено.
Обновление паролей без необходимости входа в систему
Если у вас есть существующая база данных со старым, слабым хэшем, например, 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.
Если у вашего хеширующего алгоритма нет значения work factor, реализуйте метод как пустую операцию (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. Если аргумент password имеет значениеNone, возвращается непригодный пароль (который никогда не будет принят функциейcheck_password()).
-
is_password_usable(encoded_password)[source] -
Проверяет, является ли данная строка хешированным паролем, который может быть проверен функцией
check_password().
Проверка паролей
Пользователи часто выбирают слабые пароли. Чтобы смягчить эту проблему, Django предлагает подключаемые модули для проверки паролей. Вы можете настроить несколько валидаторов паролей одновременно. В Django включены несколько валидаторов, но также легко написать свои собственные.
Каждый валидатор паролей должен предоставить текст справки, объясняющий требования пользователю, проверять заданный пароль и возвращать сообщение об ошибке, если он не соответствует требованиям, и необязательно получать пароли, которые были установлены. Валидаторы также могут иметь необязательные настройки для тонкой настройки их поведения.
Проверка управляется настройкой AUTH_PASSWORD_VALIDATORS. По умолчанию валидаторы используются в формах для сброса или изменения паролей. Значение по умолчанию для настройки — пустой список, что означает, что валидаторы не применяются. В новых проектах, созданных с помощью шаблона startproject по умолчанию, включен простой набор валидаторов.
Примечание
Проверка паролей может предотвратить использование многих типов слабых паролей. Однако тот факт, что пароль проходит все валидаторы, не гарантирует, что это сильный пароль. Есть много факторов, которые могут ослабить пароль, которые не могут быть обнаружены даже самыми продвинутыми валидаторами паролей.
Включение проверки паролей
Проверка паролей настраивается в настройке 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, который проверяет, не встречается ли пароль в списке распространённых паролей. По умолчанию он сравнивает с включённым списком из 1000 распространённых паролей. -
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'. Несуществующие атрибуты игнорируются.Максимальное сходство пароля, прежде чем он будет отклонен, можно установить с помощью параметра
max_similarityв масштабе от 0 до 1. Значение 0 приведёт к отклонению всех паролей, а значение 1 — к отклонению только паролей, идентичных значению атрибута.
-
class CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH)[source] -
Проверяет, не является ли пароль распространённым. По умолчанию это проверяется с помощью списка из 1000 распространённых паролей, созданного Mark Burnett.
Параметр
password_list_pathможет быть установлен в путь к пользовательскому файлу распространённых паролей. Этот файл должен содержать по одному паролю на строке и может быть текстовым или сжатым gzip.
-
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 ugettext as _
class MinimumLengthValidator(object):
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/1.9/topics/auth/passwords/