Невозможно войти в проект Django после перехода на django 3 и возврата на django 2
У меня есть проект Django 2.2, который работает на куче разных серверов, но они используют одну и ту же базу данных.
Я создал ветку для перехода на Django 3, но не все серверы будут переведены одновременно.
Я использую Аргон2:
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
]
Когда я перешел на django 3.2 в моей ветке разработки, все работало нормально. Но затем, когда я вернулся к Django 2.2, я начал получать ошибки типа:
- Ошибка синтаксиса шаблона
- Неправильное заполнение (
exception location: .../python3.6/base64.py in b64decode
)
Такие проблемы решались простым удалением cookies и повторной загрузкой. Поэтому я предположил, что они были связаны с изменением в django 3.1 алгоритма хэширования по умолчанию с sha1 на sha256.
После перезагрузки страница заработала. Но когда я попытался войти в систему, она не распознала учетные данные.
Затем я восстановил базу данных из резервной копии и смог войти в систему в django 2.2.
Я снова попробовал запустить на django 3.2 с настройками:
DEFAULT_HASHING_ALGORITHM = 'sha1'
Теперь, при переходе обратно на 2.2, я не получал ошибок при загрузке страницы (мне не нужно было удалять cookies), но учетные данные по-прежнему не работали.
Для меня это выглядит так, как будто после перехода на django 3.2 изменилось хэширование паролей в базе данных.
Возможно ли, что django 3 переписывает пароли в базе данных? Может ли кто-нибудь указать на решение или что-то попробовать?
Спасибо.
Решение TL;DR
Похоже, что если вы используете хешер Argon2, Django действительно обновляет хранимый пароль, и если вы хотите временно избежать этого, вы должны обновить Django 3.1, пока не будете готовы перейти на 3.2, или подклассифицировать хешер.
Сравнение сохраненных паролей
Поскольку я могу получить доступ к производственной базе данных (django 2.2) и локальной базе данных, когда в Django 3, я сравниваю пароли на моем пользователе:
Django 2.2 (выпущен в апреле 2019 года)
алгоритм: argon2 тип: argon2i версия: 19 memory cost: 512 time cost: 2 parallelism: 2 salt: UVn ********** hash: QVt *****************
Django 3.2
алгоритм: argon2 разновидность: argon2id версия: 19 затраты памяти: 102,400 затраты времени: 2 параллелизм: 8 соль: pHQzc2 **************** хэш: flj ****************
Да, они разные!
В заметках к релизу Django 3.2 рассказывается об изменениях в хешере Argon2 по умолчанию:
django.contrib.auth
Количество итераций по умолчанию для хешера паролей PBKDF2 увеличено с 216,000 до 260,000.
Вариант по умолчанию для хешера паролей Argon2 изменен на Argon2id. memory_cost и parallelism увеличены до 102,400 и 8 соответственно, чтобы соответствовать настройкам по умолчанию argon2-cffi.
Увеличение memory_cost увеличивает требуемую память с 512 КБ до 100 МБ. Это все еще довольно консервативно, но может привести к проблемам в условиях средах с ограниченным объемом памяти. Если это так, то существующий hasher может быть подклассифицирован, чтобы переопределить значения по умолчанию.
Энтропия соли по умолчанию для хешеров паролей Argon2, MD5, PBKDF2, SHA-1 увеличена с 71 до 128 битов.
Вот билет этого изменения:
#30472 Argon2id должен поддерживаться и стать разновидностью по умолчанию для Argon2PasswordHasher
Посмотрев на код в django.contrib.auth.hashers
, я вижу, что пароли изменяются на check_password
:
def check_password(password, encoded, setter=None, preferred='default'):
...
hasher_changed = hasher.algorithm != preferred.algorithm
must_update = hasher_changed or preferred.must_update(encoded)
is_correct = hasher.verify(password, encoded)
...
class Argon2PasswordHasher(BasePasswordHasher):
...
def must_update(self, encoded):
decoded = self.decode(encoded)
current_params = decoded['params']
new_params = self.params()
...
def params(self):
argon2 = self._load_library()
# salt_len is a noop, because we provide our own salt.
return argon2.Parameters(
type=argon2.low_level.Type.ID,
version=argon2.low_level.ARGON2_VERSION,
salt_len=argon2.DEFAULT_RANDOM_SALT_LENGTH,
hash_len=argon2.DEFAULT_HASH_LENGTH,
time_cost=self.time_cost,
memory_cost=self.memory_cost,
parallelism=self.parallelism,
)
Эта строка type=argon2.low_level.Type.ID
изменяет тип аргона2 с argon2i
на argon2id
. Остальные изменения понятны.
Я не уверен, что это реальный процесс, но я думаю, что это происходит примерно так:
- Вы вводите пароль
- Пароль проверяется по старому алгоритму
- Если он совпадает, он перепроверяется новым алгоритмом и сохраняется
(буду рад узнать, если я ошибаюсь)
Вкратце: Моя проблема
Моя проблема заключается в том, что у меня есть несколько различных проектов Django 2, использующих один и тот же код общего ядра и одну и ту же базу данных. Хотя это разные проекты, есть много пользователей, которые имеют доступ ко всем из них. Я хочу обновить их постепенно, начиная с наименее чувствительного, чтобы посмотреть, не возникнут ли ошибки.
Решение 1
Обновление до Django 3.1 вместо 3.2. Это позволит мне обновлять различные проекты постепенно, не нарушая при этом доступ пользователей. После того, как все проекты поработают некоторое время с версией 3.1 и исправят все возникшие ошибки, я смогу с большей уверенностью обновить их все одновременно до django 3.2. Вот что я протестировал и это работает (это не меняет пароли).
Решение 2
Подклассифицируйте хэшер django.contrib.auth.hashers.Argon2PasswordHasher
, чтобы он не обновлял пароли. Укажите на него в настройках PASSWORD_HASHERS
и удалите его, когда все проекты будут работать гладко в django 3.2.