Skip to content

Instantly share code, notes, and snippets.

@mtigas
Created December 15, 2010 01:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mtigas/741458 to your computer and use it in GitHub Desktop.
Save mtigas/741458 to your computer and use it in GitHub Desktop.
Monkey patch to add key strengthening to django.contrib.auth user passwords.
"""
Adds key strengthening (via sha384 + large number of hash iterations) to the
default Django password implementation.
The number of iterations is stored along with the hash so that in the future,
the number of rounds can be increased for new passwords in the same system.
Standard Django hashes look like:
algo$salt$hash
Hashes using this file's method will look like:
algo$salt$rounds$hash
Where `rounds` is an integer for the number of key strengthening hash
iterations to perform. `check_password` handles both cases by checking for
the extra `$` character.
"""
from hashlib import sha384
def hard_sha384(s, salt, rounds=50000):
"""
50000 rounds approximately costs 0.1-0.8 seconds on circa 2010 hardware.
sha384 used so that algo+salt+size+hash fits into 128 characters (Django
length for User.password field.)
"""
h = sha384("%s%s"%(s,salt))
r = 0
while r < rounds:
h = sha384("%s%s"%(
h.digest(),
salt
))
r = r+1
return h.hexdigest()
# ===============================================================
# Imported so we may monkey patch it.
from django.contrib.auth import models as auth_models
from django.utils.encoding import smart_str
from hashlib import md5 as md5_constructor, sha1 as sha_constructor
def get_hexdigest(algorithm, salt, raw_password, rounds=50000):
""" Adds support for our 'hard-sha384' method to get_hexdigest in auth.models """
raw_password, salt = smart_str(raw_password), smart_str(salt)
if algorithm == 'crypt':
try:
import crypt
except ImportError:
raise ValueError('"crypt" password algorithm not supported in this environment')
return crypt.crypt(raw_password, salt)
if algorithm == 'md5':
return md5_constructor(salt + raw_password).hexdigest()
elif algorithm == 'sha1':
return sha_constructor(salt + raw_password).hexdigest()
elif algorithm == "hard-sha384":
return hard_sha384(raw_password, salt, rounds)
raise ValueError("Got unknown password algorithm type in password.")
auth_models.get_hexdigest = get_hexdigest
def check_password(raw_password, enc_password):
"""
Returns a boolean of whether the raw_password was correct. Handles
encryption formats behind the scenes.
"""
algo, salt, hsh = enc_password.split('$', 2)
rounds = 1
# New detection for the `rounds` parameter.
if "$" in hsh:
rounds, hsh = hsh.split('$', 1)
rounds = int(rounds)
return hsh == get_hexdigest(algo, salt, raw_password, rounds)
auth_models.check_password = check_password
def _user_set_password(self, raw_password, rounds=None):
if type(rounds) != int:
from django.conf import settings
rounds = getattr(settings, 'PASSWORD_KEYSTRENGTH_ROUNDS', 50000)
if raw_password is None:
self.set_unusable_password()
else:
# By default use the new, key-strengthened `hard-sha384` method.
import random
algo = 'hard-sha384'
salt = get_hexdigest(algo, str(random.random()), str(random.random()), 1)[:5]
hsh = get_hexdigest(algo, salt, raw_password)
self.password = '%s$%s$%d$%s' % (algo, salt, rounds, hsh)
auth_models.User.set_password = _user_set_password
#...
#
# The monkey-patching import needs to go after INSTALLED_APPS.
# Putting this at the end of the file and remembering to keep
# it at the end of the file works fine.
PASSWORD_KEYSTRENGTH_ROUNDS = 50000
from wherever import key_strength_patch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment