[1.8.x] Fixed -- Documented how to wrap password hashers.

Backport of 5a541e2e6c from master
This commit is contained in:
Tim Graham 2016-02-10 10:20:02 -05:00
parent 061a7ff366
commit 2d321d2393
1 changed files with 83 additions and 0 deletions
docs/topics/auth

View File

@ -194,6 +194,89 @@ sure never to *remove* entries from this list. If you do, users using
unmentioned algorithms won't be able to upgrade. Passwords will be upgraded
when changing the PBKDF2 iteration count.
.. _wrapping-password-hashers:
Password upgrading without requiring a login
--------------------------------------------
If you have an existing database with an older, weak hash such as MD5 or SHA1,
you might want to upgrade those hashes yourself instead of waiting for the
upgrade to happen when a user logs in (which may never happen if a user doesn't
return to your site). In this case, you can use a "wrapped" password hasher.
For this example, we'll migrate a collection of SHA1 hashes to use
PBKDF2(SHA1(password)) and add the corresponding password hasher for checking
if a user entered the correct password on login. We assume we're using the
built-in ``User`` model and that our project has an ``accounts`` app. You can
modify the pattern to work with any algorithm or with a custom user model.
First, we'll add the custom hasher:
.. snippet::
:filename: accounts/hashers.py
from django.contrib.auth.hashers import (
PBKDF2PasswordHasher, SHA1PasswordHasher,
)
class PBKDF2WrappedSHA1PasswordHasher(PBKDF2PasswordHasher):
algorithm = 'pbkdf2_wrapped_sha1'
def encode_sha1_hash(self, sha1_hash, salt, iterations=None):
return super(PBKDF2WrappedSHA1PasswordHasher, self).encode(sha1_hash, salt, iterations)
def encode(self, password, salt, iterations=None):
_, _, sha1_hash = SHA1PasswordHasher().encode(password, salt).split('$', 2)
return self.encode_sha1_hash(sha1_hash, salt, iterations)
The data migration might look something like:
.. snippet::
:filename: accounts/migrations/0002_migrate_sha1_passwords.py
from django.db import migrations
from ..hashers import PBKDF2WrappedSHA1PasswordHasher
def forwards_func(apps, schema_editor):
User = apps.get_model('auth', 'User')
users = User.objects.filter(password__startswith='sha1$')
hasher = PBKDF2WrappedSHA1PasswordHasher()
for user in users:
algorithm, salt, sha1_hash = user.password.split('$', 2)
user.password = hasher.encode_sha1_hash(sha1_hash, salt)
user.save(update_fields=['password'])
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
# replace this with the latest migration in contrib.auth
('auth', '####_migration_name'),
]
operations = [
migrations.RunPython(forwards_func),
]
Be aware that this migration will take on the order of several minutes for
several thousand users, depending on the speed of your hardware.
Finally, we'll add a :setting:`PASSWORD_HASHERS` setting:
.. snippet::
:filename: mysite/settings.py
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'accounts.hashers.PBKDF2WrappedSHA1PasswordHasher',
]
Include any other hashers that your site uses in this list.
.. _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