From 6732566967888f2c12efee1146940c85c0154e60 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 11 Jul 2014 22:43:17 -0700 Subject: [PATCH] Bump the default iterations for PBKDF2. The rate at which we've increased this has not been keeping up with hardware (and software) improvements, and we're now considerably behind where we should be. The delta between our performance and an optimized implementation's performance prevents us from improving that further, but hopefully once Python 2.7.8 and 3.4+ get into more hands we can more aggressively increase this number. --- django/contrib/auth/hashers.py | 4 ++-- django/contrib/auth/tests/test_hashers.py | 6 +++--- django/utils/crypto.py | 14 ++++++++------ docs/internals/howto-release-django.txt | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) 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).