• 3.1
  • 5.0
  • 6.1
  • Версия документации: 3.2

Управление паролями в 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:

  1. Install the argon2-cffi library. This can be done by running python -m pip install django[argon2], which is equivalent to python -m pip install argon2-cffi (along with any version requirement from Django’s setup.cfg).

  2. Измените 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 в качестве алгоритма по умолчанию, выполните следующие действия:

  1. Install the bcrypt library. This can be done by running python -m pip install django[bcrypt], which is equivalent to python -m pip install bcrypt (along with any version requirement from Django’s setup.cfg).

  2. Измените 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 в качестве алгоритма хранения паролей.

Увеличение энтропии соли

New in Django 3.2.

Большинство хэшей паролей включают в себя соль вместе с хэшем пароля для защиты от атак радужных таблиц. Сама соль представляет собой случайное значение, которое увеличивает размер и, следовательно, стоимость радужной таблицы, и в настоящее время она установлена ​​на 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:

  1. 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.

  2. Добавьте новый алгоритм хэширования в начало списка конфигурационного параметра 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:

  1. time_cost контролирует количество итераций в хеше.

  2. memory_cost контролирует размер памяти, которая должна использоваться во время вычисления хеша.

  3. параллелизм контролирует, на скольких процессорах может быть распараллелено вычисление хеша.

Значения этих атрибутов по умолчанию, вероятно, вас устроят. Если вы определите, что хэш пароля работает слишком быстро или слишком медленно, вы можете настроить его следующим образом:

  1. Выберите «параллелизм», чтобы указать количество потоков, которые вы можете выделить для вычисления хэша.

  2. Выберите memory_cost в качестве КиБ памяти, которую вы можете сэкономить.

  3. Настройте 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_sha256

  • pbkdf2_sha1

  • аргон2

  • bcrypt_sha256

  • bcrypt

  • sha1

  • md5

  • unsalted_sha1

  • unsalted_md5

  • crypt

Написание собственного хэшера

Если вы пишете свой собственный хэшер паролей, который содержит рабочий фактор, такой как количество итераций, вам следует реализовать метод 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’s password field in the database to check against, and returns True if they match, False otherwise.

make_password(password, salt=None, hasher='default')

Создает хешированный пароль в формате, используемом этим приложением. Он принимает один обязательный аргумент: пароль в виде обычного текста (строка или байты). При желании вы можете предоставить соль и алгоритм хеширования, если вы не хотите использовать значения по умолчанию (первая запись параметра PASSWORD_HASHERS). См. Включенные хешеры для получения информации об имени алгоритма каждого хэшера. Если аргумент пароля равен None, возвращается непригодный для использования пароль (который никогда не будет принят check_password()).

Changed in Django 3.1:

The password parameter must be a string or bytes if not None.

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_length parameter.

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_similarity parameter 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), который будет вызываться после успешной смены пароля. Это можно использовать, например, для предотвращения повторного использования пароля. Однако если вы решите сохранить предыдущие пароли пользователя, никогда не следует делать это в открытом виде.

Back to Top