Fixed #26188 -- Documented how to wrap password hashers.

This commit is contained in:
Tim Graham 2016-02-10 10:20:02 -05:00
parent 33a4040d07
commit 5a541e2e6c
1 changed files with 83 additions and 0 deletions

View File

@ -199,6 +199,89 @@ bcrypt rounds.
Passwords updates when changing the number of bcrypt rounds was added. Passwords updates when changing the number of bcrypt rounds was added.
.. _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
PDKDF2(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 .. _sha1: https://en.wikipedia.org/wiki/SHA1
.. _pbkdf2: https://en.wikipedia.org/wiki/PBKDF2 .. _pbkdf2: https://en.wikipedia.org/wiki/PBKDF2
.. _nist: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf .. _nist: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf