test_hashers.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from unittest import skipUnless
  4. from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
  5. from django.contrib.auth.hashers import (is_password_usable, BasePasswordHasher,
  6. check_password, make_password, PBKDF2PasswordHasher, load_hashers, PBKDF2SHA1PasswordHasher,
  7. get_hasher, identify_hasher, UNUSABLE_PASSWORD_PREFIX, UNUSABLE_PASSWORD_SUFFIX_LENGTH)
  8. from django.test import SimpleTestCase
  9. from django.utils import six
  10. try:
  11. import crypt
  12. except ImportError:
  13. crypt = None
  14. try:
  15. import bcrypt
  16. except ImportError:
  17. bcrypt = None
  18. class PBKDF2SingleIterationHasher(PBKDF2PasswordHasher):
  19. iterations = 1
  20. class TestUtilsHashPass(SimpleTestCase):
  21. def setUp(self):
  22. load_hashers(password_hashers=default_hashers)
  23. def test_simple(self):
  24. encoded = make_password('lètmein')
  25. self.assertTrue(encoded.startswith('pbkdf2_sha256$'))
  26. self.assertTrue(is_password_usable(encoded))
  27. self.assertTrue(check_password('lètmein', encoded))
  28. self.assertFalse(check_password('lètmeinz', encoded))
  29. # Blank passwords
  30. blank_encoded = make_password('')
  31. self.assertTrue(blank_encoded.startswith('pbkdf2_sha256$'))
  32. self.assertTrue(is_password_usable(blank_encoded))
  33. self.assertTrue(check_password('', blank_encoded))
  34. self.assertFalse(check_password(' ', blank_encoded))
  35. def test_pkbdf2(self):
  36. encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256')
  37. self.assertEqual(encoded,
  38. 'pbkdf2_sha256$12000$seasalt$Ybw8zsFxqja97tY/o6G+Fy1ksY4U/Hw3DRrGED6Up4s=')
  39. self.assertTrue(is_password_usable(encoded))
  40. self.assertTrue(check_password('lètmein', encoded))
  41. self.assertFalse(check_password('lètmeinz', encoded))
  42. self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256")
  43. # Blank passwords
  44. blank_encoded = make_password('', 'seasalt', 'pbkdf2_sha256')
  45. self.assertTrue(blank_encoded.startswith('pbkdf2_sha256$'))
  46. self.assertTrue(is_password_usable(blank_encoded))
  47. self.assertTrue(check_password('', blank_encoded))
  48. self.assertFalse(check_password(' ', blank_encoded))
  49. def test_sha1(self):
  50. encoded = make_password('lètmein', 'seasalt', 'sha1')
  51. self.assertEqual(encoded,
  52. 'sha1$seasalt$cff36ea83f5706ce9aa7454e63e431fc726b2dc8')
  53. self.assertTrue(is_password_usable(encoded))
  54. self.assertTrue(check_password('lètmein', encoded))
  55. self.assertFalse(check_password('lètmeinz', encoded))
  56. self.assertEqual(identify_hasher(encoded).algorithm, "sha1")
  57. # Blank passwords
  58. blank_encoded = make_password('', 'seasalt', 'sha1')
  59. self.assertTrue(blank_encoded.startswith('sha1$'))
  60. self.assertTrue(is_password_usable(blank_encoded))
  61. self.assertTrue(check_password('', blank_encoded))
  62. self.assertFalse(check_password(' ', blank_encoded))
  63. def test_md5(self):
  64. encoded = make_password('lètmein', 'seasalt', 'md5')
  65. self.assertEqual(encoded,
  66. 'md5$seasalt$3f86d0d3d465b7b458c231bf3555c0e3')
  67. self.assertTrue(is_password_usable(encoded))
  68. self.assertTrue(check_password('lètmein', encoded))
  69. self.assertFalse(check_password('lètmeinz', encoded))
  70. self.assertEqual(identify_hasher(encoded).algorithm, "md5")
  71. # Blank passwords
  72. blank_encoded = make_password('', 'seasalt', 'md5')
  73. self.assertTrue(blank_encoded.startswith('md5$'))
  74. self.assertTrue(is_password_usable(blank_encoded))
  75. self.assertTrue(check_password('', blank_encoded))
  76. self.assertFalse(check_password(' ', blank_encoded))
  77. def test_unsalted_md5(self):
  78. encoded = make_password('lètmein', '', 'unsalted_md5')
  79. self.assertEqual(encoded, '88a434c88cca4e900f7874cd98123f43')
  80. self.assertTrue(is_password_usable(encoded))
  81. self.assertTrue(check_password('lètmein', encoded))
  82. self.assertFalse(check_password('lètmeinz', encoded))
  83. self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5")
  84. # Alternate unsalted syntax
  85. alt_encoded = "md5$$%s" % encoded
  86. self.assertTrue(is_password_usable(alt_encoded))
  87. self.assertTrue(check_password('lètmein', alt_encoded))
  88. self.assertFalse(check_password('lètmeinz', alt_encoded))
  89. # Blank passwords
  90. blank_encoded = make_password('', '', 'unsalted_md5')
  91. self.assertTrue(is_password_usable(blank_encoded))
  92. self.assertTrue(check_password('', blank_encoded))
  93. self.assertFalse(check_password(' ', blank_encoded))
  94. def test_unsalted_sha1(self):
  95. encoded = make_password('lètmein', '', 'unsalted_sha1')
  96. self.assertEqual(encoded, 'sha1$$6d138ca3ae545631b3abd71a4f076ce759c5700b')
  97. self.assertTrue(is_password_usable(encoded))
  98. self.assertTrue(check_password('lètmein', encoded))
  99. self.assertFalse(check_password('lètmeinz', encoded))
  100. self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_sha1")
  101. # Raw SHA1 isn't acceptable
  102. alt_encoded = encoded[6:]
  103. self.assertFalse(check_password('lètmein', alt_encoded))
  104. # Blank passwords
  105. blank_encoded = make_password('', '', 'unsalted_sha1')
  106. self.assertTrue(blank_encoded.startswith('sha1$'))
  107. self.assertTrue(is_password_usable(blank_encoded))
  108. self.assertTrue(check_password('', blank_encoded))
  109. self.assertFalse(check_password(' ', blank_encoded))
  110. @skipUnless(crypt, "no crypt module to generate password.")
  111. def test_crypt(self):
  112. encoded = make_password('lètmei', 'ab', 'crypt')
  113. self.assertEqual(encoded, 'crypt$$ab1Hv2Lg7ltQo')
  114. self.assertTrue(is_password_usable(encoded))
  115. self.assertTrue(check_password('lètmei', encoded))
  116. self.assertFalse(check_password('lètmeiz', encoded))
  117. self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
  118. # Blank passwords
  119. blank_encoded = make_password('', 'ab', 'crypt')
  120. self.assertTrue(blank_encoded.startswith('crypt$'))
  121. self.assertTrue(is_password_usable(blank_encoded))
  122. self.assertTrue(check_password('', blank_encoded))
  123. self.assertFalse(check_password(' ', blank_encoded))
  124. @skipUnless(bcrypt, "bcrypt not installed")
  125. def test_bcrypt_sha256(self):
  126. encoded = make_password('lètmein', hasher='bcrypt_sha256')
  127. self.assertTrue(is_password_usable(encoded))
  128. self.assertTrue(encoded.startswith('bcrypt_sha256$'))
  129. self.assertTrue(check_password('lètmein', encoded))
  130. self.assertFalse(check_password('lètmeinz', encoded))
  131. self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt_sha256")
  132. # Verify that password truncation no longer works
  133. password = ('VSK0UYV6FFQVZ0KG88DYN9WADAADZO1CTSIVDJUNZSUML6IBX7LN7ZS3R5'
  134. 'JGB3RGZ7VI7G7DJQ9NI8BQFSRPTG6UWTTVESA5ZPUN')
  135. encoded = make_password(password, hasher='bcrypt_sha256')
  136. self.assertTrue(check_password(password, encoded))
  137. self.assertFalse(check_password(password[:72], encoded))
  138. # Blank passwords
  139. blank_encoded = make_password('', hasher='bcrypt_sha256')
  140. self.assertTrue(blank_encoded.startswith('bcrypt_sha256$'))
  141. self.assertTrue(is_password_usable(blank_encoded))
  142. self.assertTrue(check_password('', blank_encoded))
  143. self.assertFalse(check_password(' ', blank_encoded))
  144. @skipUnless(bcrypt, "bcrypt not installed")
  145. def test_bcrypt(self):
  146. encoded = make_password('lètmein', hasher='bcrypt')
  147. self.assertTrue(is_password_usable(encoded))
  148. self.assertTrue(encoded.startswith('bcrypt$'))
  149. self.assertTrue(check_password('lètmein', encoded))
  150. self.assertFalse(check_password('lètmeinz', encoded))
  151. self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt")
  152. # Blank passwords
  153. blank_encoded = make_password('', hasher='bcrypt')
  154. self.assertTrue(blank_encoded.startswith('bcrypt$'))
  155. self.assertTrue(is_password_usable(blank_encoded))
  156. self.assertTrue(check_password('', blank_encoded))
  157. self.assertFalse(check_password(' ', blank_encoded))
  158. def test_unusable(self):
  159. encoded = make_password(None)
  160. self.assertEqual(len(encoded), len(UNUSABLE_PASSWORD_PREFIX) + UNUSABLE_PASSWORD_SUFFIX_LENGTH)
  161. self.assertFalse(is_password_usable(encoded))
  162. self.assertFalse(check_password(None, encoded))
  163. self.assertFalse(check_password(encoded, encoded))
  164. self.assertFalse(check_password(UNUSABLE_PASSWORD_PREFIX, encoded))
  165. self.assertFalse(check_password('', encoded))
  166. self.assertFalse(check_password('lètmein', encoded))
  167. self.assertFalse(check_password('lètmeinz', encoded))
  168. self.assertRaises(ValueError, identify_hasher, encoded)
  169. # Assert that the unusable passwords actually contain a random part.
  170. # This might fail one day due to a hash collision.
  171. self.assertNotEqual(encoded, make_password(None), "Random password collision?")
  172. def test_unspecified_password(self):
  173. """
  174. Makes sure specifying no plain password with a valid encoded password
  175. returns `False`.
  176. """
  177. self.assertFalse(check_password(None, make_password('lètmein')))
  178. def test_bad_algorithm(self):
  179. with self.assertRaises(ValueError):
  180. make_password('lètmein', hasher='lolcat')
  181. self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash")
  182. def test_bad_encoded(self):
  183. self.assertFalse(is_password_usable('lètmein_badencoded'))
  184. self.assertFalse(is_password_usable(''))
  185. def test_low_level_pkbdf2(self):
  186. hasher = PBKDF2PasswordHasher()
  187. encoded = hasher.encode('lètmein', 'seasalt2')
  188. self.assertEqual(encoded,
  189. 'pbkdf2_sha256$12000$seasalt2$hlDLKsxgkgb1aeOppkM5atCYw5rPzAjCNQZ4NYyUROw=')
  190. self.assertTrue(hasher.verify('lètmein', encoded))
  191. def test_low_level_pbkdf2_sha1(self):
  192. hasher = PBKDF2SHA1PasswordHasher()
  193. encoded = hasher.encode('lètmein', 'seasalt2')
  194. self.assertEqual(encoded,
  195. 'pbkdf2_sha1$12000$seasalt2$JeMRVfjjgtWw3/HzlnlfqBnQ6CA=')
  196. self.assertTrue(hasher.verify('lètmein', encoded))
  197. def test_upgrade(self):
  198. self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
  199. for algo in ('sha1', 'md5'):
  200. encoded = make_password('lètmein', hasher=algo)
  201. state = {'upgraded': False}
  202. def setter(password):
  203. state['upgraded'] = True
  204. self.assertTrue(check_password('lètmein', encoded, setter))
  205. self.assertTrue(state['upgraded'])
  206. def test_no_upgrade(self):
  207. encoded = make_password('lètmein')
  208. state = {'upgraded': False}
  209. def setter():
  210. state['upgraded'] = True
  211. self.assertFalse(check_password('WRONG', encoded, setter))
  212. self.assertFalse(state['upgraded'])
  213. def test_no_upgrade_on_incorrect_pass(self):
  214. self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
  215. for algo in ('sha1', 'md5'):
  216. encoded = make_password('lètmein', hasher=algo)
  217. state = {'upgraded': False}
  218. def setter():
  219. state['upgraded'] = True
  220. self.assertFalse(check_password('WRONG', encoded, setter))
  221. self.assertFalse(state['upgraded'])
  222. def test_pbkdf2_upgrade(self):
  223. self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
  224. hasher = get_hasher('default')
  225. self.assertNotEqual(hasher.iterations, 1)
  226. old_iterations = hasher.iterations
  227. try:
  228. # Generate a password with 1 iteration.
  229. hasher.iterations = 1
  230. encoded = make_password('letmein')
  231. algo, iterations, salt, hash = encoded.split('$', 3)
  232. self.assertEqual(iterations, '1')
  233. state = {'upgraded': False}
  234. def setter(password):
  235. state['upgraded'] = True
  236. # Check that no upgrade is triggerd
  237. self.assertTrue(check_password('letmein', encoded, setter))
  238. self.assertFalse(state['upgraded'])
  239. # Revert to the old iteration count and ...
  240. hasher.iterations = old_iterations
  241. # ... check if the password would get updated to the new iteration count.
  242. self.assertTrue(check_password('letmein', encoded, setter))
  243. self.assertTrue(state['upgraded'])
  244. finally:
  245. hasher.iterations = old_iterations
  246. def test_pbkdf2_upgrade_new_hasher(self):
  247. self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
  248. hasher = get_hasher('default')
  249. self.assertNotEqual(hasher.iterations, 1)
  250. state = {'upgraded': False}
  251. def setter(password):
  252. state['upgraded'] = True
  253. with self.settings(PASSWORD_HASHERS=[
  254. 'django.contrib.auth.tests.test_hashers.PBKDF2SingleIterationHasher']):
  255. encoded = make_password('letmein')
  256. algo, iterations, salt, hash = encoded.split('$', 3)
  257. self.assertEqual(iterations, '1')
  258. # Check that no upgrade is triggerd
  259. self.assertTrue(check_password('letmein', encoded, setter))
  260. self.assertFalse(state['upgraded'])
  261. # Revert to the old iteration count and check if the password would get
  262. # updated to the new iteration count.
  263. with self.settings(PASSWORD_HASHERS=[
  264. 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
  265. 'django.contrib.auth.tests.test_hashers.PBKDF2SingleIterationHasher']):
  266. self.assertTrue(check_password('letmein', encoded, setter))
  267. self.assertTrue(state['upgraded'])
  268. def test_load_library_no_algorithm(self):
  269. with self.assertRaises(ValueError) as e:
  270. BasePasswordHasher()._load_library()
  271. self.assertEqual("Hasher 'BasePasswordHasher' doesn't specify a "
  272. "library attribute", str(e.exception))
  273. def test_load_library_importerror(self):
  274. PlainHasher = type(str('PlainHasher'), (BasePasswordHasher,),
  275. {'algorithm': 'plain', 'library': 'plain'})
  276. # Python 3.3 adds quotes around module name
  277. with six.assertRaisesRegex(self, ValueError,
  278. "Couldn't load 'PlainHasher' algorithm library: No module named '?plain'?"):
  279. PlainHasher()._load_library()