Ticket #21105: 0001-PBKDF2PrehashPasswordHasher.patch
File 0001-PBKDF2PrehashPasswordHasher.patch, 6.7 KB (added by , 11 years ago) |
---|
-
django/conf/global_settings.py
From dd392197de806d357c5aac276970893f7f0e3564 Mon Sep 17 00:00:00 2001 From: Ram Rachum <ram@rachum.com> Date: Mon, 16 Sep 2013 15:16:46 +0300 Subject: [PATCH] PBKDF2PrehashPasswordHasher --- django/conf/global_settings.py | 1 + django/contrib/auth/hashers.py | 51 +++++++++++++++++++++++++++++++ django/contrib/auth/tests/test_hashers.py | 20 ++++++++---- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 68d9ded..e620f0b 100644
a b PASSWORD_RESET_TIMEOUT_DAYS = 3 516 516 # password using different algorithms will be converted automatically 517 517 # upon login 518 518 PASSWORD_HASHERS = ( 519 'django.contrib.auth.hashers.PBKDF2PrehashPasswordHasher', 519 520 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 520 521 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 521 522 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', -
django/contrib/auth/hashers.py
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index e9a1449..1f7cf65 100644
a b class PBKDF2PasswordHasher(BasePasswordHasher): 267 267 ]) 268 268 269 269 270 class PBKDF2PrehashPasswordHasher(BasePasswordHasher): 271 """ 272 Secure password hashing using the PBKDF2 algorithm (recommended) 273 274 Configured to use PBKDF2 + HMAC + SHA256 with 10000 iterations. 275 The result is a 64 byte binary string. Iterations may be changed 276 safely but you must rename the algorithm if you change SHA256. 277 278 Pre-hashes the password with SHA256 before hashing in order to ensure that 279 computation time doesn't depend on password length. 280 """ 281 algorithm = "pbkdf2_prehash_sha256" 282 iterations = 10000 283 digest = hashlib.sha256 284 prehash = True 285 286 @password_max_length(MAXIMUM_PASSWORD_LENGTH) 287 def encode(self, password, salt, iterations=None): 288 assert password is not None 289 assert salt and '$' not in salt 290 if not iterations: 291 iterations = self.iterations 292 prehashed_password = binascii.hexlify( 293 self.digest(force_bytes(password)).digest() 294 ) 295 hash = pbkdf2(prehashed_password, salt, iterations, digest=self.digest) 296 hash = base64.b64encode(hash).decode('ascii').strip() 297 return "%s$%d$%s$%s$%s" % (self.algorithm, iterations, salt, 298 'prehash', hash) 299 300 @password_max_length(MAXIMUM_PASSWORD_LENGTH) 301 def verify(self, password, encoded): 302 algorithm, iterations, salt, prehash, hash = encoded.split('$', 4) 303 assert algorithm == self.algorithm 304 assert prehash == 'prehash' 305 encoded_2 = self.encode(password, salt, int(iterations)) 306 return constant_time_compare(encoded, encoded_2) 307 308 def safe_summary(self, encoded): 309 algorithm, iterations, salt, prehash, hash = encoded.split('$', 4) 310 assert algorithm == self.algorithm 311 assert prehash == True 312 return SortedDict([ 313 (_('algorithm'), algorithm), 314 (_('iterations'), iterations), 315 (_('salt'), mask_hash(salt)), 316 (_('prehash'), 'prehash'), 317 (_('hash'), mask_hash(hash)), 318 ]) 319 320 270 321 class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher): 271 322 """ 272 323 Alternate PBKDF2 hasher which uses SHA1, the default PRF -
django/contrib/auth/tests/test_hashers.py
diff --git a/django/contrib/auth/tests/test_hashers.py b/django/contrib/auth/tests/test_hashers.py index 8ae0353..a9f9bac 100644
a b from __future__ import unicode_literals 4 4 from django.conf.global_settings import PASSWORD_HASHERS as default_hashers 5 5 from django.contrib.auth.hashers import ( 6 6 is_password_usable, BasePasswordHasher, check_password, make_password, 7 PBKDF2PasswordHasher, load_hashers, PBKDF2SHA1PasswordHasher, get_hasher, 8 identify_hasher, UNUSABLE_PASSWORD_PREFIX, UNUSABLE_PASSWORD_SUFFIX_LENGTH, 7 PBKDF2PasswordHasher, PBKDF2PrehashPasswordHasher, load_hashers, 8 PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, 9 UNUSABLE_PASSWORD_PREFIX, UNUSABLE_PASSWORD_SUFFIX_LENGTH, 9 10 MAXIMUM_PASSWORD_LENGTH, password_max_length 10 11 ) 11 12 from django.utils import six … … class TestUtilsHashPass(unittest.TestCase): 31 32 32 33 def test_simple(self): 33 34 encoded = make_password('lètmein') 34 self.assertTrue(encoded.startswith('pbkdf2_ sha256$'))35 self.assertTrue(encoded.startswith('pbkdf2_prehash_sha256$')) 35 36 self.assertTrue(is_password_usable(encoded)) 36 37 self.assertTrue(check_password('lètmein', encoded)) 37 38 self.assertFalse(check_password('lètmeinz', encoded)) 38 39 # Blank passwords 39 40 blank_encoded = make_password('') 40 self.assertTrue(blank_encoded.startswith('pbkdf2_ sha256$'))41 self.assertTrue(blank_encoded.startswith('pbkdf2_prehash_sha256$')) 41 42 self.assertTrue(is_password_usable(blank_encoded)) 42 43 self.assertTrue(check_password('', blank_encoded)) 43 44 self.assertFalse(check_password(' ', blank_encoded)) … … class TestUtilsHashPass(unittest.TestCase): 288 289 'pbkdf2_sha256$10000$seasalt$CWWFdHOWwPnki7HvkcqN9iA2T3KLW1cf2uZ5kvArtVY=') 289 290 self.assertTrue(hasher.verify('lètmein', encoded)) 290 291 292 def test_low_level_pkbdf2_prehash(self): 293 hasher = PBKDF2PrehashPasswordHasher() 294 encoded = hasher.encode('lètmein', 'seasalt') 295 self.assertEqual(encoded, 296 'pbkdf2_prehash_sha256$10000$seasalt$prehash$5EsXdehiy03QeCdmMU7KfjYz30oUqDNX0uNkHIWTXqg=') 297 self.assertTrue(hasher.verify('lètmein', encoded)) 298 291 299 def test_low_level_pbkdf2_sha1(self): 292 300 hasher = PBKDF2SHA1PasswordHasher() 293 301 encoded = hasher.encode('lètmein', 'seasalt') … … class TestUtilsHashPass(unittest.TestCase): 296 304 self.assertTrue(hasher.verify('lètmein', encoded)) 297 305 298 306 def test_upgrade(self): 299 self.assertEqual('pbkdf2_ sha256', get_hasher('default').algorithm)307 self.assertEqual('pbkdf2_prehash_sha256', get_hasher('default').algorithm) 300 308 for algo in ('sha1', 'md5'): 301 309 encoded = make_password('lètmein', hasher=algo) 302 310 state = {'upgraded': False} … … class TestUtilsHashPass(unittest.TestCase): 314 322 self.assertFalse(state['upgraded']) 315 323 316 324 def test_no_upgrade_on_incorrect_pass(self): 317 self.assertEqual('pbkdf2_ sha256', get_hasher('default').algorithm)325 self.assertEqual('pbkdf2_prehash_sha256', get_hasher('default').algorithm) 318 326 for algo in ('sha1', 'md5'): 319 327 encoded = make_password('lètmein', hasher=algo) 320 328 state = {'upgraded': False}