Commit b86abbce authored by Tim Graham's avatar Tim Graham
Browse files

Fixed #24115 -- Allowed bcrypt hashers to upgrade passwords on rounds change.

Thanks Florian Apolloner for the review.
parent e4cf8c84
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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):
    """
+3 −0
Original line number Diff line number Diff line
@@ -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`
^^^^^^^^^^^^^^^^^^^^^^^^^^

+32 −0
Original line number Diff line number Diff line
@@ -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)