470 lines
20 KiB
Plaintext
470 lines
20 KiB
Plaintext
=============================
|
|
Password management in Django
|
|
=============================
|
|
|
|
Password management is something that should generally not be reinvented
|
|
unnecessarily, and Django endeavors to provide a secure and flexible set of
|
|
tools for managing user passwords. This document describes how Django stores
|
|
passwords, how the storage hashing can be configured, and some utilities to
|
|
work with hashed passwords.
|
|
|
|
.. seealso::
|
|
|
|
Even though users may use strong passwords, attackers might be able to
|
|
eavesdrop on their connections. Use :ref:`HTTPS
|
|
<security-recommendation-ssl>` to avoid sending passwords (or any other
|
|
sensitive data) over plain HTTP connections because they will be vulnerable
|
|
to password sniffing.
|
|
|
|
.. _auth_password_storage:
|
|
|
|
How Django stores passwords
|
|
===========================
|
|
|
|
Django provides a flexible password storage system and uses PBKDF2 by default.
|
|
|
|
The :attr:`~django.contrib.auth.models.User.password` attribute of a
|
|
:class:`~django.contrib.auth.models.User` object is a string in this format::
|
|
|
|
<algorithm>$<iterations>$<salt>$<hash>
|
|
|
|
Those are the components used for storing a User's password, separated by the
|
|
dollar-sign character and consist of: the hashing algorithm, the number of
|
|
algorithm iterations (work factor), the random salt, and the resulting password
|
|
hash. The algorithm is one of a number of one-way hashing or password storage
|
|
algorithms Django can use; see below. Iterations describe the number of times
|
|
the algorithm is run over the hash. Salt is the random seed used and the hash
|
|
is the result of the one-way function.
|
|
|
|
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.
|
|
|
|
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:
|
|
|
|
Django chooses the algorithm to use 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 preferred algorithm
|
|
first in the list.
|
|
|
|
The default for :setting:`PASSWORD_HASHERS` is::
|
|
|
|
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',
|
|
'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.
|
|
|
|
.. _bcrypt_usage:
|
|
|
|
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 `bcrypt library`_. This can be done by running ``pip install
|
|
django[bcrypt]``, or by downloading the library and installing it with
|
|
``python setup.py install``.
|
|
|
|
2. Modify :setting:`PASSWORD_HASHERS` to list ``BCryptSHA256PasswordHasher``
|
|
first. That is, in your settings file, you'd put::
|
|
|
|
PASSWORD_HASHERS = [
|
|
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
|
|
'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:: Password truncation with BCryptPasswordHasher
|
|
|
|
The designers of bcrypt truncate all passwords at 72 characters which means
|
|
that ``bcrypt(password_with_100_chars) == bcrypt(password_with_100_chars[:72])``.
|
|
The original ``BCryptPasswordHasher`` does not have any special handling and
|
|
thus is also subject to this hidden password length limit.
|
|
``BCryptSHA256PasswordHasher`` fixes this by first first hashing the
|
|
password using sha256. This prevents the password truncation and so should
|
|
be preferred over the ``BCryptPasswordHasher``. The practical ramification
|
|
of this truncation is pretty marginal as the average user does not have a
|
|
password greater than 72 characters in length and even being truncated at 72
|
|
the compute powered required to brute force bcrypt in any useful amount of
|
|
time is still astronomical. Nonetheless, we recommend you use
|
|
``BCryptSHA256PasswordHasher`` anyway on the principle of "better safe than
|
|
sorry".
|
|
|
|
.. 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-password-algorithm-work-factor:
|
|
|
|
Increasing the work factor
|
|
--------------------------
|
|
|
|
The PBKDF2 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 PBKDF2 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.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.
|
|
|
|
.. _password-upgrades:
|
|
|
|
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
|
|
unmentioned algorithms won't be able to upgrade. Hashed passwords will be
|
|
updated when increasing (or decreasing) the number of PBKDF2 iterations or
|
|
bcrypt rounds.
|
|
|
|
.. versionchanged:: 1.9
|
|
|
|
Passwords updates when changing the number of bcrypt rounds was added.
|
|
|
|
.. _sha1: https://en.wikipedia.org/wiki/SHA1
|
|
.. _pbkdf2: https://en.wikipedia.org/wiki/PBKDF2
|
|
.. _nist: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
|
|
.. _bcrypt: https://en.wikipedia.org/wiki/Bcrypt
|
|
.. _`bcrypt library`: https://pypi.python.org/pypi/bcrypt/
|
|
|
|
Manually managing a user's password
|
|
===================================
|
|
|
|
.. module:: django.contrib.auth.hashers
|
|
|
|
The :mod:`django.contrib.auth.hashers` module provides a set of functions
|
|
to create and validate hashed password. You can use them independently
|
|
from the ``User`` model.
|
|
|
|
.. function:: check_password(password, encoded)
|
|
|
|
If you'd like to manually authenticate a user by comparing a plain-text
|
|
password to the hashed password in the database, use the convenience
|
|
function :func:`check_password`. It takes two arguments: the plain-text
|
|
password to check, and the full value of a user's ``password`` field in the
|
|
database to check against, and returns ``True`` if they match, ``False``
|
|
otherwise.
|
|
|
|
.. function:: make_password(password, salt=None, hasher='default')
|
|
|
|
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`).
|
|
|
|
.. function:: is_password_usable(encoded_password)
|
|
|
|
Checks if the given string is a hashed password that has a chance
|
|
of being verified against :func:`check_password`.
|
|
|
|
.. _password-validation:
|
|
|
|
Password validation
|
|
===================
|
|
|
|
.. module:: django.contrib.auth.password_validation
|
|
|
|
.. versionadded:: 1.9
|
|
|
|
Users often choose poor passwords. To help mitigate this problem, Django
|
|
offers pluggable password validation. You can configure multiple password
|
|
validators at the same time. A few validators are included in Django, but it's
|
|
simple to write your own as well.
|
|
|
|
Each password validator must provide a help text to explain the requirements to
|
|
the user, validate a given password and return an error message if it does not
|
|
meet the requirements, and optionally receive passwords that have been set.
|
|
Validators can also have optional settings to fine tune their behavior.
|
|
|
|
Validation is controlled by the :setting:`AUTH_PASSWORD_VALIDATORS` setting.
|
|
By default, validators are used in the forms to reset or change passwords.
|
|
The default for the setting is an empty list, which means no validators are
|
|
applied. In new projects created with the default :djadmin:`startproject`
|
|
template, a simple set of validators is enabled.
|
|
|
|
.. note::
|
|
|
|
Password validation can prevent the use of many types of weak passwords.
|
|
However, the fact that a password passes all the validators doesn't
|
|
guarantee that it is a strong password. There are many factors that can
|
|
weaken a password that are not detectable by even the most advanced
|
|
password validators.
|
|
|
|
Enabling password validation
|
|
----------------------------
|
|
|
|
Password validation is configured in the
|
|
:setting:`AUTH_PASSWORD_VALIDATORS` setting::
|
|
|
|
AUTH_PASSWORD_VALIDATORS = [
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
},
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
'OPTIONS': {
|
|
'min_length': 9,
|
|
}
|
|
},
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
},
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
},
|
|
]
|
|
|
|
This example enables all four included validators:
|
|
|
|
* ``UserAttributeSimilarityValidator``, which checks the similarity between
|
|
the password and a set of attributes of the user.
|
|
* ``MinimumLengthValidator``, which simply checks whether the password meets a
|
|
minimum length. This validator is configured with a custom option: it now
|
|
requires the minimum length to be nine characters, instead of the default
|
|
eight.
|
|
* ``CommonPasswordValidator``, which checks whether the password occurs in a
|
|
list of common passwords. By default, it compares to an included list of
|
|
1000 common passwords.
|
|
* ``NumericPasswordValidator``, which checks whether the password isn't
|
|
entirely numeric.
|
|
|
|
For ``UserAttributeSimilarityValidator`` and ``CommonPasswordValidator``,
|
|
we're simply using the default settings in this example.
|
|
``NumericPasswordValidator`` has no settings.
|
|
|
|
The help texts and any errors from password validators are always returned in
|
|
the order they are listed in :setting:`AUTH_PASSWORD_VALIDATORS`.
|
|
|
|
Included validators
|
|
-------------------
|
|
|
|
Django includes four validators:
|
|
|
|
.. class:: MinimumLengthValidator(min_length=8)
|
|
|
|
Validates whether the password meets a minimum length.
|
|
The minimum length can be customized with the ``min_length`` parameter.
|
|
|
|
.. class:: UserAttributeSimilarityValidator(user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7)
|
|
|
|
Validates whether the password is sufficiently different from certain
|
|
attributes of the user.
|
|
|
|
The ``user_attributes`` parameter should be an iterable of names of user
|
|
attributes to compare to. If this argument is not provided, the default
|
|
is used: ``'username', 'first_name', 'last_name', 'email'``.
|
|
Attributes that don't exist are ignored.
|
|
|
|
The maximum similarity the password can have, before it is rejected, can
|
|
be set with the ``max_similarity`` parameter, on a scale of 0 to 1.
|
|
A setting of 0 will cause all passwords to be rejected, whereas a setting
|
|
of 1 will cause it to only reject passwords that are identical to an
|
|
attribute's value.
|
|
|
|
.. class:: CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH)
|
|
|
|
Validates whether the password is not a common password. By default, this
|
|
checks against a list of 1000 common password created by
|
|
`Mark Burnett <https://xato.net/passwords/more-top-worst-passwords/>`_.
|
|
|
|
The ``password_list_path`` can be set to the path of a custom file of
|
|
common passwords. This file should contain one password per line and
|
|
may be plain text or gzipped.
|
|
|
|
.. class:: NumericPasswordValidator()
|
|
|
|
Validates whether the password is not entirely numeric.
|
|
|
|
Integrating validation
|
|
-----------------------
|
|
|
|
There are a few functions in ``django.contrib.auth.password_validation`` that
|
|
you can call from your own forms or other code to integrate password
|
|
validation. This can be useful if you use custom forms for password setting,
|
|
or if you have API calls that allow passwords to be set, for example.
|
|
|
|
.. function:: validate_password(password, user=None, password_validators=None)
|
|
|
|
Validates a password. If all validators find the password valid, returns
|
|
``None``. If one or more validators reject the password, raises a
|
|
:exc:`~django.core.exceptions.ValidationError` with all the error messages
|
|
from the validators.
|
|
|
|
The ``user`` object is optional: if it's not provided, some validators may
|
|
not be able to perform any validation and will accept any password.
|
|
|
|
.. function:: password_changed(password, user=None, password_validators=None)
|
|
|
|
Informs all validators that the password has been changed. This can be used
|
|
by validators such as one that prevents password reuse. This should be
|
|
called once the password has been successfully changed.
|
|
|
|
For subclasses of :class:`~django.contrib.auth.models.AbstractBaseUser`,
|
|
the password field will be marked as "dirty" when calling
|
|
:meth:`~django.contrib.auth.models.AbstractBaseUser.set_password` which
|
|
triggers a call to ``password_changed()`` after the user is saved.
|
|
|
|
.. function:: password_validators_help_texts(password_validators=None)
|
|
|
|
Returns a list of the help texts of all validators. These explain the
|
|
password requirements to the user.
|
|
|
|
.. function:: password_validators_help_text_html(password_validators=None)
|
|
|
|
Returns an HTML string with all help texts in an ``<ul>``. This is
|
|
helpful when adding password validation to forms, as you can pass the
|
|
output directly to the ``help_text`` parameter of a form field.
|
|
|
|
.. function:: get_password_validators(validator_config)
|
|
|
|
Returns a set of validator objects based on the ``validator_config``
|
|
parameter. By default, all functions use the validators defined in
|
|
:setting:`AUTH_PASSWORD_VALIDATORS`, but by calling this function with an
|
|
alternate set of validators and then passing the result into the
|
|
``password_validators`` parameter of the other functions, your custom set
|
|
of validators will be used instead. This is useful when you have a typical
|
|
set of validators to use for most scenarios, but also have a special
|
|
situation that requires a custom set. If you always use the same set
|
|
of validators, there is no need to use this function, as the configuration
|
|
from :setting:`AUTH_PASSWORD_VALIDATORS` is used by default.
|
|
|
|
The structure of ``validator_config`` is identical to the
|
|
structure of :setting:`AUTH_PASSWORD_VALIDATORS`. The return value of
|
|
this function can be passed into the ``password_validators`` parameter
|
|
of the functions listed above.
|
|
|
|
Note that where the password is passed to one of these functions, this should
|
|
always be the clear text password - not a hashed password.
|
|
|
|
Writing your own validator
|
|
--------------------------
|
|
|
|
If Django's built-in validators are not sufficient, you can write your own
|
|
password validators. Validators are fairly simple classes. They must implement
|
|
two methods:
|
|
|
|
* ``validate(self, password, user=None)``: validate a password. Return
|
|
``None`` if the password is valid, or raise a
|
|
:exc:`~django.core.exceptions.ValidationError` with an error message if the
|
|
password is not valid. You must be able to deal with ``user`` being
|
|
``None`` - if that means your validator can't run, simply return ``None``
|
|
for no error.
|
|
* ``get_help_text()``: provide a help text to explain the requirements to
|
|
the user.
|
|
|
|
Any items in the ``OPTIONS`` in :setting:`AUTH_PASSWORD_VALIDATORS` for your
|
|
validator will be passed to the constructor. All constructor arguments should
|
|
have a default value.
|
|
|
|
Here's a basic example of a validator, with one optional setting::
|
|
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils.translation import ugettext as _
|
|
|
|
class MinimumLengthValidator(object):
|
|
def __init__(self, min_length=8):
|
|
self.min_length = min_length
|
|
|
|
def validate(self, password, user=None):
|
|
if len(password) < self.min_length:
|
|
raise ValidationError(
|
|
_("This password must contain at least %(min_length)d characters."),
|
|
code='password_too_short',
|
|
params={'min_length': self.min_length},
|
|
)
|
|
|
|
def get_help_text(self):
|
|
return _(
|
|
"Your password must contain at least %(min_length)d characters."
|
|
% {'min_length': self.min_length}
|
|
)
|
|
|
|
You can also implement ``password_changed(password, user=None``), which will
|
|
be called after a successful password change. That can be used to prevent
|
|
password reuse, for example. However, if you decide to store a user's previous
|
|
passwords, you should never do so in clear text.
|