diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 4e40a7c2ba..184e60b8d2 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -222,12 +222,12 @@ class PBKDF2PasswordHasher(BasePasswordHasher): """ Secure password hashing using the PBKDF2 algorithm (recommended) - Configured to use PBKDF2 + HMAC + SHA256 with 12000 iterations. + Configured to use PBKDF2 + HMAC + SHA256 with 20000 iterations. The result is a 64 byte binary string. Iterations may be changed safely but you must rename the algorithm if you change SHA256. """ algorithm = "pbkdf2_sha256" - iterations = 12000 + iterations = 20000 digest = hashlib.sha256 def encode(self, password, salt, iterations=None): diff --git a/django/contrib/auth/tests/test_hashers.py b/django/contrib/auth/tests/test_hashers.py index 58628cd6cd..f7ee77aedb 100644 --- a/django/contrib/auth/tests/test_hashers.py +++ b/django/contrib/auth/tests/test_hashers.py @@ -47,7 +47,7 @@ class TestUtilsHashPass(SimpleTestCase): def test_pkbdf2(self): encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256') self.assertEqual(encoded, - 'pbkdf2_sha256$12000$seasalt$Ybw8zsFxqja97tY/o6G+Fy1ksY4U/Hw3DRrGED6Up4s=') + 'pbkdf2_sha256$20000$seasalt$oBSd886ysm3AqYun62DOdin8YcfbU1z9cksZSuLP9r0=') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) @@ -211,14 +211,14 @@ class TestUtilsHashPass(SimpleTestCase): hasher = PBKDF2PasswordHasher() encoded = hasher.encode('lètmein', 'seasalt2') self.assertEqual(encoded, - 'pbkdf2_sha256$12000$seasalt2$hlDLKsxgkgb1aeOppkM5atCYw5rPzAjCNQZ4NYyUROw=') + 'pbkdf2_sha256$20000$seasalt2$Flpve/uAcyo6+IFI6YAhjeABGPVbRQjzHDxRhqxewgw=') self.assertTrue(hasher.verify('lètmein', encoded)) def test_low_level_pbkdf2_sha1(self): hasher = PBKDF2SHA1PasswordHasher() encoded = hasher.encode('lètmein', 'seasalt2') self.assertEqual(encoded, - 'pbkdf2_sha1$12000$seasalt2$JeMRVfjjgtWw3/HzlnlfqBnQ6CA=') + 'pbkdf2_sha1$20000$seasalt2$pJt86NmjAweBY1StBvxCu7l1o9o=') self.assertTrue(hasher.verify('lètmein', encoded)) def test_upgrade(self): diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 3cf40a4848..b3823a015d 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -145,12 +145,14 @@ else: HMAC+SHA256 is used as the default pseudo random function. - As of 2011, 10,000 iterations was the recommended default which - took 100ms on a 2.2Ghz Core 2 Duo. This is probably the bare - minimum for security given 1000 iterations was recommended in - 2001. This code is very well optimized for CPython and is only - four times slower than openssl's implementation. Look in - django.contrib.auth.hashers for the present default. + As of 2014, 100,000 iterations was the recommended default which took + 100ms on a 2.7Ghz Intel i7 with an optimized implementation. This is + probably the bare minimum for security given 1000 iterations was + recommended in 2001. This code is very well optimized for CPython and + is about five times slower than OpenSSL's implementation. Look in + django.contrib.auth.hashers for the present default, it is lower than + the recommended 100,000 because of the performance difference between + this and an optimized implementation. """ assert iterations > 0 if not digest: diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt index b5d27fec71..cbc411b0aa 100644 --- a/docs/internals/howto-release-django.txt +++ b/docs/internals/howto-release-django.txt @@ -91,7 +91,7 @@ any time leading up to the actual release: #. If this is a major release, make sure the tests pass, then increase the default PBKDF2 iterations in - ``django.contrib.auth.hashers.PBKDF2PasswordHasher`` by about 10% + ``django.contrib.auth.hashers.PBKDF2PasswordHasher`` by about 20% (pick a round number). Run the tests, and update the 3 failing hasher tests with the new values. Make sure this gets noted in the release notes (see release notes on 1.6 for an example).