Loading django/conf/global_settings.py +1 −0 Original line number Diff line number Diff line Loading @@ -510,6 +510,7 @@ PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.BCryptPasswordHasher', 'django.contrib.auth.hashers.SHA1PasswordHasher', 'django.contrib.auth.hashers.MD5PasswordHasher', 'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher', 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher', 'django.contrib.auth.hashers.CryptPasswordHasher', ) Loading django/contrib/auth/hashers.py +46 −6 Original line number Diff line number Diff line Loading @@ -127,9 +127,14 @@ def identify_hasher(encoded): get_hasher() to return hasher. Raises ValueError if algorithm cannot be identified, or if hasher is not loaded. """ # Ancient versions of Django created plain MD5 passwords and accepted # MD5 passwords with an empty salt. if ((len(encoded) == 32 and '$' not in encoded) or (len(encoded) == 37 and encoded.startswith('md5$$'))): algorithm = 'unsalted_md5' # Ancient versions of Django accepted SHA1 passwords with an empty salt. elif len(encoded) == 46 and encoded.startswith('sha1$$'): algorithm = 'unsalted_sha1' else: algorithm = encoded.split('$', 1)[0] return get_hasher(algorithm) Loading Loading @@ -350,14 +355,48 @@ class MD5PasswordHasher(BasePasswordHasher): ]) class UnsaltedSHA1PasswordHasher(BasePasswordHasher): """ Very insecure algorithm that you should *never* use; stores SHA1 hashes with an empty salt. This class is implemented because Django used to accept such password hashes. Some older Django installs still have these values lingering around so we need to handle and upgrade them properly. """ algorithm = "unsalted_sha1" def salt(self): return '' def encode(self, password, salt): assert salt == '' hash = hashlib.sha1(force_bytes(password)).hexdigest() return 'sha1$$%s' % hash def verify(self, password, encoded): encoded_2 = self.encode(password, '') return constant_time_compare(encoded, encoded_2) def safe_summary(self, encoded): assert encoded.startswith('sha1$$') hash = encoded[6:] return SortedDict([ (_('algorithm'), self.algorithm), (_('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. Incredibly insecure algorithm that you should *never* use; stores unsalted MD5 hashes without the algorithm prefix, also accepts MD5 hashes with an empty salt. This class is implemented because Django used to store passwords this way. Some older Django installs still have these values lingering around so we need to handle and upgrade them properly. This class is implemented because Django used to store passwords this way and to accept such password hashes. Some older Django installs still have these values lingering around so we need to handle and upgrade them properly. """ algorithm = "unsalted_md5" Loading @@ -365,6 +404,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): return '' def encode(self, password, salt): assert salt == '' return hashlib.md5(force_bytes(password)).hexdigest() def verify(self, password, encoded): Loading django/contrib/auth/tests/hashers.py +14 −3 Original line number Diff line number Diff line Loading @@ -60,7 +60,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertEqual(identify_hasher(encoded).algorithm, "md5") def test_unsalted_md5(self): encoded = make_password('lètmein', 'seasalt', 'unsalted_md5') encoded = make_password('lètmein', '', 'unsalted_md5') self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) Loading @@ -72,6 +72,17 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(check_password('lètmein', alt_encoded)) self.assertFalse(check_password('lètmeinz', alt_encoded)) def test_unsalted_sha1(self): encoded = make_password('lètmein', '', 'unsalted_sha1') self.assertEqual(encoded, 'sha1$$6d138ca3ae545631b3abd71a4f076ce759c5700b') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_sha1") # Raw SHA1 isn't acceptable alt_encoded = encoded[6:] self.assertFalse(check_password('lètmein', alt_encoded)) @skipUnless(crypt, "no crypt module to generate password.") def test_crypt(self): encoded = make_password('lètmei', 'ab', 'crypt') Loading Loading
django/conf/global_settings.py +1 −0 Original line number Diff line number Diff line Loading @@ -510,6 +510,7 @@ PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.BCryptPasswordHasher', 'django.contrib.auth.hashers.SHA1PasswordHasher', 'django.contrib.auth.hashers.MD5PasswordHasher', 'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher', 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher', 'django.contrib.auth.hashers.CryptPasswordHasher', ) Loading
django/contrib/auth/hashers.py +46 −6 Original line number Diff line number Diff line Loading @@ -127,9 +127,14 @@ def identify_hasher(encoded): get_hasher() to return hasher. Raises ValueError if algorithm cannot be identified, or if hasher is not loaded. """ # Ancient versions of Django created plain MD5 passwords and accepted # MD5 passwords with an empty salt. if ((len(encoded) == 32 and '$' not in encoded) or (len(encoded) == 37 and encoded.startswith('md5$$'))): algorithm = 'unsalted_md5' # Ancient versions of Django accepted SHA1 passwords with an empty salt. elif len(encoded) == 46 and encoded.startswith('sha1$$'): algorithm = 'unsalted_sha1' else: algorithm = encoded.split('$', 1)[0] return get_hasher(algorithm) Loading Loading @@ -350,14 +355,48 @@ class MD5PasswordHasher(BasePasswordHasher): ]) class UnsaltedSHA1PasswordHasher(BasePasswordHasher): """ Very insecure algorithm that you should *never* use; stores SHA1 hashes with an empty salt. This class is implemented because Django used to accept such password hashes. Some older Django installs still have these values lingering around so we need to handle and upgrade them properly. """ algorithm = "unsalted_sha1" def salt(self): return '' def encode(self, password, salt): assert salt == '' hash = hashlib.sha1(force_bytes(password)).hexdigest() return 'sha1$$%s' % hash def verify(self, password, encoded): encoded_2 = self.encode(password, '') return constant_time_compare(encoded, encoded_2) def safe_summary(self, encoded): assert encoded.startswith('sha1$$') hash = encoded[6:] return SortedDict([ (_('algorithm'), self.algorithm), (_('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. Incredibly insecure algorithm that you should *never* use; stores unsalted MD5 hashes without the algorithm prefix, also accepts MD5 hashes with an empty salt. This class is implemented because Django used to store passwords this way. Some older Django installs still have these values lingering around so we need to handle and upgrade them properly. This class is implemented because Django used to store passwords this way and to accept such password hashes. Some older Django installs still have these values lingering around so we need to handle and upgrade them properly. """ algorithm = "unsalted_md5" Loading @@ -365,6 +404,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): return '' def encode(self, password, salt): assert salt == '' return hashlib.md5(force_bytes(password)).hexdigest() def verify(self, password, encoded): Loading
django/contrib/auth/tests/hashers.py +14 −3 Original line number Diff line number Diff line Loading @@ -60,7 +60,7 @@ class TestUtilsHashPass(unittest.TestCase): self.assertEqual(identify_hasher(encoded).algorithm, "md5") def test_unsalted_md5(self): encoded = make_password('lètmein', 'seasalt', 'unsalted_md5') encoded = make_password('lètmein', '', 'unsalted_md5') self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) Loading @@ -72,6 +72,17 @@ class TestUtilsHashPass(unittest.TestCase): self.assertTrue(check_password('lètmein', alt_encoded)) self.assertFalse(check_password('lètmeinz', alt_encoded)) def test_unsalted_sha1(self): encoded = make_password('lètmein', '', 'unsalted_sha1') self.assertEqual(encoded, 'sha1$$6d138ca3ae545631b3abd71a4f076ce759c5700b') self.assertTrue(is_password_usable(encoded)) self.assertTrue(check_password('lètmein', encoded)) self.assertFalse(check_password('lètmeinz', encoded)) self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_sha1") # Raw SHA1 isn't acceptable alt_encoded = encoded[6:] self.assertFalse(check_password('lètmein', alt_encoded)) @skipUnless(crypt, "no crypt module to generate password.") def test_crypt(self): encoded = make_password('lètmei', 'ab', 'crypt') Loading