test_crypto_scrypt.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. """tests for passlib.utils.scrypt"""
  2. #=============================================================================
  3. # imports
  4. #=============================================================================
  5. # core
  6. from binascii import hexlify
  7. import hashlib
  8. import logging; log = logging.getLogger(__name__)
  9. import struct
  10. import warnings
  11. warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*")
  12. # site
  13. # pkg
  14. from passlib import exc
  15. from passlib.utils import getrandbytes
  16. from passlib.utils.compat import PYPY, u, bascii_to_str
  17. from passlib.utils.decor import classproperty
  18. from passlib.tests.utils import TestCase, skipUnless, TEST_MODE, hb
  19. # subject
  20. from passlib.crypto import scrypt as scrypt_mod
  21. # local
  22. __all__ = [
  23. "ScryptEngineTest",
  24. "BuiltinScryptTest",
  25. "FastScryptTest",
  26. ]
  27. #=============================================================================
  28. # support functions
  29. #=============================================================================
  30. def hexstr(data):
  31. """return bytes as hex str"""
  32. return bascii_to_str(hexlify(data))
  33. def unpack_uint32_list(data, check_count=None):
  34. """unpack bytes as list of uint32 values"""
  35. count = len(data) // 4
  36. assert check_count is None or check_count == count
  37. return struct.unpack("<%dI" % count, data)
  38. def seed_bytes(seed, count):
  39. """
  40. generate random reference bytes from specified seed.
  41. used to generate some predictable test vectors.
  42. """
  43. if hasattr(seed, "encode"):
  44. seed = seed.encode("ascii")
  45. buf = b''
  46. i = 0
  47. while len(buf) < count:
  48. buf += hashlib.sha256(seed + struct.pack("<I", i)).digest()
  49. i += 1
  50. return buf[:count]
  51. #=============================================================================
  52. # test builtin engine's internals
  53. #=============================================================================
  54. class ScryptEngineTest(TestCase):
  55. descriptionPrefix = "passlib.crypto.scrypt._builtin"
  56. def test_smix(self):
  57. """smix()"""
  58. from passlib.crypto.scrypt._builtin import ScryptEngine
  59. rng = self.getRandom()
  60. #-----------------------------------------------------------------------
  61. # test vector from (expired) scrypt rfc draft
  62. # (https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 9)
  63. #-----------------------------------------------------------------------
  64. input = hb("""
  65. f7 ce 0b 65 3d 2d 72 a4 10 8c f5 ab e9 12 ff dd
  66. 77 76 16 db bb 27 a7 0e 82 04 f3 ae 2d 0f 6f ad
  67. 89 f6 8f 48 11 d1 e8 7b cc 3b d7 40 0a 9f fd 29
  68. 09 4f 01 84 63 95 74 f3 9a e5 a1 31 52 17 bc d7
  69. 89 49 91 44 72 13 bb 22 6c 25 b5 4d a8 63 70 fb
  70. cd 98 43 80 37 46 66 bb 8f fc b5 bf 40 c2 54 b0
  71. 67 d2 7c 51 ce 4a d5 fe d8 29 c9 0b 50 5a 57 1b
  72. 7f 4d 1c ad 6a 52 3c da 77 0e 67 bc ea af 7e 89
  73. """)
  74. output = hb("""
  75. 79 cc c1 93 62 9d eb ca 04 7f 0b 70 60 4b f6 b6
  76. 2c e3 dd 4a 96 26 e3 55 fa fc 61 98 e6 ea 2b 46
  77. d5 84 13 67 3b 99 b0 29 d6 65 c3 57 60 1f b4 26
  78. a0 b2 f4 bb a2 00 ee 9f 0a 43 d1 9b 57 1a 9c 71
  79. ef 11 42 e6 5d 5a 26 6f dd ca 83 2c e5 9f aa 7c
  80. ac 0b 9c f1 be 2b ff ca 30 0d 01 ee 38 76 19 c4
  81. ae 12 fd 44 38 f2 03 a0 e4 e1 c4 7e c3 14 86 1f
  82. 4e 90 87 cb 33 39 6a 68 73 e8 f9 d2 53 9a 4b 8e
  83. """)
  84. # NOTE: p value should be ignored, so testing w/ random inputs.
  85. engine = ScryptEngine(n=16, r=1, p=rng.randint(1, 1023))
  86. self.assertEqual(engine.smix(input), output)
  87. def test_bmix(self):
  88. """bmix()"""
  89. from passlib.crypto.scrypt._builtin import ScryptEngine
  90. rng = self.getRandom()
  91. # NOTE: bmix() call signature currently takes in list of 32*r uint32 elements,
  92. # and writes to target buffer of same size.
  93. def check_bmix(r, input, output):
  94. """helper to check bmix() output against reference"""
  95. # NOTE: * n & p values should be ignored, so testing w/ rng inputs.
  96. # * target buffer contents should be ignored, so testing w/ random inputs.
  97. engine = ScryptEngine(r=r, n=1 << rng.randint(1, 32), p=rng.randint(1, 1023))
  98. target = [rng.randint(0, 1 << 32) for _ in range((2 * r) * 16)]
  99. engine.bmix(input, target)
  100. self.assertEqual(target, list(output))
  101. # ScryptEngine special-cases bmix() for r=1.
  102. # this removes the special case patching, so we also test original bmix function.
  103. if r == 1:
  104. del engine.bmix
  105. target = [rng.randint(0, 1 << 32) for _ in range((2 * r) * 16)]
  106. engine.bmix(input, target)
  107. self.assertEqual(target, list(output))
  108. #-----------------------------------------------------------------------
  109. # test vector from (expired) scrypt rfc draft
  110. # (https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 8)
  111. #-----------------------------------------------------------------------
  112. # NOTE: this pair corresponds to the first input & output pair
  113. # from the test vector in test_smix(), above.
  114. # NOTE: original reference lists input & output as two separate 64 byte blocks.
  115. # current internal representation used by bmix() uses single 2*r*16 array of uint32,
  116. # combining all the B blocks into a single flat array.
  117. input = unpack_uint32_list(hb("""
  118. f7 ce 0b 65 3d 2d 72 a4 10 8c f5 ab e9 12 ff dd
  119. 77 76 16 db bb 27 a7 0e 82 04 f3 ae 2d 0f 6f ad
  120. 89 f6 8f 48 11 d1 e8 7b cc 3b d7 40 0a 9f fd 29
  121. 09 4f 01 84 63 95 74 f3 9a e5 a1 31 52 17 bc d7
  122. 89 49 91 44 72 13 bb 22 6c 25 b5 4d a8 63 70 fb
  123. cd 98 43 80 37 46 66 bb 8f fc b5 bf 40 c2 54 b0
  124. 67 d2 7c 51 ce 4a d5 fe d8 29 c9 0b 50 5a 57 1b
  125. 7f 4d 1c ad 6a 52 3c da 77 0e 67 bc ea af 7e 89
  126. """), 32)
  127. output = unpack_uint32_list(hb("""
  128. a4 1f 85 9c 66 08 cc 99 3b 81 ca cb 02 0c ef 05
  129. 04 4b 21 81 a2 fd 33 7d fd 7b 1c 63 96 68 2f 29
  130. b4 39 31 68 e3 c9 e6 bc fe 6b c5 b7 a0 6d 96 ba
  131. e4 24 cc 10 2c 91 74 5c 24 ad 67 3d c7 61 8f 81
  132. 20 ed c9 75 32 38 81 a8 05 40 f6 4c 16 2d cd 3c
  133. 21 07 7c fe 5f 8d 5f e2 b1 a4 16 8f 95 36 78 b7
  134. 7d 3b 3d 80 3b 60 e4 ab 92 09 96 e5 9b 4d 53 b6
  135. 5d 2a 22 58 77 d5 ed f5 84 2c b9 f1 4e ef e4 25
  136. """), 32)
  137. # check_bmix(1, input, output)
  138. #-----------------------------------------------------------------------
  139. # custom test vector for r=2
  140. # used to check for bmix() breakage while optimizing implementation.
  141. #-----------------------------------------------------------------------
  142. r = 2
  143. input = unpack_uint32_list(seed_bytes("bmix with r=2", 128 * r))
  144. output = unpack_uint32_list(hb("""
  145. ba240854954f4585f3d0573321f10beee96f12acdc1feb498131e40512934fd7
  146. 43e8139c17d0743c89d09ac8c3582c273c60ab85db63e410d049a9e17a42c6a1
  147. 6c7831b11bf370266afdaff997ae1286920dea1dedf0f4a1795ba710ba9017f1
  148. a374400766f13ebd8969362de2d153965e9941bdde0768fa5b53e8522f116ce0
  149. d14774afb88f46cd919cba4bc64af7fca0ecb8732d1fc2191e0d7d1b6475cb2e
  150. e3db789ee478d056c4eb6c6e28b99043602dbb8dfb60c6e048bf90719da8d57d
  151. 3c42250e40ab79a1ada6aae9299b9790f767f54f388d024a1465b30cbbe9eb89
  152. 002d4f5c215c4259fac4d083bac5fb0b47463747d568f40bb7fa87c42f0a1dc1
  153. """), 32 * r)
  154. check_bmix(r, input, output)
  155. #-----------------------------------------------------------------------
  156. # custom test vector for r=3
  157. # used to check for bmix() breakage while optimizing implementation.
  158. #-----------------------------------------------------------------------
  159. r = 3
  160. input = unpack_uint32_list(seed_bytes("bmix with r=3", 128 * r))
  161. output = unpack_uint32_list(hb("""
  162. 11ddd8cf60c61f59a6e5b128239bdc77b464101312c88bd1ccf6be6e75461b29
  163. 7370d4770c904d0b09c402573cf409bf2db47b91ba87d5a3de469df8fb7a003c
  164. 95a66af96dbdd88beddc8df51a2f72a6f588d67e7926e9c2b676c875da13161e
  165. b6262adac39e6b3003e9a6fbc8c1a6ecf1e227c03bc0af3e5f8736c339b14f84
  166. c7ae5b89f5e16d0faf8983551165f4bb712d97e4f81426e6b78eb63892d3ff54
  167. 80bf406c98e479496d0f76d23d728e67d2a3d2cdbc4a932be6db36dc37c60209
  168. a5ca76ca2d2979f995f73fe8182eefa1ce0ba0d4fc27d5b827cb8e67edd6552f
  169. 00a5b3ab6b371bd985a158e728011314eb77f32ade619b3162d7b5078a19886c
  170. 06f12bc8ae8afa46489e5b0239954d5216967c928982984101e4a88bae1f60ae
  171. 3f8a456e169a8a1c7450e7955b8a13a202382ae19d41ce8ef8b6a15eeef569a7
  172. 20f54c48e44cb5543dda032c1a50d5ddf2919030624978704eb8db0290052a1f
  173. 5d88989b0ef931b6befcc09e9d5162320e71e80b89862de7e2f0b6c67229b93f
  174. """), 32 * r)
  175. check_bmix(r, input, output)
  176. #-----------------------------------------------------------------------
  177. # custom test vector for r=4
  178. # used to check for bmix() breakage while optimizing implementation.
  179. #-----------------------------------------------------------------------
  180. r = 4
  181. input = unpack_uint32_list(seed_bytes("bmix with r=4", 128 * r))
  182. output = unpack_uint32_list(hb("""
  183. 803fcf7362702f30ef43250f20bc6b1b8925bf5c4a0f5a14bbfd90edce545997
  184. 3047bd81655f72588ca93f5c2f4128adaea805e0705a35e14417101fdb1c498c
  185. 33bec6f4e5950d66098da8469f3fe633f9a17617c0ea21275185697c0e4608f7
  186. e6b38b7ec71704a810424637e2c296ca30d9cbf8172a71a266e0393deccf98eb
  187. abc430d5f144eb0805308c38522f2973b7b6a48498851e4c762874497da76b88
  188. b769b471fbfc144c0e8e859b2b3f5a11f51604d268c8fd28db55dff79832741a
  189. 1ac0dfdaff10f0ada0d93d3b1f13062e4107c640c51df05f4110bdda15f51b53
  190. 3a75bfe56489a6d8463440c78fb8c0794135e38591bdc5fa6cec96a124178a4a
  191. d1a976e985bfe13d2b4af51bd0fc36dd4cfc3af08efe033b2323a235205dc43d
  192. e57778a492153f9527338b3f6f5493a03d8015cd69737ee5096ad4cbe660b10f
  193. b75b1595ddc96e3748f5c9f61fba1ef1f0c51b6ceef8bbfcc34b46088652e6f7
  194. edab61521cbad6e69b77be30c9c97ea04a4af359dafc205c7878cc9a6c5d122f
  195. 8d77f3cbe65ab14c3c491ef94ecb3f5d2c2dd13027ea4c3606262bb3c9ce46e7
  196. dc424729dc75f6e8f06096c0ad8ad4d549c42f0cad9b33cb95d10fb3cadba27c
  197. 5f4bf0c1ac677c23ba23b64f56afc3546e62d96f96b58d7afc5029f8168cbab4
  198. 533fd29fc83c8d2a32b81923992e4938281334e0c3694f0ee56f8ff7df7dc4ae
  199. """), 32 * r)
  200. check_bmix(r, input, output)
  201. def test_salsa(self):
  202. """salsa20()"""
  203. from passlib.crypto.scrypt._builtin import salsa20
  204. # NOTE: salsa2() currently operates on lists of 16 uint32 elements,
  205. # which is what unpack_uint32_list(hb(() is for...
  206. #-----------------------------------------------------------------------
  207. # test vector from (expired) scrypt rfc draft
  208. # (https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 7)
  209. #-----------------------------------------------------------------------
  210. # NOTE: this pair corresponds to the first input & output pair
  211. # from the test vector in test_bmix(), above.
  212. input = unpack_uint32_list(hb("""
  213. 7e 87 9a 21 4f 3e c9 86 7c a9 40 e6 41 71 8f 26
  214. ba ee 55 5b 8c 61 c1 b5 0d f8 46 11 6d cd 3b 1d
  215. ee 24 f3 19 df 9b 3d 85 14 12 1e 4b 5a c5 aa 32
  216. 76 02 1d 29 09 c7 48 29 ed eb c6 8d b8 b8 c2 5e
  217. """))
  218. output = unpack_uint32_list(hb("""
  219. a4 1f 85 9c 66 08 cc 99 3b 81 ca cb 02 0c ef 05
  220. 04 4b 21 81 a2 fd 33 7d fd 7b 1c 63 96 68 2f 29
  221. b4 39 31 68 e3 c9 e6 bc fe 6b c5 b7 a0 6d 96 ba
  222. e4 24 cc 10 2c 91 74 5c 24 ad 67 3d c7 61 8f 81
  223. """))
  224. self.assertEqual(salsa20(input), output)
  225. #-----------------------------------------------------------------------
  226. # custom test vector,
  227. # used to check for salsa20() breakage while optimizing _gen_files output.
  228. #-----------------------------------------------------------------------
  229. input = list(range(16))
  230. output = unpack_uint32_list(hb("""
  231. f518dd4fb98883e0a87954c05cab867083bb8808552810752285a05822f56c16
  232. 9d4a2a0fd2142523d758c60b36411b682d53860514b871d27659042a5afa475d
  233. """))
  234. self.assertEqual(salsa20(input), output)
  235. #=============================================================================
  236. # eof
  237. #=============================================================================
  238. #=============================================================================
  239. # test scrypt
  240. #=============================================================================
  241. class _CommonScryptTest(TestCase):
  242. """
  243. base class for testing various scrypt backends against same set of reference vectors.
  244. """
  245. #=============================================================================
  246. # class attrs
  247. #=============================================================================
  248. @classproperty
  249. def descriptionPrefix(cls):
  250. return "passlib.utils.scrypt.scrypt() <%s backend>" % cls.backend
  251. backend = None
  252. #=============================================================================
  253. # setup
  254. #=============================================================================
  255. def setUp(self):
  256. assert self.backend
  257. scrypt_mod._set_backend(self.backend)
  258. super(_CommonScryptTest, self).setUp()
  259. #=============================================================================
  260. # reference vectors
  261. #=============================================================================
  262. reference_vectors = [
  263. # entry format: (secret, salt, n, r, p, keylen, result)
  264. #------------------------------------------------------------------------
  265. # test vectors from scrypt whitepaper --
  266. # http://www.tarsnap.com/scrypt/scrypt.pdf, appendix b
  267. #
  268. # also present in (expired) scrypt rfc draft --
  269. # https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 11
  270. #------------------------------------------------------------------------
  271. ("", "", 16, 1, 1, 64, hb("""
  272. 77 d6 57 62 38 65 7b 20 3b 19 ca 42 c1 8a 04 97
  273. f1 6b 48 44 e3 07 4a e8 df df fa 3f ed e2 14 42
  274. fc d0 06 9d ed 09 48 f8 32 6a 75 3a 0f c8 1f 17
  275. e8 d3 e0 fb 2e 0d 36 28 cf 35 e2 0c 38 d1 89 06
  276. """)),
  277. ("password", "NaCl", 1024, 8, 16, 64, hb("""
  278. fd ba be 1c 9d 34 72 00 78 56 e7 19 0d 01 e9 fe
  279. 7c 6a d7 cb c8 23 78 30 e7 73 76 63 4b 37 31 62
  280. 2e af 30 d9 2e 22 a3 88 6f f1 09 27 9d 98 30 da
  281. c7 27 af b9 4a 83 ee 6d 83 60 cb df a2 cc 06 40
  282. """)),
  283. # NOTE: the following are skipped for all backends unless TEST_MODE="full"
  284. ("pleaseletmein", "SodiumChloride", 16384, 8, 1, 64, hb("""
  285. 70 23 bd cb 3a fd 73 48 46 1c 06 cd 81 fd 38 eb
  286. fd a8 fb ba 90 4f 8e 3e a9 b5 43 f6 54 5d a1 f2
  287. d5 43 29 55 61 3f 0f cf 62 d4 97 05 24 2a 9a f9
  288. e6 1e 85 dc 0d 65 1e 40 df cf 01 7b 45 57 58 87
  289. """)),
  290. # NOTE: the following are always skipped for the builtin backend,
  291. # (just takes too long to be worth it)
  292. ("pleaseletmein", "SodiumChloride", 1048576, 8, 1, 64, hb("""
  293. 21 01 cb 9b 6a 51 1a ae ad db be 09 cf 70 f8 81
  294. ec 56 8d 57 4a 2f fd 4d ab e5 ee 98 20 ad aa 47
  295. 8e 56 fd 8f 4b a5 d0 9f fa 1c 6d 92 7c 40 f4 c3
  296. 37 30 40 49 e8 a9 52 fb cb f4 5c 6f a7 7a 41 a4
  297. """)),
  298. ]
  299. def test_reference_vectors(self):
  300. """reference vectors"""
  301. for secret, salt, n, r, p, keylen, result in self.reference_vectors:
  302. if n >= 1024 and TEST_MODE(max="default"):
  303. # skip large values unless we're running full test suite
  304. continue
  305. if n > 16384 and self.backend == "builtin":
  306. # skip largest vector for builtin, takes WAAY too long
  307. # (46s under pypy, ~5m under cpython)
  308. continue
  309. log.debug("scrypt reference vector: %r %r n=%r r=%r p=%r", secret, salt, n, r, p)
  310. self.assertEqual(scrypt_mod.scrypt(secret, salt, n, r, p, keylen), result)
  311. #=============================================================================
  312. # fuzz testing
  313. #=============================================================================
  314. _already_tested_others = None
  315. def test_other_backends(self):
  316. """compare output to other backends"""
  317. # only run once, since test is symetric.
  318. # maybe this means it should go somewhere else?
  319. if self._already_tested_others:
  320. raise self.skipTest("already run under %r backend test" % self._already_tested_others)
  321. self._already_tested_others = self.backend
  322. rng = self.getRandom()
  323. # get available backends
  324. orig = scrypt_mod.backend
  325. available = set(name for name in scrypt_mod.backend_values
  326. if scrypt_mod._has_backend(name))
  327. scrypt_mod._set_backend(orig)
  328. available.discard(self.backend)
  329. if not available:
  330. raise self.skipTest("no other backends found")
  331. warnings.filterwarnings("ignore", "(?i)using builtin scrypt backend",
  332. category=exc.PasslibSecurityWarning)
  333. # generate some random options, and cross-check output
  334. for _ in range(10):
  335. # NOTE: keeping values low due to builtin test
  336. secret = getrandbytes(rng, rng.randint(0, 64))
  337. salt = getrandbytes(rng, rng.randint(0, 64))
  338. n = 1 << rng.randint(1, 10)
  339. r = rng.randint(1, 8)
  340. p = rng.randint(1, 3)
  341. ks = rng.randint(1, 64)
  342. previous = None
  343. backends = set()
  344. for name in available:
  345. scrypt_mod._set_backend(name)
  346. self.assertNotIn(scrypt_mod._scrypt, backends)
  347. backends.add(scrypt_mod._scrypt)
  348. result = hexstr(scrypt_mod.scrypt(secret, salt, n, r, p, ks))
  349. self.assertEqual(len(result), 2*ks)
  350. if previous is not None:
  351. self.assertEqual(result, previous,
  352. msg="%r output differs from others %r: %r" %
  353. (name, available, [secret, salt, n, r, p, ks]))
  354. #=============================================================================
  355. # test input types
  356. #=============================================================================
  357. def test_backend(self):
  358. """backend management"""
  359. # clobber backend
  360. scrypt_mod.backend = None
  361. scrypt_mod._scrypt = None
  362. self.assertRaises(TypeError, scrypt_mod.scrypt, 's', 's', 2, 2, 2, 16)
  363. # reload backend
  364. scrypt_mod._set_backend(self.backend)
  365. self.assertEqual(scrypt_mod.backend, self.backend)
  366. scrypt_mod.scrypt('s', 's', 2, 2, 2, 16)
  367. # throw error for unknown backend
  368. self.assertRaises(ValueError, scrypt_mod._set_backend, 'xxx')
  369. self.assertEqual(scrypt_mod.backend, self.backend)
  370. def test_secret_param(self):
  371. """'secret' parameter"""
  372. def run_scrypt(secret):
  373. return hexstr(scrypt_mod.scrypt(secret, "salt", 2, 2, 2, 16))
  374. # unicode
  375. TEXT = u("abc\u00defg")
  376. self.assertEqual(run_scrypt(TEXT), '05717106997bfe0da42cf4779a2f8bd8')
  377. # utf8 bytes
  378. TEXT_UTF8 = b'abc\xc3\x9efg'
  379. self.assertEqual(run_scrypt(TEXT_UTF8), '05717106997bfe0da42cf4779a2f8bd8')
  380. # latin1 bytes
  381. TEXT_LATIN1 = b'abc\xdefg'
  382. self.assertEqual(run_scrypt(TEXT_LATIN1), '770825d10eeaaeaf98e8a3c40f9f441d')
  383. # accept empty string
  384. self.assertEqual(run_scrypt(""), 'ca1399e5fae5d3b9578dcd2b1faff6e2')
  385. # reject other types
  386. self.assertRaises(TypeError, run_scrypt, None)
  387. self.assertRaises(TypeError, run_scrypt, 1)
  388. def test_salt_param(self):
  389. """'salt' parameter"""
  390. def run_scrypt(salt):
  391. return hexstr(scrypt_mod.scrypt("secret", salt, 2, 2, 2, 16))
  392. # unicode
  393. TEXT = u("abc\u00defg")
  394. self.assertEqual(run_scrypt(TEXT), 'a748ec0f4613929e9e5f03d1ab741d88')
  395. # utf8 bytes
  396. TEXT_UTF8 = b'abc\xc3\x9efg'
  397. self.assertEqual(run_scrypt(TEXT_UTF8), 'a748ec0f4613929e9e5f03d1ab741d88')
  398. # latin1 bytes
  399. TEXT_LATIN1 = b'abc\xdefg'
  400. self.assertEqual(run_scrypt(TEXT_LATIN1), '91d056fb76fb6e9a7d1cdfffc0a16cd1')
  401. # reject other types
  402. self.assertRaises(TypeError, run_scrypt, None)
  403. self.assertRaises(TypeError, run_scrypt, 1)
  404. def test_n_param(self):
  405. """'n' (rounds) parameter"""
  406. def run_scrypt(n):
  407. return hexstr(scrypt_mod.scrypt("secret", "salt", n, 2, 2, 16))
  408. # must be > 1, and a power of 2
  409. self.assertRaises(ValueError, run_scrypt, -1)
  410. self.assertRaises(ValueError, run_scrypt, 0)
  411. self.assertRaises(ValueError, run_scrypt, 1)
  412. self.assertEqual(run_scrypt(2), 'dacf2bca255e2870e6636fa8c8957a66')
  413. self.assertRaises(ValueError, run_scrypt, 3)
  414. self.assertRaises(ValueError, run_scrypt, 15)
  415. self.assertEqual(run_scrypt(16), '0272b8fc72bc54b1159340ed99425233')
  416. def test_r_param(self):
  417. """'r' (block size) parameter"""
  418. def run_scrypt(r, n=2, p=2):
  419. return hexstr(scrypt_mod.scrypt("secret", "salt", n, r, p, 16))
  420. # must be > 1
  421. self.assertRaises(ValueError, run_scrypt, -1)
  422. self.assertRaises(ValueError, run_scrypt, 0)
  423. self.assertEqual(run_scrypt(1), '3d630447d9f065363b8a79b0b3670251')
  424. self.assertEqual(run_scrypt(2), 'dacf2bca255e2870e6636fa8c8957a66')
  425. self.assertEqual(run_scrypt(5), '114f05e985a903c27237b5578e763736')
  426. # reject r*p >= 2**30
  427. self.assertRaises(ValueError, run_scrypt, (1<<30), p=1)
  428. self.assertRaises(ValueError, run_scrypt, (1<<30) / 2, p=2)
  429. def test_p_param(self):
  430. """'p' (parallelism) parameter"""
  431. def run_scrypt(p, n=2, r=2):
  432. return hexstr(scrypt_mod.scrypt("secret", "salt", n, r, p, 16))
  433. # must be > 1
  434. self.assertRaises(ValueError, run_scrypt, -1)
  435. self.assertRaises(ValueError, run_scrypt, 0)
  436. self.assertEqual(run_scrypt(1), 'f2960ea8b7d48231fcec1b89b784a6fa')
  437. self.assertEqual(run_scrypt(2), 'dacf2bca255e2870e6636fa8c8957a66')
  438. self.assertEqual(run_scrypt(5), '848a0eeb2b3543e7f543844d6ca79782')
  439. # reject r*p >= 2**30
  440. self.assertRaises(ValueError, run_scrypt, (1<<30), r=1)
  441. self.assertRaises(ValueError, run_scrypt, (1<<30) / 2, r=2)
  442. def test_keylen_param(self):
  443. """'keylen' parameter"""
  444. rng = self.getRandom()
  445. def run_scrypt(keylen):
  446. return hexstr(scrypt_mod.scrypt("secret", "salt", 2, 2, 2, keylen))
  447. # must be > 0
  448. self.assertRaises(ValueError, run_scrypt, -1)
  449. self.assertRaises(ValueError, run_scrypt, 0)
  450. self.assertEqual(run_scrypt(1), 'da')
  451. # pick random value
  452. ksize = rng.randint(1, 1 << 10)
  453. self.assertEqual(len(run_scrypt(ksize)), 2*ksize) # 2 hex chars per output
  454. # one more than upper bound
  455. self.assertRaises(ValueError, run_scrypt, ((2**32) - 1) * 32 + 1)
  456. #=============================================================================
  457. # eoc
  458. #=============================================================================
  459. #-----------------------------------------------------------------------
  460. # check what backends 'should' be available
  461. #-----------------------------------------------------------------------
  462. def _can_import_cffi_scrypt():
  463. try:
  464. import scrypt
  465. except ImportError as err:
  466. if "scrypt" in str(err):
  467. return False
  468. raise
  469. return True
  470. has_cffi_scrypt = _can_import_cffi_scrypt()
  471. def _can_import_stdlib_scrypt():
  472. try:
  473. from hashlib import scrypt
  474. return True
  475. except ImportError:
  476. return False
  477. has_stdlib_scrypt = _can_import_stdlib_scrypt()
  478. #-----------------------------------------------------------------------
  479. # test individual backends
  480. #-----------------------------------------------------------------------
  481. # NOTE: builtin version runs VERY slow (except under PyPy, where it's only 11x slower),
  482. # so skipping under quick test mode.
  483. @skipUnless(PYPY or TEST_MODE(min="default"), "skipped under current test mode")
  484. class BuiltinScryptTest(_CommonScryptTest):
  485. backend = "builtin"
  486. def setUp(self):
  487. super(BuiltinScryptTest, self).setUp()
  488. warnings.filterwarnings("ignore", "(?i)using builtin scrypt backend",
  489. category=exc.PasslibSecurityWarning)
  490. def test_missing_backend(self):
  491. """backend management -- missing backend"""
  492. if has_stdlib_scrypt or has_cffi_scrypt:
  493. raise self.skipTest("non-builtin backend is present")
  494. self.assertRaises(exc.MissingBackendError, scrypt_mod._set_backend, 'scrypt')
  495. @skipUnless(has_cffi_scrypt, "'scrypt' package not found")
  496. class ScryptPackageTest(_CommonScryptTest):
  497. backend = "scrypt"
  498. def test_default_backend(self):
  499. """backend management -- default backend"""
  500. if has_stdlib_scrypt:
  501. raise self.skipTest("higher priority backend present")
  502. scrypt_mod._set_backend("default")
  503. self.assertEqual(scrypt_mod.backend, "scrypt")
  504. @skipUnless(has_stdlib_scrypt, "'hashlib.scrypt()' not found")
  505. class StdlibScryptTest(_CommonScryptTest):
  506. backend = "stdlib"
  507. def test_default_backend(self):
  508. """backend management -- default backend"""
  509. scrypt_mod._set_backend("default")
  510. self.assertEqual(scrypt_mod.backend, "stdlib")
  511. #=============================================================================
  512. # eof
  513. #=============================================================================