mirror of
				https://github.com/django/django.git
				synced 2025-10-26 07:06:08 +00:00 
			
		
		
		
	Fixed #19509 -- Fixed crypt/bcrypt non-ascii password encoding
Also systematically added non-ascii passwords in hashers test suite. Thanks Vaal for the report.
This commit is contained in:
		| @@ -8,7 +8,7 @@ from django.conf import settings | ||||
| from django.test.signals import setting_changed | ||||
| from django.utils import importlib | ||||
| from django.utils.datastructures import SortedDict | ||||
| from django.utils.encoding import force_bytes | ||||
| from django.utils.encoding import force_bytes, force_str | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.utils.crypto import ( | ||||
|     pbkdf2, constant_time_compare, get_random_string) | ||||
| @@ -275,14 +275,16 @@ class BCryptPasswordHasher(BasePasswordHasher): | ||||
|  | ||||
|     def encode(self, password, salt): | ||||
|         bcrypt = self._load_library() | ||||
|         data = bcrypt.hashpw(password, salt) | ||||
|         # Need to reevaluate the force_bytes call once bcrypt is supported on | ||||
|         # Python 3 | ||||
|         data = bcrypt.hashpw(force_bytes(password), salt) | ||||
|         return "%s$%s" % (self.algorithm, data) | ||||
|  | ||||
|     def verify(self, password, encoded): | ||||
|         algorithm, data = encoded.split('$', 1) | ||||
|         assert algorithm == self.algorithm | ||||
|         bcrypt = self._load_library() | ||||
|         return constant_time_compare(data, bcrypt.hashpw(password, data)) | ||||
|         return constant_time_compare(data, bcrypt.hashpw(force_bytes(password), data)) | ||||
|  | ||||
|     def safe_summary(self, encoded): | ||||
|         algorithm, empty, algostr, work_factor, data = encoded.split('$', 4) | ||||
| @@ -395,7 +397,7 @@ class CryptPasswordHasher(BasePasswordHasher): | ||||
|     def encode(self, password, salt): | ||||
|         crypt = self._load_library() | ||||
|         assert len(salt) == 2 | ||||
|         data = crypt.crypt(password, salt) | ||||
|         data = crypt.crypt(force_str(password), salt) | ||||
|         # we don't need to store the salt, but Django used to do this | ||||
|         return "%s$%s$%s" % (self.algorithm, '', data) | ||||
|  | ||||
| @@ -403,7 +405,7 @@ class CryptPasswordHasher(BasePasswordHasher): | ||||
|         crypt = self._load_library() | ||||
|         algorithm, salt, data = encoded.split('$', 2) | ||||
|         assert algorithm == self.algorithm | ||||
|         return constant_time_compare(data, crypt.crypt(password, data)) | ||||
|         return constant_time_compare(data, crypt.crypt(force_str(password), data)) | ||||
|  | ||||
|     def safe_summary(self, encoded): | ||||
|         algorithm, salt, data = encoded.split('$', 2) | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from django.conf.global_settings import PASSWORD_HASHERS as default_hashers | ||||
| @@ -25,63 +26,63 @@ class TestUtilsHashPass(unittest.TestCase): | ||||
|         load_hashers(password_hashers=default_hashers) | ||||
|  | ||||
|     def test_simple(self): | ||||
|         encoded = make_password('letmein') | ||||
|         encoded = make_password('lètmein') | ||||
|         self.assertTrue(encoded.startswith('pbkdf2_sha256$')) | ||||
|         self.assertTrue(is_password_usable(encoded)) | ||||
|         self.assertTrue(check_password('letmein', encoded)) | ||||
|         self.assertFalse(check_password('letmeinz', encoded)) | ||||
|         self.assertTrue(check_password('lètmein', encoded)) | ||||
|         self.assertFalse(check_password('lètmeinz', encoded)) | ||||
|  | ||||
|     def test_pkbdf2(self): | ||||
|         encoded = make_password('letmein', 'seasalt', 'pbkdf2_sha256') | ||||
|         encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256') | ||||
|         self.assertEqual(encoded, | ||||
| 'pbkdf2_sha256$10000$seasalt$FQCNpiZpTb0zub+HBsH6TOwyRxJ19FwvjbweatNmK/Y=') | ||||
|             'pbkdf2_sha256$10000$seasalt$CWWFdHOWwPnki7HvkcqN9iA2T3KLW1cf2uZ5kvArtVY=') | ||||
|         self.assertTrue(is_password_usable(encoded)) | ||||
|         self.assertTrue(check_password('letmein', encoded)) | ||||
|         self.assertFalse(check_password('letmeinz', encoded)) | ||||
|         self.assertTrue(check_password('lètmein', encoded)) | ||||
|         self.assertFalse(check_password('lètmeinz', encoded)) | ||||
|         self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256") | ||||
|  | ||||
|     def test_sha1(self): | ||||
|         encoded = make_password('letmein', 'seasalt', 'sha1') | ||||
|         encoded = make_password('lètmein', 'seasalt', 'sha1') | ||||
|         self.assertEqual(encoded, | ||||
| 'sha1$seasalt$fec3530984afba6bade3347b7140d1a7da7da8c7') | ||||
|             'sha1$seasalt$cff36ea83f5706ce9aa7454e63e431fc726b2dc8') | ||||
|         self.assertTrue(is_password_usable(encoded)) | ||||
|         self.assertTrue(check_password('letmein', encoded)) | ||||
|         self.assertFalse(check_password('letmeinz', encoded)) | ||||
|         self.assertTrue(check_password('lètmein', encoded)) | ||||
|         self.assertFalse(check_password('lètmeinz', encoded)) | ||||
|         self.assertEqual(identify_hasher(encoded).algorithm, "sha1") | ||||
|  | ||||
|     def test_md5(self): | ||||
|         encoded = make_password('letmein', 'seasalt', 'md5') | ||||
|         encoded = make_password('lètmein', 'seasalt', 'md5') | ||||
|         self.assertEqual(encoded,  | ||||
|                          'md5$seasalt$f5531bef9f3687d0ccf0f617f0e25573') | ||||
|                          'md5$seasalt$3f86d0d3d465b7b458c231bf3555c0e3') | ||||
|         self.assertTrue(is_password_usable(encoded)) | ||||
|         self.assertTrue(check_password('letmein', encoded)) | ||||
|         self.assertFalse(check_password('letmeinz', encoded)) | ||||
|         self.assertTrue(check_password('lètmein', encoded)) | ||||
|         self.assertFalse(check_password('lètmeinz', encoded)) | ||||
|         self.assertEqual(identify_hasher(encoded).algorithm, "md5") | ||||
|  | ||||
|     def test_unsalted_md5(self): | ||||
|         encoded = make_password('letmein', 'seasalt', 'unsalted_md5') | ||||
|         self.assertEqual(encoded, '0d107d09f5bbe40cade3de5c71e9e9b7') | ||||
|         encoded = make_password('lètmein', 'seasalt', 'unsalted_md5') | ||||
|         self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43') | ||||
|         self.assertTrue(is_password_usable(encoded)) | ||||
|         self.assertTrue(check_password('letmein', encoded)) | ||||
|         self.assertFalse(check_password('letmeinz', encoded)) | ||||
|         self.assertTrue(check_password('lètmein', encoded)) | ||||
|         self.assertFalse(check_password('lètmeinz', encoded)) | ||||
|         self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5") | ||||
|  | ||||
|     @skipUnless(crypt, "no crypt module to generate password.") | ||||
|     def test_crypt(self): | ||||
|         encoded = make_password('letmein', 'ab', 'crypt') | ||||
|         self.assertEqual(encoded, 'crypt$$abN/qM.L/H8EQ') | ||||
|         encoded = make_password('lètmei', 'ab', 'crypt') | ||||
|         self.assertEqual(encoded, 'crypt$$ab1Hv2Lg7ltQo') | ||||
|         self.assertTrue(is_password_usable(encoded)) | ||||
|         self.assertTrue(check_password('letmein', encoded)) | ||||
|         self.assertFalse(check_password('letmeinz', encoded)) | ||||
|         self.assertTrue(check_password('lètmei', encoded)) | ||||
|         self.assertFalse(check_password('lètmeiz', encoded)) | ||||
|         self.assertEqual(identify_hasher(encoded).algorithm, "crypt") | ||||
|  | ||||
|     @skipUnless(bcrypt, "py-bcrypt not installed") | ||||
|     def test_bcrypt(self): | ||||
|         encoded = make_password('letmein', hasher='bcrypt') | ||||
|         encoded = make_password('lètmein', hasher='bcrypt') | ||||
|         self.assertTrue(is_password_usable(encoded)) | ||||
|         self.assertTrue(encoded.startswith('bcrypt$')) | ||||
|         self.assertTrue(check_password('letmein', encoded)) | ||||
|         self.assertFalse(check_password('letmeinz', encoded)) | ||||
|         self.assertTrue(check_password('lètmein', encoded)) | ||||
|         self.assertFalse(check_password('lètmeinz', encoded)) | ||||
|         self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt") | ||||
|  | ||||
|     def test_unusable(self): | ||||
| @@ -90,46 +91,46 @@ class TestUtilsHashPass(unittest.TestCase): | ||||
|         self.assertFalse(check_password(None, encoded)) | ||||
|         self.assertFalse(check_password(UNUSABLE_PASSWORD, encoded)) | ||||
|         self.assertFalse(check_password('', encoded)) | ||||
|         self.assertFalse(check_password('letmein', encoded)) | ||||
|         self.assertFalse(check_password('letmeinz', encoded)) | ||||
|         self.assertFalse(check_password('lètmein', encoded)) | ||||
|         self.assertFalse(check_password('lètmeinz', encoded)) | ||||
|         self.assertRaises(ValueError, identify_hasher, encoded) | ||||
|  | ||||
|     def test_bad_algorithm(self): | ||||
|         def doit(): | ||||
|             make_password('letmein', hasher='lolcat') | ||||
|             make_password('lètmein', hasher='lolcat') | ||||
|         self.assertRaises(ValueError, doit) | ||||
|         self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash") | ||||
|  | ||||
|     def test_bad_encoded(self): | ||||
|         self.assertFalse(is_password_usable('letmein_badencoded')) | ||||
|         self.assertFalse(is_password_usable('lètmein_badencoded')) | ||||
|         self.assertFalse(is_password_usable('')) | ||||
|  | ||||
|     def test_low_level_pkbdf2(self): | ||||
|         hasher = PBKDF2PasswordHasher() | ||||
|         encoded = hasher.encode('letmein', 'seasalt') | ||||
|         encoded = hasher.encode('lètmein', 'seasalt') | ||||
|         self.assertEqual(encoded, | ||||
| 'pbkdf2_sha256$10000$seasalt$FQCNpiZpTb0zub+HBsH6TOwyRxJ19FwvjbweatNmK/Y=') | ||||
|         self.assertTrue(hasher.verify('letmein', encoded)) | ||||
|             'pbkdf2_sha256$10000$seasalt$CWWFdHOWwPnki7HvkcqN9iA2T3KLW1cf2uZ5kvArtVY=') | ||||
|         self.assertTrue(hasher.verify('lètmein', encoded)) | ||||
|  | ||||
|     def test_low_level_pbkdf2_sha1(self): | ||||
|         hasher = PBKDF2SHA1PasswordHasher() | ||||
|         encoded = hasher.encode('letmein', 'seasalt') | ||||
|         encoded = hasher.encode('lètmein', 'seasalt') | ||||
|         self.assertEqual(encoded, | ||||
| 'pbkdf2_sha1$10000$seasalt$91JiNKgwADC8j2j86Ije/cc4vfQ=') | ||||
|         self.assertTrue(hasher.verify('letmein', encoded)) | ||||
|             'pbkdf2_sha1$10000$seasalt$oAfF6vgs95ncksAhGXOWf4Okq7o=') | ||||
|         self.assertTrue(hasher.verify('lètmein', encoded)) | ||||
|  | ||||
|     def test_upgrade(self): | ||||
|         self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm) | ||||
|         for algo in ('sha1', 'md5'): | ||||
|             encoded = make_password('letmein', hasher=algo) | ||||
|             encoded = make_password('lètmein', hasher=algo) | ||||
|             state = {'upgraded': False} | ||||
|             def setter(password): | ||||
|                 state['upgraded'] = True | ||||
|             self.assertTrue(check_password('letmein', encoded, setter)) | ||||
|             self.assertTrue(check_password('lètmein', encoded, setter)) | ||||
|             self.assertTrue(state['upgraded']) | ||||
|  | ||||
|     def test_no_upgrade(self): | ||||
|         encoded = make_password('letmein') | ||||
|         encoded = make_password('lètmein') | ||||
|         state = {'upgraded': False} | ||||
|         def setter(): | ||||
|             state['upgraded'] = True | ||||
| @@ -139,7 +140,7 @@ class TestUtilsHashPass(unittest.TestCase): | ||||
|     def test_no_upgrade_on_incorrect_pass(self): | ||||
|         self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm) | ||||
|         for algo in ('sha1', 'md5'): | ||||
|             encoded = make_password('letmein', hasher=algo) | ||||
|             encoded = make_password('lètmein', hasher=algo) | ||||
|             state = {'upgraded': False} | ||||
|             def setter(): | ||||
|                 state['upgraded'] = True | ||||
|   | ||||
		Reference in New Issue
	
	Block a user