Fixed #18184 -- Moved algorithm identification code to hashers module
Thanks Eli Collins for the report and the patch.
This commit is contained in:
parent
eb286aa22f
commit
70a0351fef
|
@ -7,7 +7,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, get_hasher
|
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, identify_hasher
|
||||||
from django.contrib.auth.tokens import default_token_generator
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.contrib.sites.models import get_current_site
|
from django.contrib.sites.models import get_current_site
|
||||||
|
|
||||||
|
@ -25,13 +25,8 @@ class ReadOnlyPasswordHashWidget(forms.Widget):
|
||||||
|
|
||||||
final_attrs = self.build_attrs(attrs)
|
final_attrs = self.build_attrs(attrs)
|
||||||
|
|
||||||
if len(encoded) == 32 and '$' not in encoded:
|
|
||||||
algorithm = 'unsalted_md5'
|
|
||||||
else:
|
|
||||||
algorithm = encoded.split('$', 1)[0]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hasher = get_hasher(algorithm)
|
hasher = identify_hasher(encoded)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
summary = "<strong>Invalid password format or unknown hashing algorithm.</strong>"
|
summary = "<strong>Invalid password format or unknown hashing algorithm.</strong>"
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -40,12 +40,7 @@ def check_password(password, encoded, setter=None, preferred='default'):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
preferred = get_hasher(preferred)
|
preferred = get_hasher(preferred)
|
||||||
|
hasher = identify_hasher(encoded)
|
||||||
if len(encoded) == 32 and '$' not in encoded:
|
|
||||||
hasher = get_hasher('unsalted_md5')
|
|
||||||
else:
|
|
||||||
algorithm = encoded.split('$', 1)[0]
|
|
||||||
hasher = get_hasher(algorithm)
|
|
||||||
|
|
||||||
must_update = hasher.algorithm != preferred.algorithm
|
must_update = hasher.algorithm != preferred.algorithm
|
||||||
is_correct = hasher.verify(password, encoded)
|
is_correct = hasher.verify(password, encoded)
|
||||||
|
@ -120,6 +115,21 @@ def get_hasher(algorithm='default'):
|
||||||
return HASHERS[algorithm]
|
return HASHERS[algorithm]
|
||||||
|
|
||||||
|
|
||||||
|
def identify_hasher(encoded):
|
||||||
|
"""
|
||||||
|
Returns an instance of a loaded password hasher.
|
||||||
|
|
||||||
|
Identifies hasher algorithm by examining encoded hash, and calls
|
||||||
|
get_hasher() to return hasher. Raises ValueError if
|
||||||
|
algorithm cannot be identified, or if hasher is not loaded.
|
||||||
|
"""
|
||||||
|
if len(encoded) == 32 and '$' not in encoded:
|
||||||
|
algorithm = 'unsalted_md5'
|
||||||
|
else:
|
||||||
|
algorithm = encoded.split('$', 1)[0]
|
||||||
|
return get_hasher(algorithm)
|
||||||
|
|
||||||
|
|
||||||
def mask_hash(hash, show=6, char="*"):
|
def mask_hash(hash, show=6, char="*"):
|
||||||
"""
|
"""
|
||||||
Returns the given hash, with only the first ``show`` number shown. The
|
Returns the given hash, with only the first ``show`` number shown. The
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
|
from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
|
||||||
from django.contrib.auth.hashers import (is_password_usable,
|
from django.contrib.auth.hashers import (is_password_usable,
|
||||||
check_password, make_password, PBKDF2PasswordHasher, load_hashers,
|
check_password, make_password, PBKDF2PasswordHasher, load_hashers,
|
||||||
PBKDF2SHA1PasswordHasher, get_hasher, UNUSABLE_PASSWORD)
|
PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, UNUSABLE_PASSWORD)
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from django.utils.unittest import skipUnless
|
from django.utils.unittest import skipUnless
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(is_password_usable(encoded))
|
self.assertTrue(is_password_usable(encoded))
|
||||||
self.assertTrue(check_password(u'letmein', encoded))
|
self.assertTrue(check_password(u'letmein', encoded))
|
||||||
self.assertFalse(check_password('letmeinz', encoded))
|
self.assertFalse(check_password('letmeinz', encoded))
|
||||||
|
self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256")
|
||||||
|
|
||||||
def test_sha1(self):
|
def test_sha1(self):
|
||||||
encoded = make_password('letmein', 'seasalt', 'sha1')
|
encoded = make_password('letmein', 'seasalt', 'sha1')
|
||||||
|
@ -44,6 +45,7 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(is_password_usable(encoded))
|
self.assertTrue(is_password_usable(encoded))
|
||||||
self.assertTrue(check_password(u'letmein', encoded))
|
self.assertTrue(check_password(u'letmein', encoded))
|
||||||
self.assertFalse(check_password('letmeinz', encoded))
|
self.assertFalse(check_password('letmeinz', encoded))
|
||||||
|
self.assertEqual(identify_hasher(encoded).algorithm, "sha1")
|
||||||
|
|
||||||
def test_md5(self):
|
def test_md5(self):
|
||||||
encoded = make_password('letmein', 'seasalt', 'md5')
|
encoded = make_password('letmein', 'seasalt', 'md5')
|
||||||
|
@ -52,6 +54,7 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(is_password_usable(encoded))
|
self.assertTrue(is_password_usable(encoded))
|
||||||
self.assertTrue(check_password(u'letmein', encoded))
|
self.assertTrue(check_password(u'letmein', encoded))
|
||||||
self.assertFalse(check_password('letmeinz', encoded))
|
self.assertFalse(check_password('letmeinz', encoded))
|
||||||
|
self.assertEqual(identify_hasher(encoded).algorithm, "md5")
|
||||||
|
|
||||||
def test_unsalted_md5(self):
|
def test_unsalted_md5(self):
|
||||||
encoded = make_password('letmein', 'seasalt', 'unsalted_md5')
|
encoded = make_password('letmein', 'seasalt', 'unsalted_md5')
|
||||||
|
@ -59,6 +62,7 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(is_password_usable(encoded))
|
self.assertTrue(is_password_usable(encoded))
|
||||||
self.assertTrue(check_password(u'letmein', encoded))
|
self.assertTrue(check_password(u'letmein', encoded))
|
||||||
self.assertFalse(check_password('letmeinz', encoded))
|
self.assertFalse(check_password('letmeinz', encoded))
|
||||||
|
self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5")
|
||||||
|
|
||||||
@skipUnless(crypt, "no crypt module to generate password.")
|
@skipUnless(crypt, "no crypt module to generate password.")
|
||||||
def test_crypt(self):
|
def test_crypt(self):
|
||||||
|
@ -67,6 +71,7 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(is_password_usable(encoded))
|
self.assertTrue(is_password_usable(encoded))
|
||||||
self.assertTrue(check_password(u'letmein', encoded))
|
self.assertTrue(check_password(u'letmein', encoded))
|
||||||
self.assertFalse(check_password('letmeinz', encoded))
|
self.assertFalse(check_password('letmeinz', encoded))
|
||||||
|
self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
|
||||||
|
|
||||||
@skipUnless(bcrypt, "py-bcrypt not installed")
|
@skipUnless(bcrypt, "py-bcrypt not installed")
|
||||||
def test_bcrypt(self):
|
def test_bcrypt(self):
|
||||||
|
@ -75,6 +80,7 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertTrue(encoded.startswith('bcrypt$'))
|
self.assertTrue(encoded.startswith('bcrypt$'))
|
||||||
self.assertTrue(check_password(u'letmein', encoded))
|
self.assertTrue(check_password(u'letmein', encoded))
|
||||||
self.assertFalse(check_password('letmeinz', encoded))
|
self.assertFalse(check_password('letmeinz', encoded))
|
||||||
|
self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt")
|
||||||
|
|
||||||
def test_unusable(self):
|
def test_unusable(self):
|
||||||
encoded = make_password(None)
|
encoded = make_password(None)
|
||||||
|
@ -84,11 +90,13 @@ class TestUtilsHashPass(unittest.TestCase):
|
||||||
self.assertFalse(check_password('', encoded))
|
self.assertFalse(check_password('', encoded))
|
||||||
self.assertFalse(check_password(u'letmein', encoded))
|
self.assertFalse(check_password(u'letmein', encoded))
|
||||||
self.assertFalse(check_password('letmeinz', encoded))
|
self.assertFalse(check_password('letmeinz', encoded))
|
||||||
|
self.assertRaises(ValueError, identify_hasher, encoded)
|
||||||
|
|
||||||
def test_bad_algorithm(self):
|
def test_bad_algorithm(self):
|
||||||
def doit():
|
def doit():
|
||||||
make_password('letmein', hasher='lolcat')
|
make_password('letmein', hasher='lolcat')
|
||||||
self.assertRaises(ValueError, doit)
|
self.assertRaises(ValueError, doit)
|
||||||
|
self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash")
|
||||||
|
|
||||||
def test_low_level_pkbdf2(self):
|
def test_low_level_pkbdf2(self):
|
||||||
hasher = PBKDF2PasswordHasher()
|
hasher = PBKDF2PasswordHasher()
|
||||||
|
|
Loading…
Reference in New Issue