mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							1ba0b22a7a
						
					
				
				
					commit
					a5033dbc58
				
			| @@ -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): | ||||
|     """ | ||||
|   | ||||
| @@ -142,7 +142,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.0.0+ | ||||
| *  argon2-cffi_ 16.1.0+ | ||||
| *  bcrypt_ | ||||
| *  docutils_ | ||||
| *  enum34_ (Python 2 only) | ||||
|   | ||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -49,7 +49,7 @@ setup( | ||||
|     ]}, | ||||
|     extras_require={ | ||||
|         "bcrypt": ["bcrypt"], | ||||
|         "argon2": ["argon2-cffi >= 16.0.0"], | ||||
|         "argon2": ["argon2-cffi >= 16.1.0"], | ||||
|     }, | ||||
|     zip_safe=False, | ||||
|     classifiers=[ | ||||
|   | ||||
| @@ -457,12 +457,44 @@ class TestUtilsHashPassArgon2(SimpleTestCase): | ||||
|         self.assertTrue(is_password_usable(blank_encoded)) | ||||
|         self.assertTrue(check_password('', blank_encoded)) | ||||
|         self.assertFalse(check_password(' ', blank_encoded)) | ||||
|         # Old hashes without version attribute | ||||
|         encoded = ( | ||||
|             'argon2$argon2i$m=8,t=1,p=1$c29tZXNhbHQ$gwQOXSNhxiOxPOA0+PY10P9QFO' | ||||
|             '4NAYysnqRt1GSQLE55m+2GYDt9FEjPMHhP2Cuf0nOEXXMocVrsJAtNSsKyfg' | ||||
|         ) | ||||
|         self.assertTrue(check_password('secret', encoded)) | ||||
|         self.assertFalse(check_password('wrong', encoded)) | ||||
|  | ||||
|     def test_argon2_upgrade(self): | ||||
|         self._test_argon2_upgrade('time_cost', 'time cost', 1) | ||||
|         self._test_argon2_upgrade('memory_cost', 'memory cost', 16) | ||||
|         self._test_argon2_upgrade('parallelism', 'parallelism', 1) | ||||
|  | ||||
|     def test_argon2_version_upgrade(self): | ||||
|         hasher = get_hasher('argon2') | ||||
|         state = {'upgraded': False} | ||||
|         encoded = ( | ||||
|             'argon2$argon2i$m=8,t=1,p=1$c29tZXNhbHQ$gwQOXSNhxiOxPOA0+PY10P9QFO' | ||||
|             '4NAYysnqRt1GSQLE55m+2GYDt9FEjPMHhP2Cuf0nOEXXMocVrsJAtNSsKyfg' | ||||
|         ) | ||||
|  | ||||
|         def setter(password): | ||||
|             state['upgraded'] = True | ||||
|  | ||||
|         old_m = hasher.memory_cost | ||||
|         old_t = hasher.time_cost | ||||
|         old_p = hasher.parallelism | ||||
|         try: | ||||
|             hasher.memory_cost = 8 | ||||
|             hasher.time_cost = 1 | ||||
|             hasher.parallelism = 1 | ||||
|             self.assertTrue(check_password('secret', encoded, setter, 'argon2')) | ||||
|             self.assertTrue(state['upgraded']) | ||||
|         finally: | ||||
|             hasher.memory_cost = old_m | ||||
|             hasher.time_cost = old_t | ||||
|             hasher.parallelism = old_p | ||||
|  | ||||
|     def _test_argon2_upgrade(self, attr, summary_key, new_value): | ||||
|         hasher = get_hasher('argon2') | ||||
|         self.assertEqual('argon2', hasher.algorithm) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| argon2-cffi == 16.0.0 | ||||
| argon2-cffi >= 16.1.0 | ||||
| bcrypt | ||||
| docutils | ||||
| geoip2 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user