Skip to content

Commit

Permalink
Merge pull request #16 from diox/master
Browse files Browse the repository at this point in the history
django 1.3 compatibility with dynamic hashers from 1.4
  • Loading branch information
Fred Wenzel committed Sep 20, 2012
2 parents 3ba2b47 + 2645fb7 commit f4519bf
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 12 deletions.
16 changes: 12 additions & 4 deletions django_sha2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
VERSION = (0, 4)
__version__ = '.'.join(map(str, VERSION))

def get_dynamic_hasher_names(HMAC_KEYS):
"""
Return base dynamic hasher names for each entry in HMAC_KEYS (we need to
create one hasher class for each key). Names are sorted to make sure
the HMAC_KEYS are tested in the correct order and the first one is always
the first hasher name returned.
"""
algo_name = lambda hmac_id: 'bcrypt{0}'.format(hmac_id.replace('-', '_'))
return [algo_name(key) for key in sorted(HMAC_KEYS.keys(), reverse=True)]

def get_password_hashers(BASE_PASSWORD_HASHERS, HMAC_KEYS):
"""
Returns the names of the dynamic and regular hashers
created in our hashers file
Return the names of the dynamic and regular hashers
created in our hashers file.
"""
# Where is the bcrypt hashers file located?
hashers_base = 'django_sha2.hashers.{0}'
algo_name = lambda hmac_id: 'bcrypt{0}'.format(hmac_id.replace('-', '_'))

dynamic_hasher_names = [algo_name(key) for key in HMAC_KEYS.keys()]
dynamic_hasher_names = get_dynamic_hasher_names(HMAC_KEYS)
dynamic_hashers = [hashers_base.format(k) for k in dynamic_hasher_names]

return dynamic_hashers + list(BASE_PASSWORD_HASHERS)
5 changes: 4 additions & 1 deletion django_sha2/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from django.conf import settings
from django.contrib.auth import models as auth_models

from django_sha2 import get_dynamic_hasher_names


ALGOS = (
'bcrypt',
Expand Down Expand Up @@ -63,7 +65,8 @@ def check_password(self, raw_password):
Supports automatic upgrading to stronger hashes.
"""
hashed_with = self.password.split('$', 1)[0]
if hashed_with in ['bcrypt', 'hh']:
if hashed_with in ['bcrypt', 'hh'] or \
hashed_with in get_dynamic_hasher_names(settings.HMAC_KEYS):
matched = bcrypt_auth.check_password(self, raw_password)
else:
matched = check_password_old(self, raw_password)
Expand Down
2 changes: 1 addition & 1 deletion django_sha2/bcrypt_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def check_password(user, raw_password):
except KeyError:
log.info('Invalid shared key version "{0}"'.format(key_ver))
return False
bc_value = algo_and_hash[6:] # Yes, bcrypt <3s the leading $.
bc_value = algo_and_hash[algo_and_hash.find('$'):] # Yes, bcrypt <3s the leading $.
hmac_value = _hmac_create(raw_password, shared_key)
matched = _bcrypt_verify(hmac_value, bc_value)

Expand Down
3 changes: 2 additions & 1 deletion test/django13/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
## django-sha2 settings
PWD_ALGORITHM = 'bcrypt'
HMAC_KEYS = {
'2011-01-01': 'ThisisASharedKey',
'2010-06-01': 'OldSharedKey',
'2011-01-01': 'ThisisASharedKey', # This is the most recent key
'2011-00-00': 'ThisKeyIsOldToo',
'2010-01-01': 'EvenOlderSharedKey'
}
40 changes: 37 additions & 3 deletions test/django13/tests/test_bcrypt.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ def setUp(self):

def test_bcrypt_used(self):
"""Make sure bcrypt was used as the hash."""
eq_(User.objects.get(username='john').password[:6], 'bcrypt')
eq_(User.objects.get(username='jane').password[:6], 'bcrypt')
eq_(User.objects.get(username='jude').password[:6], 'bcrypt')
eq_(User.objects.get(username='john').password[:7], 'bcrypt$')
eq_(User.objects.get(username='jane').password[:7], 'bcrypt$')
eq_(User.objects.get(username='jude').password[:7], 'bcrypt$')

def test_bcrypt_auth(self):
"""Try authenticating."""
Expand All @@ -42,6 +42,40 @@ def test_nokey(self):
assert not authenticate(username='jude', password=u'abcéäêëôøà')
assert not authenticate(username='jude', password=u'çççbbbààà')

def test_password_from_django14(self):
"""Test that a password generated by django_sha2 with django 1.4 is
recognized and changed to a 1.3 version"""
# We can't easily call 1.4's hashers so we hardcode the passwords as
# returned with the specific salts and hmac_key in 1.4.
prefix = 'bcrypt2011_01_01$2a$12$'
suffix = '$2011-01-01'
raw_hashes = {
'john': '02CfJWdVwLK80jlRe/Xx1u8sTHAR0JUmKV9YB4BS.Os4LK6nsoLie',
'jane': '.ipDt6gRL3CPkVH7FEyR6.8YXeQFXAMyiX3mXpDh4YDBonrdofrcG',
'jude': '6Ol.vgIFxMQw0LBhCLtv7OkV.oyJjen2GVMoiNcLnbsljSfYUkQqe',
}

u = User.objects.get(username="john")
django14_style_password = "%s%s%s" % (prefix, raw_hashes['john'],
suffix)
u.password = django14_style_password
assert u.check_password('123456')
eq_(u.password[:7], 'bcrypt$')

u = User.objects.get(username="jane")
django14_style_password = "%s%s%s" % (prefix, raw_hashes['jane'],
suffix)
u.password = django14_style_password
assert u.check_password('abc')
eq_(u.password[:7], 'bcrypt$')

u = User.objects.get(username="jude")
django14_style_password = "%s%s%s" % (prefix, raw_hashes['jude'],
suffix)
u.password = django14_style_password
assert u.check_password(u'abcéäêëôøà')
eq_(u.password[:7], 'bcrypt$')

def test_hmac_autoupdate(self):
"""Auto-update HMAC key if hash in DB is outdated."""
# Get HMAC key IDs to compare
Expand Down
7 changes: 6 additions & 1 deletion test/django13/tests/test_sha2.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# -*- coding:utf-8 -*-
from django import test
from django.contrib.auth.models import get_hexdigest

from nose.tools import eq_

Expand Down Expand Up @@ -40,6 +39,12 @@ class Sha2Tests(test.TestCase):

def test_hexdigest(self):
"""Test various password hashes."""

# The following import need to stay inside the function to make sure
# monkeypatching has happened. If moved to the top the test would fail
# because the function has been imported too early before monkeypatch.
from django.contrib.auth.models import get_hexdigest

for algo, pws in self.HASHES.items():
for pw, hashed in pws.items():
eq_(get_hexdigest(algo, self.SALT, pw), hashed)
3 changes: 2 additions & 1 deletion test/django14/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
## django-sha2 settings
HMAC_KEYS = {
'2010-06-01': 'OldSharedKey',
'2011-01-01': 'ThisisASharedKey',
'2011-01-01': 'ThisisASharedKey', # This is the most recent key
'2011-00-00': 'ThisKeyIsOldToo',
'2010-01-01': 'EvenOlderSharedKey'
}

Expand Down

0 comments on commit f4519bf

Please sign in to comment.