Force update of the password on iteration count changes.

This commit is contained in:
Florian Apolloner 2013-09-24 20:52:20 +02:00
parent 1e4f53a6eb
commit 7d0d0dbf26
2 changed files with 40 additions and 0 deletions

View File

@ -56,6 +56,8 @@ def check_password(password, encoded, setter=None, preferred='default'):
hasher = identify_hasher(encoded) hasher = identify_hasher(encoded)
must_update = hasher.algorithm != preferred.algorithm must_update = hasher.algorithm != preferred.algorithm
if not must_update:
must_update = hasher.must_update(encoded)
is_correct = hasher.verify(password, encoded) is_correct = hasher.verify(password, encoded)
if setter and is_correct and must_update: if setter and is_correct and must_update:
setter(password) setter(password)
@ -212,6 +214,9 @@ class BasePasswordHasher(object):
""" """
raise NotImplementedError('subclasses of BasePasswordHasher must provide a safe_summary() method') raise NotImplementedError('subclasses of BasePasswordHasher must provide a safe_summary() method')
def must_update(self, encoded):
return False
class PBKDF2PasswordHasher(BasePasswordHasher): class PBKDF2PasswordHasher(BasePasswordHasher):
""" """
@ -250,6 +255,10 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
(_('hash'), mask_hash(hash)), (_('hash'), mask_hash(hash)),
]) ])
def must_update(self, encoded):
algorithm, iterations, salt, hash = encoded.split('$', 3)
return int(iterations) != self.iterations
class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher): class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
""" """

View File

@ -245,6 +245,37 @@ class TestUtilsHashPass(unittest.TestCase):
self.assertFalse(check_password('WRONG', encoded, setter)) self.assertFalse(check_password('WRONG', encoded, setter))
self.assertFalse(state['upgraded']) self.assertFalse(state['upgraded'])
def test_pbkdf2_upgrade(self):
self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
hasher = get_hasher('default')
self.assertNotEqual(hasher.iterations, 1)
old_iterations = hasher.iterations
try:
# Generate a password with 1 iteration.
hasher.iterations = 1
encoded = make_password('letmein')
algo, iterations, salt, hash = encoded.split('$', 3)
self.assertEqual(iterations, '1')
state = {'upgraded': False}
def setter(password):
state['upgraded'] = True
# Check that no upgrade is triggerd
self.assertTrue(check_password('letmein', encoded, setter))
self.assertFalse(state['upgraded'])
# Revert to the old iteration count and ...
hasher.iterations = old_iterations
# ... check if the password would get updated to the new iteration count.
self.assertTrue(check_password('letmein', encoded, setter))
self.assertTrue(state['upgraded'])
finally:
hasher.iterations = old_iterations
def test_load_library_no_algorithm(self): def test_load_library_no_algorithm(self):
with self.assertRaises(ValueError) as e: with self.assertRaises(ValueError) as e:
BasePasswordHasher()._load_library() BasePasswordHasher()._load_library()