Ticket #19043: password_hashing.diff
File password_hashing.diff, 5.4 KB (added by , 12 years ago) |
---|
-
django/contrib/auth/hashers.py
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index c628059..59ec803 100644
a b def check_password(password, encoded, setter=None, preferred='default'): 51 51 preferred = get_hasher(preferred) 52 52 hasher = identify_hasher(encoded) 53 53 54 must_update = hasher.algorithm != preferred.algorithm 54 must_update = ( 55 hasher.algorithm != preferred.algorithm or 56 not hasher.is_current(encoded)) 55 57 is_correct = hasher.verify(password, encoded) 58 56 59 if setter and is_correct and must_update: 57 60 setter(password) 58 61 return is_correct … … class BasePasswordHasher(object): 176 179 raise ValueError("Hasher '%s' doesn't specify a library attribute" % 177 180 self.__class__) 178 181 182 def is_current(self): 183 """ 184 Determines whether or not a password needs to be reencoded based on 185 arbitrary criteria defined by the Hasher 186 """ 187 return True 188 179 189 def salt(self): 180 190 """ 181 191 Generates a cryptographically secure nonce salt in ascii … … class PBKDF2PasswordHasher(BasePasswordHasher): 216 226 safely but you must rename the algorithm if you change SHA256. 217 227 """ 218 228 algorithm = "pbkdf2_sha256" 219 iterations = 10000229 iterations = getattr(settings, 'PBKDF2_ITERATIONS', 10000) 220 230 digest = hashlib.sha256 221 231 232 def is_current(self, encoded): 233 summary = self.safe_summary(encoded) 234 return int(summary['iterations']) == self.iterations 235 222 236 def encode(self, password, salt, iterations=None): 223 237 assert password 224 238 assert salt and '$' not in salt … … class BCryptPasswordHasher(BasePasswordHasher): 267 281 """ 268 282 algorithm = "bcrypt" 269 283 library = ("py-bcrypt", "bcrypt") 270 rounds = 12 284 rounds = getattr(settings, 'BCRYPT_ROUNDS', 12) 285 286 def is_current(self, encoded): 287 summary = self.safe_summary(encoded) 288 return int(summary['work factor']) == self.rounds 271 289 272 290 def salt(self): 273 291 bcrypt = self._load_library() -
django/contrib/auth/tests/hashers.py
diff --git a/django/contrib/auth/tests/hashers.py b/django/contrib/auth/tests/hashers.py index d867a57..c29d32b 100644
a b from __future__ import unicode_literals 3 3 from django.conf.global_settings import PASSWORD_HASHERS as default_hashers 4 4 from django.contrib.auth.hashers import (is_password_usable, 5 5 check_password, make_password, PBKDF2PasswordHasher, load_hashers, 6 PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, UNUSABLE_PASSWORD) 6 PBKDF2SHA1PasswordHasher, BCryptPasswordHasher, get_hasher, identify_hasher, 7 UNUSABLE_PASSWORD) 7 8 from django.utils import unittest 8 9 from django.utils.unittest import skipUnless 9 10 … … class TestUtilsHashPass(unittest.TestCase): 118 119 'pbkdf2_sha1$10000$seasalt$91JiNKgwADC8j2j86Ije/cc4vfQ=') 119 120 self.assertTrue(hasher.verify('letmein', encoded)) 120 121 122 def test_pbkdf2_is_current_returns_false_if_iterations_differs(self): 123 hasher = PBKDF2PasswordHasher() 124 hasher.iterations = 1000 125 encoded = hasher.encode('letmein', 'seasalt') 126 hasher.iterations = 2000 127 self.assertFalse(hasher.is_current(encoded)) 128 129 @skipUnless(bcrypt, "py-bcrypt not installed") 130 def test_bcrypt_is_current_returns_false_if_work_factor_differs(self): 131 hasher = BCryptPasswordHasher() 132 hasher.rounds = 2 133 encoded = hasher.encode('letmein', hasher.salt()) 134 hasher.rounds = 3 135 self.assertFalse(hasher.is_current(encoded)) 136 137 def test_check_password_setter_called_when_is_current_false(self): 138 class ExceptionSetterCalled(Exception): 139 pass 140 def setter(password): 141 raise ExceptionSetterCalled 142 raw_password = 'letmein' 143 hasher = PBKDF2PasswordHasher() 144 hasher.iterations = 1000 145 encoded = make_password(raw_password, hasher=hasher) 146 hasher.iterations = 2000 147 self.assertRaises(ExceptionSetterCalled, 148 check_password, *(raw_password, encoded), **{ 'setter': setter }) 149 150 def test_check_password_setter_not_called_when_is_current_true(self): 151 class ExceptionSetterCalled(Exception): 152 pass 153 def setter(password): 154 raise ExceptionSetterCalled 155 raw_password = 'letmein' 156 hasher = PBKDF2PasswordHasher() 157 encoded = make_password(raw_password, hasher=hasher) 158 self.assertTrue(check_password(raw_password, encoded, setter)) 159 121 160 def test_upgrade(self): 122 161 self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm) 123 162 for algo in ('sha1', 'md5'): -
docs/ref/settings.txt
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index a909c12..dc2097e 100644
a b Default: 'auth.User' 119 119 120 120 The model to use to represent a User. See :ref:`auth-custom-user`. 121 121 122 BCRYPT_ROUNDS 123 --------------- 124 125 Default: '12' 126 127 The work factor used for generating bcrypt hashes when using Django's BCryptPasswordHasher. 128 122 129 .. setting:: CACHES 123 130 124 131 CACHES … … The number of days a password reset link is valid for. Used by the 1459 1466 1460 1467 .. setting:: PREPEND_WWW 1461 1468 1469 PBKDF2_ITERATIONS 1470 --------------- 1471 1472 Default: '10000' 1473 1474 The number of iterations used when generating PBKDF2 hashes when using Django's 1475 PBKDF2PasswordHasher. 1476 1462 1477 PREPEND_WWW 1463 1478 ----------- 1464 1479