summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2016-02-10 10:20:02 -0500
committerTim Graham <timograham@gmail.com>2016-02-23 09:34:04 -0500
commit2d321d2393ebf5a8d69604d857fd7df77887ccf7 (patch)
tree5fec11cece8530228350f9650fc57a64c60d6eca
parent061a7ff3661bcad4632f1418017c5a9a24a1dc6c (diff)
[1.8.x] Fixed #26188 -- Documented how to wrap password hashers.
Backport of 5a541e2e6cb01e254f20c302093a24d7dc9af8ce from master
-rw-r--r--docs/topics/auth/passwords.txt83
1 files changed, 83 insertions, 0 deletions
diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt
index 1c9cef8b0e..29da3ae1d1 100644
--- a/docs/topics/auth/passwords.txt
+++ b/docs/topics/auth/passwords.txt
@@ -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