test_PBES.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. #
  2. # SelfTest/IO/test_PBES.py: Self-test for the _PBES module
  3. #
  4. # ===================================================================
  5. #
  6. # Copyright (c) 2014, Legrandin <helderijs@gmail.com>
  7. # All rights reserved.
  8. #
  9. # Redistribution and use in source and binary forms, with or without
  10. # modification, are permitted provided that the following conditions
  11. # are met:
  12. #
  13. # 1. Redistributions of source code must retain the above copyright
  14. # notice, this list of conditions and the following disclaimer.
  15. # 2. Redistributions in binary form must reproduce the above copyright
  16. # notice, this list of conditions and the following disclaimer in
  17. # the documentation and/or other materials provided with the
  18. # distribution.
  19. #
  20. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  23. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  24. # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  25. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  26. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  27. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  28. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  29. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  30. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  31. # POSSIBILITY OF SUCH DAMAGE.
  32. # ===================================================================
  33. """Self-tests for Crypto.IO._PBES module"""
  34. import unittest
  35. from Crypto.IO._PBES import PBES2, PbesError, _DEFAULT_MAX_ITERATION_COUNT
  36. from Crypto.Util.asn1 import (DerSequence, DerOctetString,
  37. DerObjectId, DerInteger)
  38. class TestPBES2(unittest.TestCase):
  39. def setUp(self):
  40. self.ref = b"Test data"
  41. self.passphrase = b"Passphrase"
  42. def test1(self):
  43. ct = PBES2.encrypt(self.ref, self.passphrase,
  44. 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC')
  45. pt = PBES2.decrypt(ct, self.passphrase)
  46. self.assertEqual(self.ref, pt)
  47. def test2(self):
  48. ct = PBES2.encrypt(self.ref, self.passphrase,
  49. 'PBKDF2WithHMAC-SHA224AndAES128-CBC')
  50. pt = PBES2.decrypt(ct, self.passphrase)
  51. self.assertEqual(self.ref, pt)
  52. def test3(self):
  53. ct = PBES2.encrypt(self.ref, self.passphrase,
  54. 'PBKDF2WithHMAC-SHA256AndAES192-CBC')
  55. pt = PBES2.decrypt(ct, self.passphrase)
  56. self.assertEqual(self.ref, pt)
  57. def test4(self):
  58. ct = PBES2.encrypt(self.ref, self.passphrase,
  59. 'PBKDF2WithHMAC-SHA384AndAES256-CBC')
  60. pt = PBES2.decrypt(ct, self.passphrase)
  61. self.assertEqual(self.ref, pt)
  62. def test5(self):
  63. ct = PBES2.encrypt(self.ref, self.passphrase,
  64. 'PBKDF2WithHMAC-SHA512AndAES128-GCM')
  65. pt = PBES2.decrypt(ct, self.passphrase)
  66. self.assertEqual(self.ref, pt)
  67. def test6(self):
  68. ct = PBES2.encrypt(self.ref, self.passphrase,
  69. 'PBKDF2WithHMAC-SHA512-224AndAES192-GCM')
  70. pt = PBES2.decrypt(ct, self.passphrase)
  71. self.assertEqual(self.ref, pt)
  72. def test7(self):
  73. ct = PBES2.encrypt(self.ref, self.passphrase,
  74. 'PBKDF2WithHMAC-SHA3-256AndAES256-GCM')
  75. pt = PBES2.decrypt(ct, self.passphrase)
  76. self.assertEqual(self.ref, pt)
  77. def test8(self):
  78. ct = PBES2.encrypt(self.ref, self.passphrase,
  79. 'scryptAndAES128-CBC')
  80. pt = PBES2.decrypt(ct, self.passphrase)
  81. self.assertEqual(self.ref, pt)
  82. def test9(self):
  83. ct = PBES2.encrypt(self.ref, self.passphrase,
  84. 'scryptAndAES192-CBC')
  85. pt = PBES2.decrypt(ct, self.passphrase)
  86. self.assertEqual(self.ref, pt)
  87. def test10(self):
  88. ct = PBES2.encrypt(self.ref, self.passphrase,
  89. 'scryptAndAES256-CBC')
  90. pt = PBES2.decrypt(ct, self.passphrase)
  91. self.assertEqual(self.ref, pt)
  92. class TestPBES2_IterationLimit(unittest.TestCase):
  93. """Tests for the max_iteration_count guard in PBES2.decrypt."""
  94. def setUp(self):
  95. self.ref = b"Test data"
  96. self.passphrase = b"Passphrase"
  97. def _make_pbkdf2_blob(self, iteration_count):
  98. """Build a valid PBES2/PBKDF2 encrypted blob with the given
  99. iteration count by encrypting normally and then patching the
  100. DER-encoded iteration count field in place."""
  101. # Encrypt with a low count so it's fast
  102. ct = PBES2.encrypt(self.ref, self.passphrase,
  103. 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC',
  104. prot_params={'iteration_count': 1000})
  105. # Decode the outer structure, replace the iteration count, re-encode.
  106. # The blob is:
  107. # SEQUENCE {
  108. # SEQUENCE { # encryptionAlgorithm
  109. # OID 1.2.840.113549.1.5.13 # PBES2
  110. # SEQUENCE { # PBES2-params
  111. # SEQUENCE { # kdf
  112. # OID 1.2.840.113549.1.5.12 # PBKDF2
  113. # SEQUENCE { salt, iterCount } # PBKDF2-params
  114. # }
  115. # SEQUENCE { ... } # enc
  116. # }
  117. # }
  118. # OCTET STRING # encrypted data
  119. # }
  120. outer = DerSequence().decode(ct, nr_elements=2)
  121. enc_algo = DerSequence().decode(outer[0])
  122. pbes2_params = DerSequence().decode(enc_algo[1], nr_elements=2)
  123. kdf_info = DerSequence().decode(pbes2_params[0], nr_elements=2)
  124. pbkdf2_params = DerSequence().decode(kdf_info[1], nr_elements=(2, 3, 4))
  125. # Patch the iteration count (index 1)
  126. pbkdf2_params[1] = iteration_count
  127. # Re-encode all the way back up
  128. kdf_info[1] = pbkdf2_params.encode()
  129. pbes2_params[0] = kdf_info.encode()
  130. enc_algo[1] = pbes2_params.encode()
  131. outer[0] = enc_algo.encode()
  132. return outer.encode()
  133. def _make_scrypt_blob(self, cost_param):
  134. """Build a valid PBES2/scrypt encrypted blob with the given
  135. scrypt cost parameter (N) by encrypting normally then patching."""
  136. ct = PBES2.encrypt(self.ref, self.passphrase,
  137. 'scryptAndAES128-CBC',
  138. prot_params={'iteration_count': 16384})
  139. outer = DerSequence().decode(ct, nr_elements=2)
  140. enc_algo = DerSequence().decode(outer[0])
  141. pbes2_params = DerSequence().decode(enc_algo[1], nr_elements=2)
  142. kdf_info = DerSequence().decode(pbes2_params[0], nr_elements=2)
  143. scrypt_params = DerSequence().decode(kdf_info[1], nr_elements=(4, 5))
  144. # Patch the cost parameter (index 1)
  145. scrypt_params[1] = cost_param
  146. kdf_info[1] = scrypt_params.encode()
  147. pbes2_params[0] = kdf_info.encode()
  148. enc_algo[1] = pbes2_params.encode()
  149. outer[0] = enc_algo.encode()
  150. return outer.encode()
  151. def test_pbkdf2_default_limit_rejects_high_count(self):
  152. """PBES2.decrypt rejects PBKDF2 iteration count above the default
  153. limit (50M) without burning CPU."""
  154. blob = self._make_pbkdf2_blob(2**31 - 1)
  155. with self.assertRaises(PbesError) as ctx:
  156. PBES2.decrypt(blob, self.passphrase)
  157. self.assertIn("too high", str(ctx.exception))
  158. def test_pbkdf2_custom_limit(self):
  159. """PBES2.decrypt honours a caller-supplied max_iteration_count."""
  160. blob = self._make_pbkdf2_blob(5000)
  161. with self.assertRaises(PbesError):
  162. PBES2.decrypt(blob, self.passphrase, max_iteration_count=4999)
  163. def test_pbkdf2_limit_disabled_with_zero(self):
  164. """Setting max_iteration_count=0 disables the check.
  165. We use a very low iteration count so this stays fast."""
  166. # Encrypt with count=1000, then decrypt with check disabled.
  167. ct = PBES2.encrypt(self.ref, self.passphrase,
  168. 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC',
  169. prot_params={'iteration_count': 1000})
  170. pt = PBES2.decrypt(ct, self.passphrase, max_iteration_count=0)
  171. self.assertEqual(self.ref, pt)
  172. def test_scrypt_default_limit_rejects_high_count(self):
  173. """PBES2.decrypt rejects scrypt cost parameter above the default
  174. limit without burning CPU."""
  175. blob = self._make_scrypt_blob(2**31 - 1)
  176. with self.assertRaises(PbesError) as ctx:
  177. PBES2.decrypt(blob, self.passphrase)
  178. self.assertIn("too high", str(ctx.exception))
  179. def test_scrypt_custom_limit(self):
  180. """PBES2.decrypt honours a caller-supplied max_iteration_count
  181. for scrypt."""
  182. blob = self._make_scrypt_blob(100000)
  183. with self.assertRaises(PbesError):
  184. PBES2.decrypt(blob, self.passphrase, max_iteration_count=99999)
  185. def test_normal_decrypt_within_limit(self):
  186. """Normal encrypt→decrypt round-trip still works with the default
  187. limit in place (default PBKDF2 count is 1000)."""
  188. ct = PBES2.encrypt(self.ref, self.passphrase,
  189. 'PBKDF2WithHMAC-SHA256AndAES256-CBC')
  190. pt = PBES2.decrypt(ct, self.passphrase)
  191. self.assertEqual(self.ref, pt)
  192. def test_at_boundary_is_allowed(self):
  193. """An iteration count exactly at the limit should be accepted."""
  194. ct = PBES2.encrypt(self.ref, self.passphrase,
  195. 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC',
  196. prot_params={'iteration_count': 5000})
  197. # max_iteration_count == iteration_count → should pass
  198. pt = PBES2.decrypt(ct, self.passphrase, max_iteration_count=5000)
  199. self.assertEqual(self.ref, pt)
  200. def get_tests(config={}):
  201. from Crypto.SelfTest.st_common import list_test_cases
  202. listTests = []
  203. listTests += list_test_cases(TestPBES2)
  204. listTests += list_test_cases(TestPBES2_IterationLimit)
  205. return listTests
  206. if __name__ == '__main__':
  207. def suite():
  208. return unittest.TestSuite(get_tests())
  209. unittest.main(defaultTest='suite')