summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
Diffstat (limited to 'django')
-rw-r--r--django/conf/global_settings.py1
-rw-r--r--django/contrib/auth/hashers.py49
-rw-r--r--django/contrib/auth/tests/hashers.py16
3 files changed, 62 insertions, 4 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 42df4b601a..aa67e48bcd 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -515,6 +515,7 @@ PASSWORD_RESET_TIMEOUT_DAYS = 3
PASSWORD_HASHERS = (
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
+ 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
'django.contrib.auth.hashers.SHA1PasswordHasher',
'django.contrib.auth.hashers.MD5PasswordHasher',
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
index 480fde69ce..092cccedde 100644
--- a/django/contrib/auth/hashers.py
+++ b/django/contrib/auth/hashers.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
import base64
+import binascii
import hashlib
from django.dispatch import receiver
@@ -257,7 +258,7 @@ class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
digest = hashlib.sha1
-class BCryptPasswordHasher(BasePasswordHasher):
+class BCryptSHA256PasswordHasher(BasePasswordHasher):
"""
Secure password hashing using the bcrypt algorithm (recommended)
@@ -266,7 +267,8 @@ class BCryptPasswordHasher(BasePasswordHasher):
this library depends on native C code and might cause portability
issues.
"""
- algorithm = "bcrypt"
+ algorithm = "bcrypt_sha256"
+ digest = hashlib.sha256
library = ("py-bcrypt", "bcrypt")
rounds = 12
@@ -278,14 +280,34 @@ class BCryptPasswordHasher(BasePasswordHasher):
bcrypt = self._load_library()
# Need to reevaluate the force_bytes call once bcrypt is supported on
# Python 3
- data = bcrypt.hashpw(force_bytes(password), salt)
+
+ # Hash the password prior to using bcrypt to prevent password truncation
+ # See: https://code.djangoproject.com/ticket/20138
+ if self.digest is not None:
+ # We use binascii.hexlify here because Python3 decided that a hex encoded
+ # bytestring is somehow a unicode.
+ password = binascii.hexlify(self.digest(force_bytes(password)).digest())
+ else:
+ password = force_bytes(password)
+
+ data = bcrypt.hashpw(password, salt)
return "%s$%s" % (self.algorithm, data)
def verify(self, password, encoded):
algorithm, data = encoded.split('$', 1)
assert algorithm == self.algorithm
bcrypt = self._load_library()
- return constant_time_compare(data, bcrypt.hashpw(force_bytes(password), data))
+
+ # Hash the password prior to using bcrypt to prevent password truncation
+ # See: https://code.djangoproject.com/ticket/20138
+ if self.digest is not None:
+ # We use binascii.hexlify here because Python3 decided that a hex encoded
+ # bytestring is somehow a unicode.
+ password = binascii.hexlify(self.digest(force_bytes(password)).digest())
+ else:
+ password = force_bytes(password)
+
+ return constant_time_compare(data, bcrypt.hashpw(password, data))
def safe_summary(self, encoded):
algorithm, empty, algostr, work_factor, data = encoded.split('$', 4)
@@ -299,6 +321,25 @@ class BCryptPasswordHasher(BasePasswordHasher):
])
+class BCryptPasswordHasher(BCryptSHA256PasswordHasher):
+ """
+ Secure password hashing using the bcrypt algorithm
+
+ This is considered by many to be the most secure algorithm but you
+ must first install the py-bcrypt library. Please be warned that
+ this library depends on native C code and might cause portability
+ issues.
+
+ This hasher does not first hash the password which means it is subject to
+ the 72 character bcrypt password truncation, most use cases should prefer
+ the BCryptSha512PasswordHasher.
+
+ See: https://code.djangoproject.com/ticket/20138
+ """
+ algorithm = "bcrypt"
+ digest = None
+
+
class SHA1PasswordHasher(BasePasswordHasher):
"""
The SHA1 password hashing algorithm (not recommended)
diff --git a/django/contrib/auth/tests/hashers.py b/django/contrib/auth/tests/hashers.py
index 2b2243cb0c..9253fcbc43 100644
--- a/django/contrib/auth/tests/hashers.py
+++ b/django/contrib/auth/tests/hashers.py
@@ -93,6 +93,22 @@ class TestUtilsHashPass(unittest.TestCase):
self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
@skipUnless(bcrypt, "py-bcrypt not installed")
+ def test_bcrypt_sha256(self):
+ encoded = make_password('lètmein', hasher='bcrypt_sha256')
+ self.assertTrue(is_password_usable(encoded))
+ self.assertTrue(encoded.startswith('bcrypt_sha256$'))
+ self.assertTrue(check_password('lètmein', encoded))
+ self.assertFalse(check_password('lètmeinz', encoded))
+ self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt_sha256")
+
+ # Verify that password truncation no longer works
+ password = ('VSK0UYV6FFQVZ0KG88DYN9WADAADZO1CTSIVDJUNZSUML6IBX7LN7ZS3R5'
+ 'JGB3RGZ7VI7G7DJQ9NI8BQFSRPTG6UWTTVESA5ZPUN')
+ encoded = make_password(password, hasher='bcrypt_sha256')
+ self.assertTrue(check_password(password, encoded))
+ self.assertFalse(check_password(password[:72], encoded))
+
+ @skipUnless(bcrypt, "py-bcrypt not installed")
def test_bcrypt(self):
encoded = make_password('lètmein', hasher='bcrypt')
self.assertTrue(is_password_usable(encoded))