From b86abbceb9a96d7a0fe18047c8fcd6fca90a2f3e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 26 Feb 2015 14:04:24 -0500 Subject: [PATCH] Fixed #24115 -- Allowed bcrypt hashers to upgrade passwords on rounds change. Thanks Florian Apolloner for the review. --- django/contrib/auth/hashers.py | 4 ++++ docs/releases/1.9.txt | 3 +++ tests/auth_tests/test_hashers.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index b56f74f0d9..0188f4a4e7 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -337,6 +337,10 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher): (_('checksum'), mask_hash(checksum)), ]) + def must_update(self, encoded): + algorithm, empty, algostr, rounds, data = encoded.split('$', 4) + return int(rounds) != self.rounds + class BCryptPasswordHasher(BCryptSHA256PasswordHasher): """ diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 41ac4f27c9..3bcbd8bbfe 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -56,6 +56,9 @@ Minor features subclassed ``django.contrib.auth.hashers.PBKDF2PasswordHasher`` to change the default value. +* The ``BCryptSHA256PasswordHasher`` will now update passwords if its + ``rounds`` attribute is changed. + :mod:`django.contrib.gis` ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py index f264926aff..993010b2f4 100644 --- a/tests/auth_tests/test_hashers.py +++ b/tests/auth_tests/test_hashers.py @@ -177,6 +177,38 @@ class TestUtilsHashPass(SimpleTestCase): self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) + @skipUnless(bcrypt, "bcrypt not installed") + def test_bcrypt_upgrade(self): + hasher = get_hasher('bcrypt') + self.assertEqual('bcrypt', hasher.algorithm) + self.assertNotEqual(hasher.rounds, 4) + + old_rounds = hasher.rounds + try: + # Generate a password with 4 rounds. + hasher.rounds = 4 + encoded = make_password('letmein', hasher='bcrypt') + rounds = hasher.safe_summary(encoded)['work factor'] + self.assertEqual(rounds, '04') + + state = {'upgraded': False} + + def setter(password): + state['upgraded'] = True + + # Check that no upgrade is triggered. + self.assertTrue(check_password('letmein', encoded, setter, 'bcrypt')) + self.assertFalse(state['upgraded']) + + # Revert to the old rounds count and ... + hasher.rounds = old_rounds + + # ... check if the password would get updated to the new count. + self.assertTrue(check_password('letmein', encoded, setter, 'bcrypt')) + self.assertTrue(state['upgraded']) + finally: + hasher.rounds = old_rounds + def test_unusable(self): encoded = make_password(None) self.assertEqual(len(encoded), len(UNUSABLE_PASSWORD_PREFIX) + UNUSABLE_PASSWORD_SUFFIX_LENGTH)