Невозможно войти в проект 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.

Вернуться на верх