diff options
| author | Bas Westerbaan <bas@westerbaan.name> | 2016-04-22 13:26:41 +0200 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2016-04-25 21:17:53 -0400 |
| commit | a5033dbc58d6e09d28b8abe3acda20b9c60e0b8e (patch) | |
| tree | 7d8edefe51942a50bafbef0903c9e417de7858af /django | |
| parent | 1ba0b22a7a262e63cb7152ef3ab513660156d135 (diff) | |
Refs #26033 -- Added password hasher support for Argon2 v1.3.
The previous version of Argon2 uses encoded hashes of the form:
$argon2d$m=8,t=1,p=1$<salt>$<data>
The new version of Argon2 adds its version into the hash:
$argon2d$v=19$m=8,t=1,p=1$<salt>$<data>
This lets Django handle both version properly.
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/auth/hashers.py | 56 |
1 files changed, 42 insertions, 14 deletions
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 6cd57f197a..8673b95cef 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -327,11 +327,11 @@ class Argon2PasswordHasher(BasePasswordHasher): def verify(self, password, encoded): argon2 = self._load_library() - algorithm, data = encoded.split('$', 1) + algorithm, rest = encoded.split('$', 1) assert algorithm == self.algorithm try: return argon2.low_level.verify_secret( - force_bytes('$' + data), + force_bytes('$' + rest), force_bytes(password), type=argon2.low_level.Type.I, ) @@ -339,29 +339,30 @@ class Argon2PasswordHasher(BasePasswordHasher): return False def safe_summary(self, encoded): - algorithm, variety, raw_pars, salt, data = encoded.split('$', 5) - pars = dict(bit.split('=', 1) for bit in raw_pars.split(',')) + (algorithm, variety, version, time_cost, memory_cost, parallelism, + salt, data) = self._decode(encoded) assert algorithm == self.algorithm - assert len(pars) == 3 and 't' in pars and 'm' in pars and 'p' in pars return OrderedDict([ (_('algorithm'), algorithm), (_('variety'), variety), - (_('memory cost'), int(pars['m'])), - (_('time cost'), int(pars['t'])), - (_('parallelism'), int(pars['p'])), + (_('version'), version), + (_('memory cost'), memory_cost), + (_('time cost'), time_cost), + (_('parallelism'), parallelism), (_('salt'), mask_hash(salt)), (_('hash'), mask_hash(data)), ]) def must_update(self, encoded): - algorithm, variety, raw_pars, salt, data = encoded.split('$', 5) - pars = dict([bit.split('=', 1) for bit in raw_pars.split(',')]) + (algorithm, variety, version, time_cost, memory_cost, parallelism, + salt, data) = self._decode(encoded) assert algorithm == self.algorithm - assert len(pars) == 3 and 't' in pars and 'm' in pars and 'p' in pars + argon2 = self._load_library() return ( - self.time_cost != int(pars['t']) or - self.memory_cost != int(pars['m']) or - self.parallelism != int(pars['p']) + argon2.low_level.ARGON2_VERSION != version or + self.time_cost != time_cost or + self.memory_cost != memory_cost or + self.parallelism != parallelism ) def harden_runtime(self, password, encoded): @@ -369,6 +370,33 @@ class Argon2PasswordHasher(BasePasswordHasher): # hardening algorithm. pass + def _decode(self, encoded): + """ + Split an encoded hash and return: ( + algorithm, variety, version, time_cost, memory_cost, + 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']) + return ( + algorithm, variety, version, time_cost, memory_cost, parallelism, + salt, data, + ) + class BCryptSHA256PasswordHasher(BasePasswordHasher): """ |
