Управление паролями в 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 - победитель Конкурса хеширования паролей 2015 г. , открытого конкурса, проводимого сообществом для выбора алгоритма хеширования следующего поколения. Он разработан таким образом, чтобы его не было проще выполнять на выделенном оборудовании, чем на обычном процессоре.
Argon2 не является алгоритмом по умолчанию, используемым в Django, потому что для него требуется сторонняя библиотека. Тем не менее, эксперты Password Hashing Competition рекомендуют немедленно использовать Argon2, а не другие алгоритмы, поддерживаемые Django.
Чтобы использовать Argon2 в качестве алгоритма хранения по умолчанию, сделайте следующее:
Установите библиотеку argon2-cffi . Это можно сделать, запустив , что эквивалентно (вместе с любыми требованиями к версии от Django ).
python -m pip install django[argon2]
python -m pip install argon2-cffi
setup.cfg
Отредактируйте
PASSWORD_HASHERS
так, чтобы оноArgon2PasswordHasher
появилось первым. Вот что должно появиться в вашем файле настроек:PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.Argon2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ]
Сохраните или добавьте другие элементы в этот список, если вам нужно, чтобы Django мог обновлять пароли .
Использование bcrypt
с Django ¶
Bcrypt - это популярный алгоритм хранения паролей, специально разработанный для долгосрочного хранения паролей. Это не алгоритм Django по умолчанию, поскольку он зависит от установки сторонних библиотек. Но поскольку потенциальных пользователей много, Django поддерживает bcrypt с минимальными усилиями.
Чтобы использовать Bcrypt в качестве алгоритма хранения по умолчанию, сделайте следующее:
Установите библиотеку bcrypt . Это можно сделать, запустив , что эквивалентно (вместе с любыми требованиями к версии от Django ).
python -m pip install django[bcrypt]
python -m pip install bcrypt
setup.cfg
Отредактируйте
PASSWORD_HASHERS
так, чтобы оноBCryptSHA256PasswordHasher
появилось первым. Вот что должно появиться в вашем файле настроек:PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.Argon2PasswordHasher', ]
Сохраните или добавьте другие элементы в этот список, если вам нужно, чтобы Django мог обновлять пароли .
Итак, ваша установка Django теперь использует Bcrypt в качестве алгоритма хранения по умолчанию.
Увеличение трудоемкости ¶
PBKDF2 и bcrypt ¶
Алгоритмы PBKDF2 и bcrypt используют несколько итераций или хеш-проходов. Это намеренно замедляет атаки, что затрудняет взлом отпечатков пароля. Однако по мере увеличения вычислительной мощности количество требуемых итераций также увеличивается. Мы выбрали разумное значение по умолчанию (и мы будем увеличивать его с каждым новым выпуском Django), но вы можете увеличить или уменьшить его в зависимости от ваших потребностей в безопасности и возможностей вычислительной мощности. Вы можете сделать это, создав подкласс соответствующего алгоритма и переопределив параметр iterations
. Например, чтобы увеличить количество итераций, используемых алгоритмом PBKDF2 по умолчанию:
Создайте подкласс
django.contrib.auth.hashers.PBKDF2PasswordHasher
:from django.contrib.auth.hashers import PBKDF2PasswordHasher class MyPBKDF2PasswordHasher(PBKDF2PasswordHasher): """ A subclass of PBKDF2PasswordHasher that uses 100 times more iterations. """ iterations = PBKDF2PasswordHasher.iterations * 100
Сохраните его где-нибудь в своем проекте. Например, вы можете поместить его в файл с именем
myproject/hashers.py
.Добавьте свой новый хэш-класс первым в
PASSWORD_HASHERS
:PASSWORD_HASHERS = [ 'myproject.hashers.MyPBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.Argon2PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ]
Вуаля, ваша установка Django теперь использует больше итераций при хранении паролей с помощью PBKDF2.
Аргон2 ¶
Argon2 имеет три атрибута, которые можно настроить:
time_cost
контролирует количество итераций посадочного места.memory_cost
контролирует размер памяти, используемый при расчете посадочного места.parallelism
контролирует количество процессоров, которые можно использовать для расчета занимаемой площади.
Значения по умолчанию для этих атрибутов, вероятно, разумны. Если вы определили, что хеширование паролей происходит слишком быстро или слишком медленно, вы можете настроить их следующим образом:
- Определите
parallelism
количество потоков, которые будут использоваться для расчета посадочного места. - Определите
memory_cost
количество килобайт используемой памяти. - Отрегулируйте
time_cost
и измерьте время, затрачиваемое на хеширование пароля. Выберите значение, наtime_cost
которое у вас уйдет приемлемое время. Еслиtime_cost
значение 1 по-прежнему слишком медленное, уменьшите значениеmemory_cost
.
Толкование memory_cost
Утилита командной строки argon2 и некоторые другие библиотеки интерпретируют параметр memory_cost
иначе, чем значение, которое использует Django. Преобразование дается .memory_cost == 2 ** memory_cost_en_ligne_de_commande
Обновление паролей ¶
Когда пользователи входят в систему, если их пароль хранится с другим алгоритмом, чем тот, который установлен в качестве основного, Django автоматически обновляет алгоритм, чтобы использовать основной. Это означает, что старые установки Django автоматически становятся более безопасными, когда пользователи входят в систему, а также означает, что вы можете перейти на новые (и более совершенные) алгоритмы хранения по мере их появления.
Однако Django может обновлять пароли только с использованием алгоритмов, упомянутых в PASSWORD_HASHERS
, поэтому важно не удалять старые алгоритмы из этого списка при переходе на новые системы. В противном случае пользователи, использующие не упомянутые алгоритмы, не смогут обновить свой пароль. Хеш-пароли обновляются, когда количество итераций PBKDF2 или раундов bcrypt увеличивается (или уменьшается).
Имейте в виду, что если все пароли в вашей базе данных не зашифрованы с помощью алгоритма хеширования по умолчанию, вы можете быть уязвимы для атаки типа перечисления учетных записей на основе времени из-за разница между продолжительностью запроса на соединение для пользователя с паролем, закодированным в другом алгоритме, и продолжительностью запроса на соединение от несуществующего пользователя (который выполняет алгоритм по умолчанию). Можно рассмотреть эту проблему в перспективе, обновив старые отпечатки пароля .
Обновление паролей без входа в систему ¶
Если в существующей базе данных есть старые и уязвимые отпечатки паролей MD5 или SHA1, может быть желательно обновить эти отпечатки, не дожидаясь входа затронутых пользователей в систему (что может никогда не произойти, если они пользователи больше не возвращаются на сайт). В этом случае вы можете использовать "завернутый" хешер паролей.
В этом примере мы перенесем серию отпечатков SHA1, чтобы они использовали PBKDF2 (SHA1 (пароль)), и добавим соответствующий хэш пароля, который позаботится о проверке пароля пользователя во время связь. Мы предполагаем, что используется модель пользователя по умолчанию и что в проекте есть приложение 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_sha256
pbkdf2_sha1
argon2
bcrypt_sha256
bcrypt
sha1
md5
unsalted_sha1
unsalted_md5
crypt
Написание собственного алгоритма хеширования ¶
Если вы пишете свой собственный алгоритм хеширования пароля, который имеет рабочий фактор, такой как количество итераций, вы должны реализовать какой-нибудь метод, чтобы компенсировать разницу во времени выполнения между рабочим фактором, указанным в пароль и коэффициент работы алгоритма по умолчанию. Это предотвращает атаки с перечислением учетных записей на основе времени, которые используют разницу между запросами на вход пользователя, пароль которого закодирован со старым счетчиком итераций, и несуществующим пользователем (который использует номер итерации алгоритма по умолчанию).harden_runtime(self, password, encoded)
encoded
В качестве примера возьмем PBKDF2, если он encoded
содержит 20 000 итераций, а количество итераций по умолчанию для алгоритма равно 30 000, метод должен пройти еще password
10 000 итераций PBKDF2.
Если ваш алгоритм хеширования не имеет рабочего фактора, реализуйте метод как dummy ( pass
).
Ручное управление паролями ¶
Модуль django.contrib.auth.hashers
предоставляет набор функций для создания и проверки отпечатков пароля. Вы можете использовать их независимо от модели User
.
-
check_password
( пароль , закодированный ) ¶ Если вам нужно вручную аутентифицировать пользователя, сравнивая открытый пароль с отпечатком этого пароля в базе данных, используйте служебную функцию
check_password()
. Он принимает два параметра: четкий пароль для проверки и полное значениеpassword
поля пользователя в базе данных в качестве значения для сравнения; функция возвращает,True
если значения совпадают, в противном случаеFalse
.
-
make_password
( пароль , соль = None , hasher = 'default' ) ¶ Создает хешированный пароль в формате, используемом этим приложением. Требуется один обязательный аргумент: пароль в виде обычного текста (строка или байты). При желании вы можете предоставить соль и алгоритм хеширования, если вы не хотите использовать значения по умолчанию (первая запись
PASSWORD_HASHERS
настройки). См. В разделе « Встроенные прерыватели паролей» имя алгоритма каждого хешера. Если аргументNone
пароля равен, возвращается непригодный для использования пароль (тот, который никогда не будет принятcheck_password()
).Изменено в Django 3.1:password
Параметр должен быть строкой или байт , если неNone
.
-
is_password_usable
( encoded_password ) ¶ Возвращает
False
, был ли сгенерирован парольUser.set_unusable_password()
.
Проверка паролей ¶
Пользователи часто выбирают слабые пароли. Чтобы решить эту проблему, Django предлагает дополнительную проверку пароля. Вы можете настроить несколько валидаторов паролей параллельно. Некоторые из них включены в Django, но вы также можете написать свой собственный валидатор.
Каждый валидатор паролей должен предоставлять текст справки, объясняющий требования для пользователей, проверять заданный пароль и сообщать об ошибке, если пароль не соответствует требованиям. По желанию, он может получать измененные пароли. Валидаторы также могут предлагать дополнительные настройки, чтобы специализировать свое поведение.
Проверка контролируется настройкой AUTH_PASSWORD_VALIDATORS
. Список для этого параметра по умолчанию пуст, что означает, что валидатор не применяется. В новых проектах, созданных по шаблону по startproject
умолчанию, активируется набор валидаторов.
По умолчанию валидаторы используются в формах для сброса или изменения паролей, а также в административных командах createsuperuser
и changepassword
. Валидаторы не применяются на уровне модели, например в User.objects.create_user()
и create_superuser()
, потому что на этом уровне разработчики, а не пользователи взаимодействуют с Django, а также потому, что проверка модели не выполняется автоматически в контекст создания моделей.
Заметка
Проверка паролей может предотвратить использование различных типов слабых паролей. Однако даже если пароль подтвержден всеми валидаторами, это не обязательно гарантирует надежный пароль. Существует множество факторов, ослабляющих надежность пароля, которые не могут быть обнаружены даже самыми продвинутыми валидаторами паролей.
Включение проверки пароля ¶
Проверка пароля настраивается установкой AUTH_PASSWORD_VALIDATORS
:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 9,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
В этом примере активируются четыре встроенных валидатора:
UserAttributeSimilarityValidator
, который проверяет схожесть между паролем и определенными атрибутами пользователя.MinimumLengthValidator
, который проверяет минимальную длину пароля. Этот валидатор настроен с настраиваемой опцией: для него требуется минимальная длина 9 символов вместо 8 по умолчанию.CommonPasswordValidator
, который проверяет, находится ли пароль в списке общих паролей. По умолчанию это сравнение производится со списком из 20 000 общих паролей.NumericPasswordValidator
, который проверяет, что пароль не полностью числовой.
Для UserAttributeSimilarityValidator
и CommonPasswordValidator
в этом примере мы используем настройки по умолчанию. NumericPasswordValidator
не содержит никаких настроек.
Тексты справки по валидатору паролей и сообщения об ошибках всегда возвращаются в том порядке, в котором они появляются AUTH_PASSWORD_VALIDATORS
.
Встроенные валидаторы ¶
Django предоставляет четыре встроенных валидатора:
-
класс
MinimumLengthValidator
( min_length = 8 ) ¶ Проверяет минимальную длину паролей. Эту минимальную длину можно настроить с помощью параметра
min_length
.
-
class
UserAttributeSimilarityValidator
( user_attributes = DEFAULT_USER_ATTRIBUTES , max_similarity = 0.7 ) ¶ Проверяет несоответствие пароля нескольким атрибутам пользователя.
Параметр
user_attributes
соответствует итерации имен атрибутов пользователя, с которыми будет выполняться сравнение. Если этот параметр опущен, список по умолчанию включает в себя: . Несуществующие атрибуты игнорируются.'username', 'first_name', 'last_name', 'email'
Минимальная степень сходства для отклоненного пароля может быть установлена с помощью параметра
max_similarity
по шкале от 0 до 1. Значение 0 приведет к отклонению всех паролей, а значение 1 приведет только к отклонению пароли, совпадающие с одним из значений атрибута.
-
class
CommonPasswordValidator
( password_list_path = DEFAULT_PASSWORD_LIST_PATH ) ¶ Проверяет, что пароль не соответствует текущему паролю. Пароль преобразуется в нижний регистр (для сравнения без учета регистра), а затем сравнивается со списком из 20 000 общих паролей, созданным Ройсом Уильямсом .
password_list_path
может содержать путь к настраиваемому файлу общих паролей. Этот файл должен содержать один пароль в нижнем регистре в каждой строке и может быть сжатым с помощью gzip или обычным текстом.
-
класс
NumericPasswordValidator
¶ Убедитесь, что пароль не полностью числовой.
Интеграция валидации ¶
Некоторые функции django.contrib.auth.password_validation
можно вызывать из ваших собственных форм или кода для интеграции проверки пароля. Это может быть полезно, например, если вы используете настраиваемые формы для установки паролей или если вы пишете вызовы API для установки паролей.
-
validate_password
( пароль , пользователь = Нет , password_validators = Нет ) ¶ Проверяет пароль. Если все валидаторы верят, что пароль действителен,
None
будет возвращен. Если один или несколько валидаторов отклоняют пароль, выдается исключение,ValidationError
содержащее все сообщения об ошибках валидаторов.Объект не
user
является обязательным: если его нет, некоторые валидаторы не смогут выполнить проверку и, следовательно, примут любой пароль.
-
password_changed
( пароль , пользователь = Нет , password_validators = Нет ) ¶ Уведомляет всех валидаторов об изменении пароля. Это может использоваться некоторыми валидаторами, например, чтобы избежать повторного использования одного и того же пароля. Этот метод следует вызывать после успешной смены пароля.
Для подклассов
AbstractBaseUser
, поле пароля будет помечено как «грязное» при вызове,set_password()
который вызывает вызовpassword_changed()
после регистрации пользователя.
-
password_validators_help_texts
( password_validators = Нет ) ¶ Возвращает список справочных текстов для всех валидаторов. Они объясняют требования к паролям для пользователей.
-
password_validators_help_text_html
( password_validators = Нет ) ¶ Вернуть строку HTML со всем текстом справки в теге
<ul>
. Это удобно при добавлении проверки пароля в формы, поскольку позволяет передавать это содержимое непосредственно в параметрhelp_text
поля формы.
-
get_password_validators
( validator_config ) ¶ Возвращает набор объектов валидатора на основе параметра
validator_config
. По умолчанию все функции используют валидаторы, определенные вAUTH_PASSWORD_VALIDATORS
, но, вызывая эту функцию с другим набором валидаторов и передавая результат в параметреpassword_validators
других функций, затем будет использоваться этот набор валидаторов. Это полезно, когда определенный набор валидаторов применяется к большинству сценариев, но в некоторых случаях требуется другой набор. Если вы все еще используете те же валидаторы, эта функция бесполезна, потому чтоAUTH_PASSWORD_VALIDATORS
по умолчанию используется конфигурация из .Структура
validator_config
идентична структуреAUTH_PASSWORD_VALIDATORS
. Значение, возвращаемое этой функцией, может быть передано в параметреpassword_validators
функций, представленных ранее.
Обратите внимание, что если пароль передается в качестве параметра одной из этих функций, это всегда открытый пароль, а не хешированная версия.
Написание собственного валидатора ¶
Если встроенных в Django валидаторов недостаточно, вы можете написать свой собственный валидатор пароля. Валидаторы имеют довольно ограниченный интерфейс. Они должны реализовать два метода:
validate(self, password, user=None)
: проверить пароль. Возвращает,None
если пароль действителен, или генерирует ошибкуValidationError
с сообщением об ошибке, если пароль недействителен. Метод должен иметь возможность управлятьuser
допустимым пользователемNone
; если это означает, что валидатор не может подать заявку, отправьте повторно,None
чтобы убедиться, что ошибки нет.get_help_text()
: предоставляет текст справки для объяснения требований пользователю.
Все , что в OPTIONS
в AUTH_PASSWORD_VALIDATORS
течение вашего валидатора будет передано в конструктор. Все параметры конструктора должны иметь значение по умолчанию.
Вот базовый пример валидатора с необязательной настройкой:
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
class MinimumLengthValidator:
def __init__(self, min_length=8):
self.min_length = min_length
def validate(self, password, user=None):
if len(password) < self.min_length:
raise ValidationError(
_("This password must contain at least %(min_length)d characters."),
code='password_too_short',
params={'min_length': self.min_length},
)
def get_help_text(self):
return _(
"Your password must contain at least %(min_length)d characters."
% {'min_length': self.min_length}
)
Вы также можете реализовать ), который будет вызываться после успешной смены пароля. Это можно использовать, например, для предотвращения повторного использования пароля. Однако, если вы решили сохранить прошлые пароли пользователя, вам никогда не следует хранить пароли в открытом виде.password_changed(password, user=None