summaryrefslogtreecommitdiff
path: root/tests/auth_tests/test_hashers.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auth_tests/test_hashers.py')
-rw-r--r--tests/auth_tests/test_hashers.py58
1 files changed, 57 insertions, 1 deletions
diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py
index d79a246276..ecd3f276a9 100644
--- a/tests/auth_tests/test_hashers.py
+++ b/tests/auth_tests/test_hashers.py
@@ -10,9 +10,10 @@ from django.contrib.auth.hashers import (
check_password, get_hasher, identify_hasher, is_password_usable,
make_password,
)
-from django.test import SimpleTestCase
+from django.test import SimpleTestCase, mock
from django.test.utils import override_settings
from django.utils import six
+from django.utils.encoding import force_bytes
try:
import crypt
@@ -214,6 +215,28 @@ class TestUtilsHashPass(SimpleTestCase):
finally:
hasher.rounds = old_rounds
+ @skipUnless(bcrypt, "bcrypt not installed")
+ def test_bcrypt_harden_runtime(self):
+ hasher = get_hasher('bcrypt')
+ self.assertEqual('bcrypt', hasher.algorithm)
+
+ with mock.patch.object(hasher, 'rounds', 4):
+ encoded = make_password('letmein', hasher='bcrypt')
+
+ with mock.patch.object(hasher, 'rounds', 6), \
+ mock.patch.object(hasher, 'encode', side_effect=hasher.encode):
+ hasher.harden_runtime('wrong_password', encoded)
+
+ # Increasing rounds from 4 to 6 means an increase of 4 in workload,
+ # therefore hardening should run 3 times to make the timing the
+ # same (the original encode() call already ran once).
+ self.assertEqual(hasher.encode.call_count, 3)
+
+ # Get the original salt (includes the original workload factor)
+ algorithm, data = encoded.split('$', 1)
+ expected_call = (('wrong_password', force_bytes(data[:29])),)
+ self.assertEqual(hasher.encode.call_args_list, [expected_call] * 3)
+
def test_unusable(self):
encoded = make_password(None)
self.assertEqual(len(encoded), len(UNUSABLE_PASSWORD_PREFIX) + UNUSABLE_PASSWORD_SUFFIX_LENGTH)
@@ -337,6 +360,25 @@ class TestUtilsHashPass(SimpleTestCase):
finally:
hasher.iterations = old_iterations
+ def test_pbkdf2_harden_runtime(self):
+ hasher = get_hasher('default')
+ self.assertEqual('pbkdf2_sha256', hasher.algorithm)
+
+ with mock.patch.object(hasher, 'iterations', 1):
+ encoded = make_password('letmein')
+
+ with mock.patch.object(hasher, 'iterations', 6), \
+ mock.patch.object(hasher, 'encode', side_effect=hasher.encode):
+ hasher.harden_runtime('wrong_password', encoded)
+
+ # Encode should get called once ...
+ self.assertEqual(hasher.encode.call_count, 1)
+
+ # ... with the original salt and 5 iterations.
+ algorithm, iterations, salt, hash = encoded.split('$', 3)
+ expected_call = (('wrong_password', salt, 5),)
+ self.assertEqual(hasher.encode.call_args, expected_call)
+
def test_pbkdf2_upgrade_new_hasher(self):
hasher = get_hasher('default')
self.assertEqual('pbkdf2_sha256', hasher.algorithm)
@@ -365,6 +407,20 @@ class TestUtilsHashPass(SimpleTestCase):
self.assertTrue(check_password('letmein', encoded, setter))
self.assertTrue(state['upgraded'])
+ def test_check_password_calls_harden_runtime(self):
+ hasher = get_hasher('default')
+ encoded = make_password('letmein')
+
+ with mock.patch.object(hasher, 'harden_runtime'), \
+ mock.patch.object(hasher, 'must_update', return_value=True):
+ # Correct password supplied, no hardening needed
+ check_password('letmein', encoded)
+ self.assertEqual(hasher.harden_runtime.call_count, 0)
+
+ # Wrong password supplied, hardening needed
+ check_password('wrong_password', encoded)
+ self.assertEqual(hasher.harden_runtime.call_count, 1)
+
def test_load_library_no_algorithm(self):
with self.assertRaises(ValueError) as e:
BasePasswordHasher()._load_library()