Fixed #26187 -- Removed weak password hashers from PASSWORD_HASHERS.
This commit is contained in:
parent
b14470c7b7
commit
47b5a6a43c
|
@ -502,11 +502,6 @@ PASSWORD_HASHERS = [
|
|||
'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',
|
||||
'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher',
|
||||
'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
|
||||
'django.contrib.auth.hashers.CryptPasswordHasher',
|
||||
]
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = []
|
||||
|
|
|
@ -2686,13 +2686,22 @@ Default::
|
|||
'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',
|
||||
'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher',
|
||||
'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
|
||||
'django.contrib.auth.hashers.CryptPasswordHasher',
|
||||
]
|
||||
|
||||
.. versionchanged:: 1.10
|
||||
|
||||
The following hashers were removed from the defaults::
|
||||
|
||||
'django.contrib.auth.hashers.SHA1PasswordHasher'
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher'
|
||||
'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher'
|
||||
'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher'
|
||||
'django.contrib.auth.hashers.CryptPasswordHasher'
|
||||
|
||||
Consider using a :ref:`wrapped password hasher <wrapping-password-hashers>`
|
||||
to strengthen the hashes in your database. If that's not feasible, add this
|
||||
setting to your project and add back any hashers that you need.
|
||||
|
||||
.. setting:: AUTH_PASSWORD_VALIDATORS
|
||||
|
||||
``AUTH_PASSWORD_VALIDATORS``
|
||||
|
|
|
@ -502,6 +502,50 @@ In older versions, assigning ``None`` to a non-nullable ``ForeignKey`` or
|
|||
not allow null values.')``. For consistency with other model fields which don't
|
||||
have a similar check, this check is removed.
|
||||
|
||||
Removed weak password hashers from the default ``PASSWORD_HASHERS`` setting
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
Django 0.90 stored passwords as unsalted MD5. Django 0.91 added support for
|
||||
salted SHA1 with automatic upgrade of passwords when a user logs in. Django 1.4
|
||||
added PBKDF2 as the default password hasher.
|
||||
|
||||
If you have an old Django project with MD5 or SHA1 (even salted) encoded
|
||||
passwords, be aware that these can be cracked fairly easily with today's
|
||||
hardware. To make Django users acknowledge continued use of weak hashers, the
|
||||
following hashers are removed from the default :setting:`PASSWORD_HASHERS`
|
||||
setting::
|
||||
|
||||
'django.contrib.auth.hashers.SHA1PasswordHasher'
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher'
|
||||
'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher'
|
||||
'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher'
|
||||
'django.contrib.auth.hashers.CryptPasswordHasher'
|
||||
|
||||
Consider using a :ref:`wrapped password hasher <wrapping-password-hashers>` to
|
||||
strengthen the hashes in your database. If that's not feasible, add the
|
||||
:setting:`PASSWORD_HASHERS` setting to your project and add back any hashers
|
||||
that you need.
|
||||
|
||||
You can check if your database has any of the removed hashers like this::
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
User = get_user_model()
|
||||
|
||||
# Unsalted MD5/SHA1:
|
||||
User.objects.filter(password__startswith='md5$$')
|
||||
User.objects.filter(password__startswith='sha1$$')
|
||||
# Salted MD5/SHA1:
|
||||
User.objects.filter(password__startswith='md5$').exclude(password__startswith='md5$$')
|
||||
User.objects.filter(password__startswith='sha1$').exclude(password__startswith='sha1$$')
|
||||
# Crypt hasher:
|
||||
User.objects.filter(password__startswith='crypt$$')
|
||||
|
||||
from django.db.models import CharField
|
||||
from django.db.models.functions import Length
|
||||
CharField.register_lookup(Length)
|
||||
# Unsalted MD5 passwords might not have an 'md5$$' prefix:
|
||||
User.objects.filter(password__length=32)
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
|
|
|
@ -62,15 +62,13 @@ The default for :setting:`PASSWORD_HASHERS` is::
|
|||
'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',
|
||||
'django.contrib.auth.hashers.CryptPasswordHasher',
|
||||
]
|
||||
|
||||
This means that Django will use PBKDF2_ to store all passwords, but will support
|
||||
checking passwords stored with PBKDF2SHA1, bcrypt_, SHA1_, etc. The next few
|
||||
sections describe a couple of common ways advanced users may want to modify this
|
||||
setting.
|
||||
This means that Django will use PBKDF2_ to store all passwords but will support
|
||||
checking passwords stored with PBKDF2SHA1 and bcrypt_.
|
||||
|
||||
The next few sections describe a couple of common ways advanced users may want
|
||||
to modify this setting.
|
||||
|
||||
.. _bcrypt_usage:
|
||||
|
||||
|
@ -96,13 +94,10 @@ To use Bcrypt as your default storage algorithm, do the following:
|
|||
'django.contrib.auth.hashers.BCryptPasswordHasher',
|
||||
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
||||
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
|
||||
'django.contrib.auth.hashers.SHA1PasswordHasher',
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||
'django.contrib.auth.hashers.CryptPasswordHasher',
|
||||
]
|
||||
|
||||
(You need to keep the other entries in this list, or else Django won't
|
||||
be able to upgrade passwords; see below).
|
||||
Keep and/or add any entries in this list if you need Django to :ref:`upgrade
|
||||
passwords <password-upgrades>`.
|
||||
|
||||
That's it -- now your Django install will use Bcrypt as the default storage
|
||||
algorithm.
|
||||
|
@ -168,12 +163,8 @@ default PBKDF2 algorithm:
|
|||
'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',
|
||||
'django.contrib.auth.hashers.CryptPasswordHasher',
|
||||
]
|
||||
|
||||
|
||||
That's it -- now your Django install will use more iterations when it
|
||||
stores passwords using PBKDF2.
|
||||
|
||||
|
@ -288,6 +279,37 @@ Include any other hashers that your site uses in this list.
|
|||
.. _bcrypt: https://en.wikipedia.org/wiki/Bcrypt
|
||||
.. _`bcrypt library`: https://pypi.python.org/pypi/bcrypt/
|
||||
|
||||
.. _auth-included-hashers:
|
||||
|
||||
Included hashers
|
||||
----------------
|
||||
|
||||
The full list of hashers included in Django is::
|
||||
|
||||
[
|
||||
'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',
|
||||
'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher',
|
||||
'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
|
||||
'django.contrib.auth.hashers.CryptPasswordHasher',
|
||||
]
|
||||
|
||||
The corresponding algorithm names are:
|
||||
|
||||
* ``pbkdf2_sha256``
|
||||
* ``pbkdf2_sha1``
|
||||
* ``bcrypt_sha256``
|
||||
* ``bcrypt``
|
||||
* ``sha1``
|
||||
* ``md5``
|
||||
* ``unsalted_sha1``
|
||||
* ``unsalted_md5``
|
||||
* ``crypt``
|
||||
|
||||
Manually managing a user's password
|
||||
===================================
|
||||
|
||||
|
@ -311,13 +333,10 @@ from the ``User`` model.
|
|||
Creates a hashed password in the format used by this application. It takes
|
||||
one mandatory argument: the password in plain-text. Optionally, you can
|
||||
provide a salt and a hashing algorithm to use, if you don't want to use the
|
||||
defaults (first entry of ``PASSWORD_HASHERS`` setting).
|
||||
Currently supported algorithms are: ``'pbkdf2_sha256'``, ``'pbkdf2_sha1'``,
|
||||
``'bcrypt_sha256'`` (see :ref:`bcrypt_usage`), ``'bcrypt'``, ``'sha1'``,
|
||||
``'md5'``, ``'unsalted_md5'`` (only for backward compatibility) and ``'crypt'``
|
||||
if you have the ``crypt`` library installed. If the password argument is
|
||||
``None``, an unusable password is returned (a one that will be never
|
||||
accepted by :func:`check_password`).
|
||||
defaults (first entry of ``PASSWORD_HASHERS`` setting). See
|
||||
:ref:`auth-included-hashers` for the algorithm name of each hasher. If the
|
||||
password argument is ``None``, an unusable password is returned (a one that
|
||||
will be never accepted by :func:`check_password`).
|
||||
|
||||
.. function:: is_password_usable(encoded_password)
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ class TestUtilsHashPass(SimpleTestCase):
|
|||
self.assertTrue(check_password('', blank_encoded))
|
||||
self.assertFalse(check_password(' ', blank_encoded))
|
||||
|
||||
@override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
|
||||
def test_sha1(self):
|
||||
encoded = make_password('lètmein', 'seasalt', 'sha1')
|
||||
self.assertEqual(encoded,
|
||||
|
@ -75,6 +76,7 @@ class TestUtilsHashPass(SimpleTestCase):
|
|||
self.assertTrue(check_password('', blank_encoded))
|
||||
self.assertFalse(check_password(' ', blank_encoded))
|
||||
|
||||
@override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.MD5PasswordHasher'])
|
||||
def test_md5(self):
|
||||
encoded = make_password('lètmein', 'seasalt', 'md5')
|
||||
self.assertEqual(encoded,
|
||||
|
@ -90,6 +92,7 @@ class TestUtilsHashPass(SimpleTestCase):
|
|||
self.assertTrue(check_password('', blank_encoded))
|
||||
self.assertFalse(check_password(' ', blank_encoded))
|
||||
|
||||
@override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.UnsaltedMD5PasswordHasher'])
|
||||
def test_unsalted_md5(self):
|
||||
encoded = make_password('lètmein', '', 'unsalted_md5')
|
||||
self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43')
|
||||
|
@ -108,6 +111,7 @@ class TestUtilsHashPass(SimpleTestCase):
|
|||
self.assertTrue(check_password('', blank_encoded))
|
||||
self.assertFalse(check_password(' ', blank_encoded))
|
||||
|
||||
@override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher'])
|
||||
def test_unsalted_sha1(self):
|
||||
encoded = make_password('lètmein', '', 'unsalted_sha1')
|
||||
self.assertEqual(encoded, 'sha1$$6d138ca3ae545631b3abd71a4f076ce759c5700b')
|
||||
|
@ -126,6 +130,7 @@ class TestUtilsHashPass(SimpleTestCase):
|
|||
self.assertFalse(check_password(' ', blank_encoded))
|
||||
|
||||
@skipUnless(crypt, "no crypt module to generate password.")
|
||||
@override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.CryptPasswordHasher'])
|
||||
def test_crypt(self):
|
||||
encoded = make_password('lètmei', 'ab', 'crypt')
|
||||
self.assertEqual(encoded, 'crypt$$ab1Hv2Lg7ltQo')
|
||||
|
@ -256,6 +261,13 @@ class TestUtilsHashPass(SimpleTestCase):
|
|||
'pbkdf2_sha1$30000$seasalt2$pMzU1zNPcydf6wjnJFbiVKwgULc=')
|
||||
self.assertTrue(hasher.verify('lètmein', encoded))
|
||||
|
||||
@override_settings(
|
||||
PASSWORD_HASHERS=[
|
||||
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
||||
'django.contrib.auth.hashers.SHA1PasswordHasher',
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||
],
|
||||
)
|
||||
def test_upgrade(self):
|
||||
self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
|
||||
for algo in ('sha1', 'md5'):
|
||||
|
@ -276,6 +288,13 @@ class TestUtilsHashPass(SimpleTestCase):
|
|||
self.assertFalse(check_password('WRONG', encoded, setter))
|
||||
self.assertFalse(state['upgraded'])
|
||||
|
||||
@override_settings(
|
||||
PASSWORD_HASHERS=[
|
||||
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
||||
'django.contrib.auth.hashers.SHA1PasswordHasher',
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||
],
|
||||
)
|
||||
def test_no_upgrade_on_incorrect_pass(self):
|
||||
self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
|
||||
for algo in ('sha1', 'md5'):
|
||||
|
|
Loading…
Reference in New Issue