Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Force update of the password on iteration count changes.

  • Loading branch information...
commit 7d0d0dbf26a3c0d16e9c2b930fd6d7b89f215946 1 parent 1e4f53a
Florian Apolloner authored September 24, 2013
9  django/contrib/auth/hashers.py
@@ -56,6 +56,8 @@ def check_password(password, encoded, setter=None, preferred='default'):
56 56
     hasher = identify_hasher(encoded)
57 57
 
58 58
     must_update = hasher.algorithm != preferred.algorithm
  59
+    if not must_update:
  60
+        must_update = hasher.must_update(encoded)
59 61
     is_correct = hasher.verify(password, encoded)
60 62
     if setter and is_correct and must_update:
61 63
         setter(password)
@@ -212,6 +214,9 @@ def safe_summary(self, encoded):
212 214
         """
213 215
         raise NotImplementedError('subclasses of BasePasswordHasher must provide a safe_summary() method')
214 216
 
  217
+    def must_update(self, encoded):
  218
+        return False
  219
+
215 220
 
216 221
 class PBKDF2PasswordHasher(BasePasswordHasher):
217 222
     """
@@ -250,6 +255,10 @@ def safe_summary(self, encoded):
250 255
             (_('hash'), mask_hash(hash)),
251 256
         ])
252 257
 
  258
+    def must_update(self, encoded):
  259
+        algorithm, iterations, salt, hash = encoded.split('$', 3)
  260
+        return int(iterations) != self.iterations
  261
+
253 262
 
254 263
 class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
255 264
     """
31  django/contrib/auth/tests/test_hashers.py
@@ -245,6 +245,37 @@ def setter():
245 245
             self.assertFalse(check_password('WRONG', encoded, setter))
246 246
             self.assertFalse(state['upgraded'])
247 247
 
  248
+    def test_pbkdf2_upgrade(self):
  249
+        self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
  250
+        hasher = get_hasher('default')
  251
+        self.assertNotEqual(hasher.iterations, 1)
  252
+
  253
+        old_iterations = hasher.iterations
  254
+        try:
  255
+            # Generate a password with 1 iteration.
  256
+            hasher.iterations = 1
  257
+            encoded = make_password('letmein')
  258
+            algo, iterations, salt, hash = encoded.split('$', 3)
  259
+            self.assertEqual(iterations, '1')
  260
+
  261
+            state = {'upgraded': False}
  262
+            def setter(password):
  263
+                state['upgraded'] = True
  264
+
  265
+            # Check that no upgrade is triggerd
  266
+            self.assertTrue(check_password('letmein', encoded, setter))
  267
+            self.assertFalse(state['upgraded'])
  268
+
  269
+            # Revert to the old iteration count and ...
  270
+            hasher.iterations = old_iterations
  271
+
  272
+            # ... check if the password would get updated to the new iteration count.
  273
+            self.assertTrue(check_password('letmein', encoded, setter))
  274
+            self.assertTrue(state['upgraded'])
  275
+        finally:
  276
+            hasher.iterations = old_iterations
  277
+
  278
+
248 279
     def test_load_library_no_algorithm(self):
249 280
         with self.assertRaises(ValueError) as e:
250 281
             BasePasswordHasher()._load_library()

0 notes on commit 7d0d0db

Please sign in to comment.
Something went wrong with that request. Please try again.