From 6bd3896fcb5c626a5ef613895d52c69130156d3a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 2 Feb 2013 11:57:25 +0100 Subject: [PATCH] [1.4.x] Fixed #18144 -- Added backwards compatibility with old unsalted MD5 passwords Thanks apreobrazhensky at gmail.com for the report. Backport of 63d6a50dd from master. --- django/contrib/auth/hashers.py | 5 ++++- django/contrib/auth/tests/hashers.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 58246852a8..1a93e8945b 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -35,7 +35,8 @@ def check_password(password, encoded, setter=None, preferred='default'): password = smart_str(password) encoded = smart_str(encoded) - if len(encoded) == 32 and '$' not in encoded: + if ((len(encoded) == 32 and '$' not in encoded) or + (len(encoded) == 37 and encoded.startswith('md5$$'))): hasher = get_hasher('unsalted_md5') else: algorithm = encoded.split('$', 1)[0] @@ -347,6 +348,8 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): return hashlib.md5(password).hexdigest() def verify(self, password, encoded): + if len(encoded) == 37 and encoded.startswith('md5$$'): + encoded = encoded[5:] encoded_2 = self.encode(password, '') return constant_time_compare(encoded, encoded_2) diff --git a/django/contrib/auth/tests/hashers.py b/django/contrib/auth/tests/hashers.py index 865085a194..6203e9a899 100644 --- a/django/contrib/auth/tests/hashers.py +++ b/django/contrib/auth/tests/hashers.py @@ -59,6 +59,11 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password(u'letmein', encoded)) self.assertFalse(check_password('letmeinz', encoded)) + # Alternate unsalted syntax + alt_encoded = "md5$$%s" % encoded + self.assertTrue(is_password_usable(alt_encoded)) + self.assertTrue(check_password(u'letmein', alt_encoded)) + self.assertFalse(check_password('letmeinz', alt_encoded)) @skipUnless(crypt, "no crypt module to generate password.") def test_crypt(self):