Fixed #26188 -- Documented how to wrap password hashers.
This commit is contained in:
parent
33a4040d07
commit
5a541e2e6c
|
@ -199,6 +199,89 @@ bcrypt rounds.
|
|||
|
||||
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
|
||||
.. _pbkdf2: https://en.wikipedia.org/wiki/PBKDF2
|
||||
.. _nist: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
|
||||
|
|
Loading…
Reference in New Issue