From faad809e09af5617f1cda579f5b031233be21cb3 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 17 Jun 2020 07:46:45 +0200 Subject: [PATCH] Refs #30472 -- Simplified Argon2PasswordHasher with argon2-cffi 19.1+ API. --- django/contrib/auth/hashers.py | 68 +++++++++---------- .../contributing/writing-code/unit-tests.txt | 2 +- docs/releases/3.2.txt | 2 + setup.cfg | 2 +- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 29cba3fd5c1..344176be694 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -307,14 +307,15 @@ class Argon2PasswordHasher(BasePasswordHasher): def encode(self, password, salt): argon2 = self._load_library() + params = self.params() data = argon2.low_level.hash_secret( password.encode(), salt.encode(), - time_cost=self.time_cost, - memory_cost=self.memory_cost, - parallelism=self.parallelism, - hash_len=argon2.DEFAULT_HASH_LENGTH, - type=argon2.low_level.Type.I, + time_cost=params.time_cost, + memory_cost=params.memory_cost, + parallelism=params.parallelism, + hash_len=params.hash_len, + type=params.type, ) return self.algorithm + data.decode('ascii') @@ -323,11 +324,7 @@ class Argon2PasswordHasher(BasePasswordHasher): algorithm, rest = encoded.split('$', 1) assert algorithm == self.algorithm try: - return argon2.low_level.verify_secret( - ('$' + rest).encode('ascii'), - password.encode(), - type=argon2.low_level.Type.I, - ) + return argon2.PasswordHasher().verify('$' + rest, password) except argon2.exceptions.VerificationError: return False @@ -347,22 +344,34 @@ class Argon2PasswordHasher(BasePasswordHasher): } def must_update(self, encoded): - (algorithm, variety, version, time_cost, memory_cost, parallelism, - salt, data) = self._decode(encoded) + algorithm, rest = encoded.split('$', 1) assert algorithm == self.algorithm argon2 = self._load_library() - return ( - argon2.low_level.ARGON2_VERSION != version or - self.time_cost != time_cost or - self.memory_cost != memory_cost or - self.parallelism != parallelism - ) + current_params = argon2.extract_parameters('$' + rest) + new_params = self.params() + # Set salt_len to the salt_len of the current parameters because salt + # is explicitly passed to argon2. + new_params.salt_len = current_params.salt_len + return current_params != new_params def harden_runtime(self, password, encoded): # The runtime for Argon2 is too complicated to implement a sensible # hardening algorithm. pass + 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.I, + 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, + ) + def _decode(self, encoded): """ Split an encoded hash and return: ( @@ -370,24 +379,13 @@ class Argon2PasswordHasher(BasePasswordHasher): parallelism, salt, data, ). """ - bits = encoded.split('$') - if len(bits) == 5: - # Argon2 < 1.3 - algorithm, variety, raw_params, salt, data = bits - version = 0x10 - else: - assert len(bits) == 6 - algorithm, variety, raw_version, raw_params, salt, data = bits - assert raw_version.startswith('v=') - version = int(raw_version[len('v='):]) - params = dict(bit.split('=', 1) for bit in raw_params.split(',')) - assert len(params) == 3 and all(x in params for x in ('t', 'm', 'p')) - time_cost = int(params['t']) - memory_cost = int(params['m']) - parallelism = int(params['p']) + argon2 = self._load_library() + algorithm, rest = encoded.split('$', 1) + params = argon2.extract_parameters('$' + rest) + variety, *_, salt, data = rest.split('$') return ( - algorithm, variety, version, time_cost, memory_cost, parallelism, - salt, data, + algorithm, variety, params.version, params.time_cost, + params.memory_cost, params.parallelism, salt, data, ) diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index deaa5a0b196..f8c1347abc5 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -270,7 +270,7 @@ Running all the tests If you want to run the full suite of tests, you'll need to install a number of dependencies: -* argon2-cffi_ 16.1.0+ +* argon2-cffi_ 19.1.0+ * asgiref_ 3.2+ (required) * bcrypt_ * docutils_ diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index 1bb4dbed1e2..11da13daba8 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -324,6 +324,8 @@ Miscellaneous * The :tfilter:`intcomma` and :tfilter:`intword` template filters no longer depend on the :setting:`USE_L10N` setting. +* Support for ``argon2-cffi`` < 19.1.0 is removed. + .. _deprecated-features-3.2: Features deprecated in 3.2 diff --git a/setup.cfg b/setup.cfg index 6ce81ec081f..0c1ff825fb3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,7 +49,7 @@ console_scripts = django-admin = django.core.management:execute_from_command_line [options.extras_require] -argon2 = argon2-cffi >= 16.1.0 +argon2 = argon2-cffi >= 19.1.0 bcrypt = bcrypt [bdist_rpm]