Renovated password hashing. Many thanks to Justine Tunney for help with the initial patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17253 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
a976159db0
commit
dce820ff70
|
@ -498,6 +498,18 @@ LOGIN_REDIRECT_URL = '/accounts/profile/'
|
|||
# The number of days a password reset link is valid for
|
||||
PASSWORD_RESET_TIMEOUT_DAYS = 3
|
||||
|
||||
# the first hasher in this list is the preferred algorithm. any
|
||||
# password using different algorithms will be converted automatically
|
||||
# upon login
|
||||
PASSWORD_HASHERS = (
|
||||
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
||||
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
|
||||
'django.contrib.auth.hashers.BCryptPasswordHasher',
|
||||
'django.contrib.auth.hashers.SHA1PasswordHasher',
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||
'django.contrib.auth.hashers.CryptPasswordHasher',
|
||||
)
|
||||
|
||||
###########
|
||||
# SIGNING #
|
||||
###########
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
from django import forms
|
||||
from django.forms.util import flatatt
|
||||
from django.template import loader
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.http import int_to_base36
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.utils import UNUSABLE_PASSWORD
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, get_hasher
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.sites.models import get_current_site
|
||||
|
||||
|
@ -18,27 +19,26 @@ mask_password = lambda p: "%s%s" % (p[:UNMASKED_DIGITS_TO_SHOW], "*" * max(len(p
|
|||
|
||||
class ReadOnlyPasswordHashWidget(forms.Widget):
|
||||
def render(self, name, value, attrs):
|
||||
if not value:
|
||||
encoded = value
|
||||
|
||||
if not is_password_usable(encoded):
|
||||
return "None"
|
||||
|
||||
final_attrs = self.build_attrs(attrs)
|
||||
parts = value.split("$")
|
||||
if len(parts) != 3:
|
||||
# Legacy passwords didn't specify a hash type and were md5.
|
||||
hash_type = "md5"
|
||||
masked = mask_password(value)
|
||||
|
||||
encoded = smart_str(encoded)
|
||||
|
||||
if len(encoded) == 32 and '$' not in encoded:
|
||||
hasher = get_hasher('md5')
|
||||
else:
|
||||
hash_type = parts[0]
|
||||
masked = mask_password(parts[2])
|
||||
return mark_safe("""<div%(attrs)s>
|
||||
<strong>%(hash_type_label)s</strong>: %(hash_type)s
|
||||
<strong>%(masked_label)s</strong>: %(masked)s
|
||||
</div>""" % {
|
||||
"attrs": flatatt(final_attrs),
|
||||
"hash_type_label": _("Hash type"),
|
||||
"hash_type": hash_type,
|
||||
"masked_label": _("Masked hash"),
|
||||
"masked": masked,
|
||||
})
|
||||
algorithm = encoded.split('$', 1)[0]
|
||||
hasher = get_hasher(algorithm)
|
||||
|
||||
summary = ""
|
||||
for key, value in hasher.safe_summary(encoded).iteritems():
|
||||
summary += "<strong>%(key)s</strong>: %(value)s " % {"key": key, "value": value}
|
||||
|
||||
return mark_safe("<div%(attrs)s>%(summary)s</div>" % {"attrs": flatatt(final_attrs), "summary": summary})
|
||||
|
||||
|
||||
class ReadOnlyPasswordHashField(forms.Field):
|
||||
|
|
|
@ -9,11 +9,10 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.utils import timezone
|
||||
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.signals import user_logged_in
|
||||
# UNUSABLE_PASSWORD is still imported here for backwards compatibility
|
||||
from django.contrib.auth.utils import (get_hexdigest, make_password,
|
||||
check_password, is_password_usable, get_random_string,
|
||||
UNUSABLE_PASSWORD)
|
||||
from django.contrib.auth.hashers import (
|
||||
check_password, make_password, is_password_usable, UNUSABLE_PASSWORD)
|
||||
from django.contrib.auth.signals import user_logged_in
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
def update_last_login(sender, user, **kwargs):
|
||||
|
@ -220,27 +219,21 @@ class User(models.Model):
|
|||
return full_name.strip()
|
||||
|
||||
def set_password(self, raw_password):
|
||||
self.password = make_password('sha1', raw_password)
|
||||
self.password = make_password(raw_password)
|
||||
|
||||
def check_password(self, raw_password):
|
||||
"""
|
||||
Returns a boolean of whether the raw_password was correct. Handles
|
||||
hashing formats behind the scenes.
|
||||
"""
|
||||
# Backwards-compatibility check. Older passwords won't include the
|
||||
# algorithm or salt.
|
||||
if '$' not in self.password:
|
||||
is_correct = (self.password == get_hexdigest('md5', '', raw_password))
|
||||
if is_correct:
|
||||
# Convert the password to the new, more secure format.
|
||||
self.set_password(raw_password)
|
||||
self.save()
|
||||
return is_correct
|
||||
return check_password(raw_password, self.password)
|
||||
def setter(raw_password):
|
||||
self.set_password(raw_password)
|
||||
self.save()
|
||||
return check_password(raw_password, self.password, setter)
|
||||
|
||||
def set_unusable_password(self):
|
||||
# Sets a value that will never be a valid hash
|
||||
self.password = make_password('sha1', None)
|
||||
self.password = make_password(None)
|
||||
|
||||
def has_usable_password(self):
|
||||
return is_password_usable(self.password)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib.auth.tests.auth_backends import (BackendTest,
|
||||
RowlevelBackendTest, AnonymousUserBackendTest, NoBackendsTest,
|
||||
InActiveUserBackendTest, NoInActiveUserBackendTest)
|
||||
from django.contrib.auth.tests.basic import BasicTestCase, PasswordUtilsTestCase
|
||||
from django.contrib.auth.tests.basic import BasicTestCase
|
||||
from django.contrib.auth.tests.context_processors import AuthContextProcessorTests
|
||||
from django.contrib.auth.tests.decorators import LoginRequiredTestCase
|
||||
from django.contrib.auth.tests.forms import (UserCreationFormTest,
|
||||
|
@ -11,9 +11,11 @@ from django.contrib.auth.tests.remote_user import (RemoteUserTest,
|
|||
RemoteUserNoCreateTest, RemoteUserCustomTest)
|
||||
from django.contrib.auth.tests.management import GetDefaultUsernameTestCase
|
||||
from django.contrib.auth.tests.models import ProfileTestCase
|
||||
from django.contrib.auth.tests.hashers import TestUtilsHashPass
|
||||
from django.contrib.auth.tests.signals import SignalTestCase
|
||||
from django.contrib.auth.tests.tokens import TokenGeneratorTest
|
||||
from django.contrib.auth.tests.views import (AuthViewNamedURLTests, PasswordResetTest,
|
||||
ChangePasswordTest, LoginTest, LogoutTest, LoginURLSettings)
|
||||
from django.contrib.auth.tests.views import (AuthViewNamedURLTests,
|
||||
PasswordResetTest, ChangePasswordTest, LoginTest, LogoutTest,
|
||||
LoginURLSettings)
|
||||
|
||||
# The password for the fixture data users is 'password'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from django.test import TestCase
|
||||
from django.utils.unittest import skipUnless
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.contrib.auth import utils
|
||||
from django.core.management import call_command
|
||||
from StringIO import StringIO
|
||||
|
||||
|
@ -111,30 +110,3 @@ class BasicTestCase(TestCase):
|
|||
u = User.objects.get(username="joe+admin@somewhere.org")
|
||||
self.assertEqual(u.email, 'joe@somewhere.org')
|
||||
self.assertFalse(u.has_usable_password())
|
||||
|
||||
|
||||
class PasswordUtilsTestCase(TestCase):
|
||||
|
||||
def _test_make_password(self, algo):
|
||||
password = utils.make_password(algo, "foobar")
|
||||
self.assertTrue(utils.is_password_usable(password))
|
||||
self.assertTrue(utils.check_password("foobar", password))
|
||||
|
||||
def test_make_unusable(self):
|
||||
"Check that you can create an unusable password."
|
||||
password = utils.make_password("any", None)
|
||||
self.assertFalse(utils.is_password_usable(password))
|
||||
self.assertFalse(utils.check_password("foobar", password))
|
||||
|
||||
def test_make_password_sha1(self):
|
||||
"Check creating passwords with SHA1 algorithm."
|
||||
self._test_make_password("sha1")
|
||||
|
||||
def test_make_password_md5(self):
|
||||
"Check creating passwords with MD5 algorithm."
|
||||
self._test_make_password("md5")
|
||||
|
||||
@skipUnless(crypt_module, "no crypt module to generate password.")
|
||||
def test_make_password_crypt(self):
|
||||
"Check creating passwords with CRYPT algorithm."
|
||||
self._test_make_password("crypt")
|
||||
|
|
|
@ -2,10 +2,18 @@
|
|||
Django's standard crypto functions and utilities.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import struct
|
||||
import hashlib
|
||||
import binascii
|
||||
import operator
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
trans_5c = "".join([chr(x ^ 0x5C) for x in xrange(256)])
|
||||
trans_36 = "".join([chr(x ^ 0x36) for x in xrange(256)])
|
||||
|
||||
|
||||
def salted_hmac(key_salt, value, secret=None):
|
||||
"""
|
||||
Returns the HMAC-SHA1 of 'value', using a key generated from key_salt and a
|
||||
|
@ -27,6 +35,23 @@ def salted_hmac(key_salt, value, secret=None):
|
|||
# However, we need to ensure that we *always* do this.
|
||||
return hmac.new(key, msg=value, digestmod=hashlib.sha1)
|
||||
|
||||
|
||||
def get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
|
||||
"""
|
||||
Returns a random string of length characters from the set of a-z, A-Z, 0-9
|
||||
for use as a salt.
|
||||
|
||||
The default length of 12 with the a-z, A-Z, 0-9 character set returns
|
||||
a 71-bit salt. log_2((26+26+10)^12) =~ 71 bits
|
||||
"""
|
||||
import random
|
||||
try:
|
||||
random = random.SystemRandom()
|
||||
except NotImplementedError:
|
||||
pass
|
||||
return ''.join([random.choice(allowed_chars) for i in range(length)])
|
||||
|
||||
|
||||
def constant_time_compare(val1, val2):
|
||||
"""
|
||||
Returns True if the two strings are equal, False otherwise.
|
||||
|
@ -39,3 +64,72 @@ def constant_time_compare(val1, val2):
|
|||
for x, y in zip(val1, val2):
|
||||
result |= ord(x) ^ ord(y)
|
||||
return result == 0
|
||||
|
||||
|
||||
def bin_to_long(x):
|
||||
"""
|
||||
Convert a binary string into a long integer
|
||||
|
||||
This is a clever optimization for fast xor vector math
|
||||
"""
|
||||
return long(x.encode('hex'), 16)
|
||||
|
||||
|
||||
def long_to_bin(x):
|
||||
"""
|
||||
Convert a long integer into a binary string
|
||||
"""
|
||||
hex = "%x" % (x)
|
||||
if len(hex) % 2 == 1:
|
||||
hex = '0' + hex
|
||||
return binascii.unhexlify(hex)
|
||||
|
||||
|
||||
def fast_hmac(key, msg, digest):
|
||||
"""
|
||||
A trimmed down version of Python's HMAC implementation
|
||||
"""
|
||||
dig1, dig2 = digest(), digest()
|
||||
if len(key) > dig1.block_size:
|
||||
key = digest(key).digest()
|
||||
key += chr(0) * (dig1.block_size - len(key))
|
||||
dig1.update(key.translate(trans_36))
|
||||
dig1.update(msg)
|
||||
dig2.update(key.translate(trans_5c))
|
||||
dig2.update(dig1.digest())
|
||||
return dig2
|
||||
|
||||
|
||||
def pbkdf2(password, salt, iterations, dklen=0, digest=None):
|
||||
"""
|
||||
Implements PBKDF2 as defined in RFC 2898, section 5.2
|
||||
|
||||
HMAC+SHA256 is used as the default pseudo random function.
|
||||
|
||||
Right now 10,000 iterations is the recommended default which takes
|
||||
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.
|
||||
"""
|
||||
assert iterations > 0
|
||||
if not digest:
|
||||
digest = hashlib.sha256
|
||||
hlen = digest().digest_size
|
||||
if not dklen:
|
||||
dklen = hlen
|
||||
if dklen > (2 ** 32 - 1) * hlen:
|
||||
raise OverflowError('dklen too big')
|
||||
l = -(-dklen // hlen)
|
||||
r = dklen - (l - 1) * hlen
|
||||
|
||||
def F(i):
|
||||
def U():
|
||||
u = salt + struct.pack('>I', i)
|
||||
for j in xrange(int(iterations)):
|
||||
u = fast_hmac(password, u, digest).digest()
|
||||
yield bin_to_long(u)
|
||||
return long_to_bin(reduce(operator.xor, U()))
|
||||
|
||||
T = [F(x) for x in range(1, l + 1)]
|
||||
return ''.join(T[:-1]) + T[-1][:r]
|
||||
|
|
|
@ -90,6 +90,22 @@ allows you to fix a very common performance problem in which your code ends up
|
|||
doing O(n) database queries (or worse) if objects on your primary ``QuerySet``
|
||||
each have many related objects that you also need.
|
||||
|
||||
Improved password hashing
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Django's auth system (``django.contrib.auth``) stores passwords using a one-way
|
||||
algorithm. Django 1.3 uses the SHA1_ algorithm, but increasing processor speeds
|
||||
and theoretical attacks have revealed that SHA1 isn't as secure as we'd like.
|
||||
Thus, Django 1.4 introduces a new password storage system: by default Django now
|
||||
uses the PBKDF2_ algorithm (as recommended by NIST_). You can also easily choose
|
||||
a different algorithm (including the popular bcrypt_ algorithm). For more
|
||||
details, see :ref:`auth_password_storage`.
|
||||
|
||||
.. _sha1: http://en.wikipedia.org/wiki/SHA1
|
||||
.. _pbkdf2: http://en.wikipedia.org/wiki/PBKDF2
|
||||
.. _nist: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
|
||||
.. _bcrypt: http://en.wikipedia.org/wiki/Bcrypt
|
||||
|
||||
HTML5 Doctype
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -371,35 +371,162 @@ Don't set the :attr:`~django.contrib.auth.models.User.password` attribute
|
|||
directly unless you know what you're doing. This is explained in the next
|
||||
section.
|
||||
|
||||
Passwords
|
||||
---------
|
||||
.. _auth_password_storage:
|
||||
|
||||
How Django stores passwords
|
||||
---------------------------
|
||||
|
||||
.. versionadded:: 1.4
|
||||
Django 1.4 introduces a new flexible password storage system and uses
|
||||
PBKDF2 by default. Previous versions of Django used SHA1, and other
|
||||
algorithms couldn't be chosen.
|
||||
|
||||
The :attr:`~django.contrib.auth.models.User.password` attribute of a
|
||||
:class:`~django.contrib.auth.models.User` object is a string in this format::
|
||||
|
||||
hashtype$salt$hash
|
||||
algorithm$hash
|
||||
|
||||
That's hashtype, salt and hash, separated by the dollar-sign character.
|
||||
That's a storage algorithm, and hash, separated by the dollar-sign
|
||||
character. The algorithm is one of a number of one way hashing or password
|
||||
storage algorithms Django can use; see below. The hash is the result of the one-
|
||||
way function.
|
||||
|
||||
Hashtype is either ``sha1`` (default), ``md5`` or ``crypt`` -- the algorithm
|
||||
used to perform a one-way hash of the password. Salt is a random string used
|
||||
to salt the raw password to create the hash. Note that the ``crypt`` method is
|
||||
only supported on platforms that have the standard Python ``crypt`` module
|
||||
available.
|
||||
By default, Django uses the PBKDF2_ algorithm with a SHA256 hash, a
|
||||
password stretching mechanism recommended by NIST_. This should be
|
||||
sufficient for most users: it's quite secure, requiring massive
|
||||
amounts of computing time to break.
|
||||
|
||||
For example::
|
||||
However, depending on your requirements, you may choose a different
|
||||
algorithm, or even use a custom algorithm to match your specific
|
||||
security situation. Again, most users shouldn't need to do this -- if
|
||||
you're not sure, you probably don't. If you do, please read on:
|
||||
|
||||
sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4
|
||||
Django chooses the an algorithm by consulting the :setting:`PASSWORD_HASHERS`
|
||||
setting. This is a list of hashing algorithm classes that this Django
|
||||
installation supports. The first entry in this list (that is,
|
||||
``settings.PASSWORD_HASHERS[0]``) will be used to store passwords, and all the
|
||||
other entries are valid hashers that can be used to check existing passwords.
|
||||
This means that if you want to use a different algorithm, you'll need to modify
|
||||
:setting:`PASSWORD_HASHERS` to list your prefered algorithm first in the list.
|
||||
|
||||
The :meth:`~django.contrib.auth.models.User.set_password` and
|
||||
:meth:`~django.contrib.auth.models.User.check_password` functions handle the
|
||||
setting and checking of these values behind the scenes.
|
||||
The default for :setting:`PASSWORD_HASHERS` is::
|
||||
|
||||
Previous Django versions, such as 0.90, used simple MD5 hashes without password
|
||||
salts. For backwards compatibility, those are still supported; they'll be
|
||||
converted automatically to the new style the first time
|
||||
:meth:`~django.contrib.auth.models.User.check_password()` works correctly for
|
||||
a given user.
|
||||
PASSWORD_HASHERS = (
|
||||
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
||||
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
|
||||
'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.
|
||||
|
||||
Using bcrypt with Django
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Bcrypt_ is a popular password storage algorithm that's specifically designed
|
||||
for long-term password storage. It's not the default used by Django since it
|
||||
requires the use of third-party libraries, but since many people may want to
|
||||
use it Django supports bcrypt with minimal effort.
|
||||
|
||||
To use Bcrypt as your default storage algorithm, do the following:
|
||||
|
||||
1. Install the `py-bcrypt`_ library (probably by running ``pip install py-bcrypt``,
|
||||
``easy_install py-bcrypt``, or downloading the library and installing
|
||||
it with ``python setup.py install``).
|
||||
|
||||
2. Modify :setting:`PASSWORD_HASHERS` to list ``BCryptPasswordHasher``
|
||||
first. That is, in your settings file, you'd put::
|
||||
|
||||
PASSWORD_HASHERS = (
|
||||
'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).
|
||||
|
||||
That's it -- now your Django install will use Bcrypt as the default storage
|
||||
algorithm.
|
||||
|
||||
.. admonition:: Other bcrypt implementations
|
||||
|
||||
There are several other implementations that allow bcrypt to be
|
||||
used with Django. Django's bcrypt support is NOT directly
|
||||
compatible with these. To upgrade, you will need to modify the
|
||||
hashes in your database to be in the form `bcrypt$(raw bcrypt
|
||||
output)`. For example:
|
||||
`bcrypt$$2a$12$NT0I31Sa7ihGEWpka9ASYrEFkhuTNeBQ2xfZskIiiJeyFXhRgS.Sy`.
|
||||
|
||||
Increasing the work factor
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The PDKDF2 and bcrypt algorithms use a number of iterations or rounds of
|
||||
hashing. This deliberately slows down attackers, making attacks against hashed
|
||||
passwords harder. However, as computing power increases, the number of
|
||||
iterations needs to be increased. We've chosen a reasonable default (and will
|
||||
increase it with each release of Django), but you may wish to tune it up or
|
||||
down, depending on your security needs and available processing power. To do so,
|
||||
you'll subclass the appropriate algorithm and override the ``iterations``
|
||||
parameters. For example, to increase the number of iterations used by the
|
||||
default PDKDF2 algorithm:
|
||||
|
||||
1. Create a subclass of ``django.contrib.auth.hashers.PBKDF2PasswordHasher``::
|
||||
|
||||
from django.contrib.auth.hashers import PBKDF2PasswordHasher
|
||||
|
||||
class MyPBKDF2PasswordHasher(PBKDF2PasswordHasher):
|
||||
"""
|
||||
A subclass of PBKDF2PasswordHasher that uses 100 times more iterations.
|
||||
"""
|
||||
iterations = PBKDF2PasswordHasher.iterations * 100
|
||||
|
||||
Save this somewhere in your project. For example, you might put this in
|
||||
a file like ``myproject/hashers.py``.
|
||||
|
||||
2. Add your new hasher as the first entry in :setting:`PASSWORD_HASHERS`::
|
||||
|
||||
PASSWORD_HASHERS = (
|
||||
'myproject.hashers.MyPBKDF2PasswordHasher',
|
||||
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
||||
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
|
||||
'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.
|
||||
|
||||
Password upgrading
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When users log in, if their passwords are stored with anything other than
|
||||
the preferred algorithm, Django will automatically upgrade the algorithm
|
||||
to the preferred one. This means that old installs of Django will get
|
||||
automatically more secure as users log in, and it also means that you
|
||||
can switch to new (and better) storage algorithms as they get invented.
|
||||
|
||||
However, Django can only upgrade passwords that use algorithms mentioned in
|
||||
:setting:`PASSWORD_HASHERS`, so as you upgrade to new systems you should make
|
||||
sure never to *remove* entries from this list. If you do, users using un-
|
||||
mentioned algorithms won't be able to upgrade.
|
||||
|
||||
.. _sha1: http://en.wikipedia.org/wiki/SHA1
|
||||
.. _pbkdf2: http://en.wikipedia.org/wiki/PBKDF2
|
||||
.. _nist: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
|
||||
.. _bcrypt: http://en.wikipedia.org/wiki/Bcrypt
|
||||
.. _py-bcrypt: http://pypi.python.org/pypi/py-bcrypt/
|
||||
|
||||
Anonymous users
|
||||
---------------
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
Tests for django.utils.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .dateformat import DateFormatTests
|
||||
|
@ -24,4 +23,5 @@ from .baseconv import TestBaseConv
|
|||
from .jslex import JsTokensTest, JsToCForGettextTest
|
||||
from .ipv6 import TestUtilsIPv6
|
||||
from .timezone import TimezoneTests
|
||||
from .crypto import TestUtilsCryptoPBKDF2
|
||||
from .archive import TestZip, TestTar, TestGzipTar, TestBzip2Tar
|
||||
|
|
Loading…
Reference in New Issue