Fixes #17777 and makes tests run again.

Adds a salted MD5 hasher for backwards compatibility.
Thanks gunnar@g10f.de for the report.

Also fixes a bug preventing the hasher tests from being run during
contrib tests.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@17604 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Paul McMillan 2012-02-29 20:12:16 +00:00
parent ae640e5ea0
commit 413e37481d
3 changed files with 44 additions and 8 deletions

View File

@ -507,6 +507,7 @@ PASSWORD_HASHERS = (
'django.contrib.auth.hashers.BCryptPasswordHasher',
'django.contrib.auth.hashers.SHA1PasswordHasher',
'django.contrib.auth.hashers.MD5PasswordHasher',
'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
'django.contrib.auth.hashers.CryptPasswordHasher',
)

View File

@ -36,7 +36,7 @@ def check_password(password, encoded, setter=None, preferred='default'):
encoded = smart_str(encoded)
if len(encoded) == 32 and '$' not in encoded:
hasher = get_hasher('md5')
hasher = get_hasher('unsalted_md5')
else:
algorithm = encoded.split('$', 1)[0]
hasher = get_hasher(algorithm)
@ -69,11 +69,13 @@ def make_password(password, salt=None, hasher='default'):
return hasher.encode(password, salt)
def load_hashers():
def load_hashers(password_hashers=None):
global HASHERS
global PREFERRED_HASHER
hashers = []
for backend in settings.PASSWORD_HASHERS:
if not password_hashers:
password_hashers = settings.PASSWORD_HASHERS
for backend in password_hashers:
try:
mod_path, cls_name = backend.rsplit('.', 1)
mod = importlib.import_module(mod_path)
@ -300,6 +302,34 @@ class SHA1PasswordHasher(BasePasswordHasher):
class MD5PasswordHasher(BasePasswordHasher):
"""
The Salted MD5 password hashing algorithm (not recommended)
"""
algorithm = "md5"
def encode(self, password, salt):
assert password
assert salt and '$' not in salt
hash = hashlib.md5(salt + password).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash)
def verify(self, password, encoded):
algorithm, salt, hash = encoded.split('$', 2)
assert algorithm == self.algorithm
encoded_2 = self.encode(password, salt)
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
algorithm, salt, hash = encoded.split('$', 2)
assert algorithm == self.algorithm
return SortedDict([
(_('algorithm'), algorithm),
(_('salt'), mask_hash(salt, show=2)),
(_('hash'), mask_hash(hash)),
])
class UnsaltedMD5PasswordHasher(BasePasswordHasher):
"""
I am an incredibly insecure algorithm you should *never* use;
stores unsalted MD5 hashes without the algorithm prefix.
@ -308,7 +338,7 @@ class MD5PasswordHasher(BasePasswordHasher):
this way. Some older Django installs still have these values
lingering around so we need to handle and upgrade them properly.
"""
algorithm = "md5"
algorithm = "unsalted_md5"
def salt(self):
return ''

View File

@ -20,7 +20,7 @@ except ImportError:
class TestUtilsHashPass(unittest.TestCase):
def setUp(self):
load_hashers()
load_hashers(password_hashers=default_hashers)
def test_simple(self):
encoded = make_password('letmein')
@ -47,6 +47,14 @@ class TestUtilsHashPass(unittest.TestCase):
def test_md5(self):
encoded = make_password('letmein', 'seasalt', 'md5')
self.assertEqual(encoded,
'md5$seasalt$f5531bef9f3687d0ccf0f617f0e25573')
self.assertTrue(is_password_usable(encoded))
self.assertTrue(check_password(u'letmein', encoded))
self.assertFalse(check_password('letmeinz', encoded))
def test_unsalted_md5(self):
encoded = make_password('letmein', 'seasalt', 'unsalted_md5')
self.assertEqual(encoded, '0d107d09f5bbe40cade3de5c71e9e9b7')
self.assertTrue(is_password_usable(encoded))
self.assertTrue(check_password(u'letmein', encoded))
@ -123,6 +131,3 @@ class TestUtilsHashPass(unittest.TestCase):
state['upgraded'] = True
self.assertFalse(check_password('WRONG', encoded, setter))
self.assertFalse(state['upgraded'])
TestUtilsHashPass = override_settings(PASSWORD_HASHERS=default_hashers)(TestUtilsHashPass)