Управление паролями в Django¶
Управление паролями является такой сущностью, которую не следует переизобретать без особой необходимости и Django старается предоставить безопасный и гибкий набор инструментов для управления пользовательскими паролями. Этот документ описывает как Django хранит пароли, как может быть настроено их хэширование, а также некоторые утилиты для работы с хэшированными паролями.
См.также
Несмотря на то, что пользователи могут использовать надежные пароли, злоумышленники могут подслушать их соединения. Используйте HTTPS, чтобы избежать отправки паролей (или любых других конфиденциальных данных) через простые HTTP-соединения, поскольку они будут уязвимы для перехвата паролей.
Как Django хранит пароли¶
Django предоставляет гибкую систему хранения паролей и по умолчанию использует PBKDF2.
The password attribute of a
User object is a string in this format:
<algorithm>$<iterations>$<salt>$<hash>
Данная строка показывает компоненты, которые используются для хранения пользовательского пароля и разделены знаком доллара, а именно: хэширующий алгоритм, количество итераций алгоритма (work factor), случайная соль и полученный хэш пароля. Алгоритмом может быть любой из ряда однонаправленных хэширующих алгоритмов, которые использует Django; см. далее. Итерации описывают количество применений алгоритма для получения хэша. Соль является случайными данным, а сам хэш получается в результате работы однонаправленной функции.
По умолчанию, Django использует алгоритм PBKDF2 с хэшем SHA256, механизм защиты паролей рекомендованный NIST. Этого должно хватить для большинства пользователей: достаточная защита, требующая большой объём вычислительного времени для взлома.
Тем не менее, в зависимости от ваших требований, вы можете выбрать другой алгоритм или даже реализовать собственный алгоритм, который будет соответствовать вашим требованиям к безопасности. Итак, большинство пользователей не должны думать об этом, если вы сомневаетесь, значит вам это точно не надою В противном случае, прочитайте:
Django chooses the algorithm to use by consulting the
PASSWORD_HASHERS setting. This is a list of hashing algorithm
classes that this Django installation supports. The first entry in this list
(that is, settings.PASSWORD_HASHERS[0]) will be used to store passwords,
and all the other entries are valid hashers that can be used to check existing
passwords. This means that if you want to use a different algorithm, you’ll
need to modify PASSWORD_HASHERS to list your preferred algorithm
first in the list.
По умолчанию 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, bcrypt, SHA1 и так далее. Следующие несколько разделов описывают ряд общих способов, которые могут быть использованы опытными пользователями для изменения данного параметра конфигурации.
В следующих нескольких разделах описывается несколько распространенных способов, которыми опытные пользователи могут захотеть изменить этот параметр.
Использование bcrypt с Django¶
Argon2 is the winner of the 2015 Password Hashing Competition, a community organized open competition to select a next generation hashing algorithm. It’s designed not to be easier to compute on custom hardware than it is to compute on an ordinary CPU.
Argon2 не используется в Django по умолчанию, поскольку для него требуется сторонняя библиотека. Однако комиссия по хэшированию паролей рекомендует немедленно использовать Argon2, а не другие алгоритмы, поддерживаемые Django.
To use Argon2 as your default storage algorithm, do the following:
Install the argon2-cffi library. This can be done by running
python -m pip install django[argon2], which is equivalent topython -m pip install argon2-cffi(along with any version requirement from Django’ssetup.cfg).Измените
PASSWORD_HASHERSтак, чтобыBCryptSHA256PasswordHasherбыл указан первым. То есть, в файле конфигурации надо сделать так: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 в качестве алгоритма по умолчанию, выполните следующие действия:
Install the bcrypt library. This can be done by running
python -m pip install django[bcrypt], which is equivalent topython -m pip install bcrypt(along with any version requirement from Django’ssetup.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 в качестве алгоритма хранения паролей.
Увеличение энтропии соли¶
Большинство хэшей паролей включают в себя соль вместе с хэшем пароля для защиты от атак радужных таблиц. Сама соль представляет собой случайное значение, которое увеличивает размер и, следовательно, стоимость радужной таблицы, и в настоящее время она установлена на 128 бит со значением salt_entropy в BasePasswordHasher. По мере снижения затрат на вычисления и хранение это значение должно быть увеличено. При реализации собственного хэшера паролей вы можете переопределить это значение, чтобы использовать желаемый уровень энтропии для хэшей паролей. salt_entropy измеряется в битах.
Детали реализации
Из-за метода хранения значений соли значение salt_entropy фактически является минимальным значением. Например, значение 128 предоставит соль, которая фактически будет содержать 131 бит энтропии.
Увеличение сложности хэша¶
PBKDF2 и bcrypt¶
The PBKDF2 and bcrypt algorithms use a number of iterations or rounds of
hashing. This deliberately slows down attackers, making attacks against hashed
passwords harder. However, as computing power increases, the number of
iterations needs to be increased. We’ve chosen a reasonable default (and will
increase it with each release of Django), but you may wish to tune it up or
down, depending on your security needs and available processing power. To do so,
you’ll subclass the appropriate algorithm and override the iterations
parameters. For example, to increase the number of iterations used by the
default PBKDF2 algorithm:
Create a subclass of
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.
Аргон2¶
Argon2 has three attributes that can be customized:
time_costконтролирует количество итераций в хеше.memory_costконтролирует размер памяти, которая должна использоваться во время вычисления хеша.параллелизмконтролирует, на скольких процессорах может быть распараллелено вычисление хеша.
Значения этих атрибутов по умолчанию, вероятно, вас устроят. Если вы определите, что хэш пароля работает слишком быстро или слишком медленно, вы можете настроить его следующим образом:
Выберите «параллелизм», чтобы указать количество потоков, которые вы можете выделить для вычисления хэша.
Выберите
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.
Имейте в виду, что если все пароли в вашей базе данных не закодированы в алгоритме хеширования по умолчанию, вы можете быть уязвимы для атаки по времени перечисления пользователей из-за разницы между продолжительностью запроса на вход для пользователя с паролем, закодированным в алгоритме, отличном от алгоритма по умолчанию, и продолжительностью запроса на вход для несуществующего пользователя (который запускает хэшер по умолчанию). Вы можете смягчить это, обновив старые хеши паролей.
Обновление пароля без необходимости входа в систему¶
If you have an existing database with an older, weak hash such as MD5 or SHA1, you might want to upgrade those hashes yourself instead of waiting for the upgrade to happen when a user logs in (which may never happen if a user doesn’t return to your site). In this case, you can use a «wrapped» password hasher.
For this example, we’ll migrate a collection of SHA1 hashes to use
PBKDF2(SHA1(password)) and add the corresponding password hasher for checking
if a user entered the correct password on login. We assume we’re using the
built-in User model and that our project has an accounts app. You can
modify the pattern to work with any algorithm or with a custom user model.
Сначала мы добавим собственный хэшер:
accounts/hashers.py¶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)
Миграция данных может выглядеть примерно так:
accounts/migrations/0002_migrate_sha1_passwords.py¶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 содержит:
mysite/settings.py¶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_sha1аргон2bcrypt_sha256bcryptsha1md5unsalted_sha1unsalted_md5crypt
Написание собственного хэшера¶
Если вы пишете свой собственный хэшер паролей, который содержит рабочий фактор, такой как количество итераций, вам следует реализовать метод Harden_runtime(self, пароль, закодированный) для устранения разрыва во время выполнения между рабочим фактором, указанным в «закодированном» пароле, и рабочим фактором хэшера по умолчанию. Это предотвращает атаку по времени перечисления пользователей из-за разницы между запросом на вход в систему для пользователя с паролем, закодированным в более старом количестве итераций, и несуществующим пользователем (который выполняет количество итераций хэшера по умолчанию).
Если взять в качестве примера PBKDF2, то если encoded содержит 20 000 итераций, а итерации по умолчанию для хэшера равны 30 000, метод должен запустить password через еще 10 000 итераций PBKDF2.
Если у вашего хэшера нет рабочего фактора, реализуйте этот метод как пустой («pass»).
Ручное управление паролями пользователей¶
Модуль django.contrib.auth.hashers предоставляет набор функций для создания и проверки хэшированных паролей. Вы можете использовать эти функции независимо от модели ``User``l.
- check_password(password, encoded)¶
If you’d like to manually authenticate a user by comparing a plain-text password to the hashed password in the database, use the convenience function
check_password(). It takes two arguments: the plain-text password to check, and the full value of a user’spasswordfield in the database to check against, and returnsTrueif they match,Falseotherwise.
- make_password(password, salt=None, hasher='default')¶
Создает хешированный пароль в формате, используемом этим приложением. Он принимает один обязательный аргумент: пароль в виде обычного текста (строка или байты). При желании вы можете предоставить соль и алгоритм хеширования, если вы не хотите использовать значения по умолчанию (первая запись параметра PASSWORD_HASHERS). См. Включенные хешеры для получения информации об имени алгоритма каждого хэшера. Если аргумент пароля равен None, возвращается непригодный для использования пароль (который никогда не будет принят
check_password()).Changed in Django 3.1:The
passwordparameter must be a string or bytes if notNone.
- is_password_usable(encoded_password)¶
Возвращает
False, если пароль является результатомUser.set_unusable_password().
Обновление паролей¶
Пользователи часто выбирают плохие пароли. Чтобы решить эту проблему, Django предлагает подключаемую проверку пароля. Вы можете настроить несколько валидаторов паролей одновременно. В Django включено несколько валидаторов, но вы также можете написать свои собственные.
Each password validator must provide a help text to explain the requirements to the user, validate a given password and return an error message if it does not meet the requirements, and optionally receive passwords that have been set. Validators can also have optional settings to fine tune their behavior.
Проверка контролируется настройкой 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)¶
Validates whether the password meets a minimum length. The minimum length can be customized with the
min_lengthparameter.
- class UserAttributeSimilarityValidator(user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7)¶
Validates whether the password is sufficiently different from certain attributes of the user.
Параметр user_attributes должен представлять собой итерацию имен пользовательских атрибутов для сравнения. Если этот аргумент не указан, используется значение по умолчанию:
'username', 'first_name', 'last_name', 'email'. Несуществующие атрибуты игнорируются.Максимально допустимое сходство паролей можно установить по шкале от 0,1 до 1,0 с помощью параметра max_similarity. Это сравнивается с результатом
difflib.SequenceMatcher.quick_ratio(). Значение 0,1 отклоняет пароли, если они существенно не отличаются от user_attributes, тогда как значение 1,0 отклоняет только пароли, идентичные значению атрибута.Changed in Django 2.2.26:The
max_similarityparameter was limited to a minimum value of 0.1.
- class CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH)¶
Validates whether the password is not a common password. This converts the password to lowercase (to do a case-insensitive comparison) and checks it against a list of 20,000 common password created by Royce Williams.
В качестве
password_list_pathможно указать путь к пользовательскому файлу общих паролей. Этот файл должен содержать один пароль в нижнем регистре в каждой строке и может быть в виде обычного текста или в формате gzip.
- class NumericPasswordValidator¶
Validates whether the password is not entirely numeric.
Интеграция проверки¶
В 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, пароль, 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), который будет вызываться после успешной смены пароля. Это можно использовать, например, для предотвращения повторного использования пароля. Однако если вы решите сохранить предыдущие пароли пользователя, никогда не следует делать это в открытом виде.