123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- # -*- coding: utf-8 -*-
- from __future__ import unicode_literals
- from unittest import skipUnless
- from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
- from django.contrib.auth.hashers import (is_password_usable, BasePasswordHasher,
- check_password, make_password, PBKDF2PasswordHasher, load_hashers, PBKDF2SHA1PasswordHasher,
- get_hasher, identify_hasher, UNUSABLE_PASSWORD_PREFIX, UNUSABLE_PASSWORD_SUFFIX_LENGTH)
- from django.test import SimpleTestCase
- from django.utils import six
- try:
- import crypt
- except ImportError:
- crypt = None
- try:
- import bcrypt
- except ImportError:
- bcrypt = None
- class PBKDF2SingleIterationHasher(PBKDF2PasswordHasher):
- iterations = 1
- class TestUtilsHashPass(SimpleTestCase):
- def setUp(self):
- load_hashers(password_hashers=default_hashers)
- def test_simple(self):
- encoded = make_password('lètmein')
- self.assertTrue(encoded.startswith('pbkdf2_sha256$'))
- self.assertTrue(is_password_usable(encoded))
- self.assertTrue(check_password('lètmein', encoded))
- self.assertFalse(check_password('lètmeinz', encoded))
- # Blank passwords
- blank_encoded = make_password('')
- self.assertTrue(blank_encoded.startswith('pbkdf2_sha256$'))
- self.assertTrue(is_password_usable(blank_encoded))
- self.assertTrue(check_password('', blank_encoded))
- self.assertFalse(check_password(' ', blank_encoded))
- def test_pkbdf2(self):
- encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256')
- self.assertEqual(encoded,
- 'pbkdf2_sha256$12000$seasalt$Ybw8zsFxqja97tY/o6G+Fy1ksY4U/Hw3DRrGED6Up4s=')
- 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, "pbkdf2_sha256")
- # Blank passwords
- blank_encoded = make_password('', 'seasalt', 'pbkdf2_sha256')
- self.assertTrue(blank_encoded.startswith('pbkdf2_sha256$'))
- self.assertTrue(is_password_usable(blank_encoded))
- self.assertTrue(check_password('', blank_encoded))
- self.assertFalse(check_password(' ', blank_encoded))
- def test_sha1(self):
- encoded = make_password('lètmein', 'seasalt', 'sha1')
- self.assertEqual(encoded,
- 'sha1$seasalt$cff36ea83f5706ce9aa7454e63e431fc726b2dc8')
- 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, "sha1")
- # Blank passwords
- blank_encoded = make_password('', 'seasalt', 'sha1')
- self.assertTrue(blank_encoded.startswith('sha1$'))
- self.assertTrue(is_password_usable(blank_encoded))
- self.assertTrue(check_password('', blank_encoded))
- self.assertFalse(check_password(' ', blank_encoded))
- def test_md5(self):
- encoded = make_password('lètmein', 'seasalt', 'md5')
- self.assertEqual(encoded,
- 'md5$seasalt$3f86d0d3d465b7b458c231bf3555c0e3')
- 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, "md5")
- # Blank passwords
- blank_encoded = make_password('', 'seasalt', 'md5')
- self.assertTrue(blank_encoded.startswith('md5$'))
- self.assertTrue(is_password_usable(blank_encoded))
- self.assertTrue(check_password('', blank_encoded))
- self.assertFalse(check_password(' ', blank_encoded))
- def test_unsalted_md5(self):
- encoded = make_password('lètmein', '', 'unsalted_md5')
- self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43')
- 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_md5")
- # Alternate unsalted syntax
- alt_encoded = "md5$$%s" % encoded
- self.assertTrue(is_password_usable(alt_encoded))
- self.assertTrue(check_password('lètmein', alt_encoded))
- self.assertFalse(check_password('lètmeinz', alt_encoded))
- # Blank passwords
- blank_encoded = make_password('', '', 'unsalted_md5')
- self.assertTrue(is_password_usable(blank_encoded))
- self.assertTrue(check_password('', blank_encoded))
- self.assertFalse(check_password(' ', blank_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))
- # Blank passwords
- blank_encoded = make_password('', '', 'unsalted_sha1')
- self.assertTrue(blank_encoded.startswith('sha1$'))
- self.assertTrue(is_password_usable(blank_encoded))
- self.assertTrue(check_password('', blank_encoded))
- self.assertFalse(check_password(' ', blank_encoded))
- @skipUnless(crypt, "no crypt module to generate password.")
- def test_crypt(self):
- encoded = make_password('lètmei', 'ab', 'crypt')
- self.assertEqual(encoded, 'crypt$$ab1Hv2Lg7ltQo')
- self.assertTrue(is_password_usable(encoded))
- self.assertTrue(check_password('lètmei', encoded))
- self.assertFalse(check_password('lètmeiz', encoded))
- self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
- # Blank passwords
- blank_encoded = make_password('', 'ab', 'crypt')
- self.assertTrue(blank_encoded.startswith('crypt$'))
- self.assertTrue(is_password_usable(blank_encoded))
- self.assertTrue(check_password('', blank_encoded))
- self.assertFalse(check_password(' ', blank_encoded))
- @skipUnless(bcrypt, "bcrypt not installed")
- def test_bcrypt_sha256(self):
- encoded = make_password('lètmein', hasher='bcrypt_sha256')
- self.assertTrue(is_password_usable(encoded))
- self.assertTrue(encoded.startswith('bcrypt_sha256$'))
- self.assertTrue(check_password('lètmein', encoded))
- self.assertFalse(check_password('lètmeinz', encoded))
- self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt_sha256")
- # Verify that password truncation no longer works
- password = ('VSK0UYV6FFQVZ0KG88DYN9WADAADZO1CTSIVDJUNZSUML6IBX7LN7ZS3R5'
- 'JGB3RGZ7VI7G7DJQ9NI8BQFSRPTG6UWTTVESA5ZPUN')
- encoded = make_password(password, hasher='bcrypt_sha256')
- self.assertTrue(check_password(password, encoded))
- self.assertFalse(check_password(password[:72], encoded))
- # Blank passwords
- blank_encoded = make_password('', hasher='bcrypt_sha256')
- self.assertTrue(blank_encoded.startswith('bcrypt_sha256$'))
- self.assertTrue(is_password_usable(blank_encoded))
- self.assertTrue(check_password('', blank_encoded))
- self.assertFalse(check_password(' ', blank_encoded))
- @skipUnless(bcrypt, "bcrypt not installed")
- def test_bcrypt(self):
- encoded = make_password('lètmein', hasher='bcrypt')
- self.assertTrue(is_password_usable(encoded))
- self.assertTrue(encoded.startswith('bcrypt$'))
- self.assertTrue(check_password('lètmein', encoded))
- self.assertFalse(check_password('lètmeinz', encoded))
- self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt")
- # Blank passwords
- blank_encoded = make_password('', hasher='bcrypt')
- self.assertTrue(blank_encoded.startswith('bcrypt$'))
- self.assertTrue(is_password_usable(blank_encoded))
- self.assertTrue(check_password('', blank_encoded))
- self.assertFalse(check_password(' ', blank_encoded))
- def test_unusable(self):
- encoded = make_password(None)
- self.assertEqual(len(encoded), len(UNUSABLE_PASSWORD_PREFIX) + UNUSABLE_PASSWORD_SUFFIX_LENGTH)
- self.assertFalse(is_password_usable(encoded))
- self.assertFalse(check_password(None, encoded))
- self.assertFalse(check_password(encoded, encoded))
- self.assertFalse(check_password(UNUSABLE_PASSWORD_PREFIX, encoded))
- self.assertFalse(check_password('', encoded))
- self.assertFalse(check_password('lètmein', encoded))
- self.assertFalse(check_password('lètmeinz', encoded))
- self.assertRaises(ValueError, identify_hasher, encoded)
- # Assert that the unusable passwords actually contain a random part.
- # This might fail one day due to a hash collision.
- self.assertNotEqual(encoded, make_password(None), "Random password collision?")
- def test_unspecified_password(self):
- """
- Makes sure specifying no plain password with a valid encoded password
- returns `False`.
- """
- self.assertFalse(check_password(None, make_password('lètmein')))
- def test_bad_algorithm(self):
- with self.assertRaises(ValueError):
- make_password('lètmein', hasher='lolcat')
- self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash")
- def test_bad_encoded(self):
- self.assertFalse(is_password_usable('lètmein_badencoded'))
- self.assertFalse(is_password_usable(''))
- def test_low_level_pkbdf2(self):
- hasher = PBKDF2PasswordHasher()
- encoded = hasher.encode('lètmein', 'seasalt2')
- self.assertEqual(encoded,
- 'pbkdf2_sha256$12000$seasalt2$hlDLKsxgkgb1aeOppkM5atCYw5rPzAjCNQZ4NYyUROw=')
- self.assertTrue(hasher.verify('lètmein', encoded))
- def test_low_level_pbkdf2_sha1(self):
- hasher = PBKDF2SHA1PasswordHasher()
- encoded = hasher.encode('lètmein', 'seasalt2')
- self.assertEqual(encoded,
- 'pbkdf2_sha1$12000$seasalt2$JeMRVfjjgtWw3/HzlnlfqBnQ6CA=')
- 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('lètmein', hasher=algo)
- state = {'upgraded': False}
- def setter(password):
- state['upgraded'] = True
- self.assertTrue(check_password('lètmein', encoded, setter))
- self.assertTrue(state['upgraded'])
- def test_no_upgrade(self):
- encoded = make_password('lètmein')
- state = {'upgraded': False}
- def setter():
- state['upgraded'] = True
- self.assertFalse(check_password('WRONG', encoded, setter))
- self.assertFalse(state['upgraded'])
- def test_no_upgrade_on_incorrect_pass(self):
- self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
- for algo in ('sha1', 'md5'):
- encoded = make_password('lètmein', hasher=algo)
- state = {'upgraded': False}
- def setter():
- state['upgraded'] = True
- self.assertFalse(check_password('WRONG', encoded, setter))
- self.assertFalse(state['upgraded'])
- def test_pbkdf2_upgrade(self):
- self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
- hasher = get_hasher('default')
- self.assertNotEqual(hasher.iterations, 1)
- old_iterations = hasher.iterations
- try:
- # Generate a password with 1 iteration.
- hasher.iterations = 1
- encoded = make_password('letmein')
- algo, iterations, salt, hash = encoded.split('$', 3)
- self.assertEqual(iterations, '1')
- state = {'upgraded': False}
- def setter(password):
- state['upgraded'] = True
- # Check that no upgrade is triggerd
- self.assertTrue(check_password('letmein', encoded, setter))
- self.assertFalse(state['upgraded'])
- # Revert to the old iteration count and ...
- hasher.iterations = old_iterations
- # ... check if the password would get updated to the new iteration count.
- self.assertTrue(check_password('letmein', encoded, setter))
- self.assertTrue(state['upgraded'])
- finally:
- hasher.iterations = old_iterations
- def test_pbkdf2_upgrade_new_hasher(self):
- self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
- hasher = get_hasher('default')
- self.assertNotEqual(hasher.iterations, 1)
- state = {'upgraded': False}
- def setter(password):
- state['upgraded'] = True
- with self.settings(PASSWORD_HASHERS=[
- 'django.contrib.auth.tests.test_hashers.PBKDF2SingleIterationHasher']):
- encoded = make_password('letmein')
- algo, iterations, salt, hash = encoded.split('$', 3)
- self.assertEqual(iterations, '1')
- # Check that no upgrade is triggerd
- self.assertTrue(check_password('letmein', encoded, setter))
- self.assertFalse(state['upgraded'])
- # Revert to the old iteration count and check if the password would get
- # updated to the new iteration count.
- with self.settings(PASSWORD_HASHERS=[
- 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
- 'django.contrib.auth.tests.test_hashers.PBKDF2SingleIterationHasher']):
- self.assertTrue(check_password('letmein', encoded, setter))
- self.assertTrue(state['upgraded'])
- def test_load_library_no_algorithm(self):
- with self.assertRaises(ValueError) as e:
- BasePasswordHasher()._load_library()
- self.assertEqual("Hasher 'BasePasswordHasher' doesn't specify a "
- "library attribute", str(e.exception))
- def test_load_library_importerror(self):
- PlainHasher = type(str('PlainHasher'), (BasePasswordHasher,),
- {'algorithm': 'plain', 'library': 'plain'})
- # Python 3.3 adds quotes around module name
- with six.assertRaisesRegex(self, ValueError,
- "Couldn't load 'PlainHasher' algorithm library: No module named '?plain'?"):
- PlainHasher()._load_library()
|