# -*- coding: utf-8 -*- # # SelfTest/Cipher/test_pkcs1_15.py: Self-test for PKCS#1 v1.5 encryption # # =================================================================== # The contents of this file are dedicated to the public domain. To # the extent that dedication to the public domain is not available, # everyone is granted a worldwide, perpetual, royalty-free, # non-exclusive license to exercise all rights associated with the # contents of this file for any purpose whatsoever. # No rights are reserved. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # =================================================================== from __future__ import print_function import unittest from Crypto.PublicKey import RSA from Crypto.SelfTest.st_common import list_test_cases, a2b_hex from Crypto import Random from Crypto.Cipher import PKCS1_v1_5 as PKCS from Crypto.Util.py3compat import b from Crypto.Util.number import bytes_to_long, long_to_bytes from Crypto.SelfTest.loader import load_test_vectors_wycheproof def rws(t): """Remove white spaces, tabs, and new lines from a string""" for c in ['\n', '\t', ' ']: t = t.replace(c, '') return t def t2b(t): """Convert a text string with bytes in hex form to a byte string""" clean = b(rws(t)) if len(clean) % 2 == 1: raise ValueError("Even number of characters expected") return a2b_hex(clean) class PKCS1_15_Tests(unittest.TestCase): def setUp(self): self.rng = Random.new().read self.key1024 = RSA.generate(1024, self.rng) # List of tuples with test data for PKCS#1 v1.5. # Each tuple is made up by: # Item #0: dictionary with RSA key component, or key to import # Item #1: plaintext # Item #2: ciphertext # Item #3: random data _testData = ( # # Generated with openssl 0.9.8o # ( # Private key '''-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQDAiAnvIAOvqVwJTaYzsKnefZftgtXGE2hPJppGsWl78yz9jeXY W/FxX/gTPURArNhdnhP6n3p2ZaDIBrO2zizbgIXs0IsljTTcr4vnI8fMXzyNUOjA zP3nzMqZDZK6757XQAobOssMkBFqRWwilT/3DsBhRpl3iMUhF+wvpTSHewIDAQAB AoGAC4HV/inOrpgTvSab8Wj0riyZgQOZ3U3ZpSlsfR8ra9Ib9Uee3jCYnKscu6Gk y6zI/cdt8EPJ4PuwAWSNJzbpbVaDvUq25OD+CX8/uRT08yBS4J8TzBitZJTD4lS7 atdTnKT0Wmwk+u8tDbhvMKwnUHdJLcuIsycts9rwJVapUtkCQQDvDpx2JMun0YKG uUttjmL8oJ3U0m3ZvMdVwBecA0eebZb1l2J5PvI3EJD97eKe91Nsw8T3lwpoN40k IocSVDklAkEAzi1HLHE6EzVPOe5+Y0kGvrIYRRhncOb72vCvBZvD6wLZpQgqo6c4 d3XHFBBQWA6xcvQb5w+VVEJZzw64y25sHwJBAMYReRl6SzL0qA0wIYrYWrOt8JeQ 8mthulcWHXmqTgC6FEXP9Es5GD7/fuKl4wqLKZgIbH4nqvvGay7xXLCXD/ECQH9a 1JYNMtRen5unSAbIOxRcKkWz92F0LKpm9ZW/S9vFHO+mBcClMGoKJHiuQxLBsLbT NtEZfSJZAeS2sUtn3/0CQDb2M2zNBTF8LlM0nxmh0k9VGm5TVIyBEMcipmvOgqIs HKukWBcq9f/UOmS0oEhai/6g+Uf7VHJdWaeO5LzuvwU= -----END RSA PRIVATE KEY-----''', # Plaintext '''THIS IS PLAINTEXT\x0A''', # Ciphertext '''3f dc fd 3c cd 5c 9b 12 af 65 32 e3 f7 d0 da 36 8f 8f d9 e3 13 1c 7f c8 b3 f9 c1 08 e4 eb 79 9c 91 89 1f 96 3b 94 77 61 99 a4 b1 ee 5d e6 17 c9 5d 0a b5 63 52 0a eb 00 45 38 2a fb b0 71 3d 11 f7 a1 9e a7 69 b3 af 61 c0 bb 04 5b 5d 4b 27 44 1f 5b 97 89 ba 6a 08 95 ee 4f a2 eb 56 64 e5 0f da 7c f9 9a 61 61 06 62 ed a0 bc 5f aa 6c 31 78 70 28 1a bb 98 3c e3 6a 60 3c d1 0b 0f 5a f4 75''', # Random data '''eb d7 7d 86 a4 35 23 a3 54 7e 02 0b 42 1d 61 6c af 67 b8 4e 17 56 80 66 36 04 64 34 26 8a 47 dd 44 b3 1a b2 17 60 f4 91 2e e2 b5 95 64 cc f9 da c8 70 94 54 86 4c ef 5b 08 7d 18 c4 ab 8d 04 06 33 8f ca 15 5f 52 60 8a a1 0c f5 08 b5 4c bb 99 b8 94 25 04 9c e6 01 75 e6 f9 63 7a 65 61 13 8a a7 47 77 81 ae 0d b8 2c 4d 50 a5''' ), ) def testEncrypt1(self): for test in self._testData: # Build the key key = RSA.importKey(test[0]) # RNG that takes its random numbers from a pool given # at initialization class randGen: def __init__(self, data): self.data = data self.idx = 0 def __call__(self, N): r = self.data[self.idx:self.idx+N] self.idx += N return r # The real test cipher = PKCS.new(key, randfunc=randGen(t2b(test[3]))) ct = cipher.encrypt(b(test[1])) self.assertEqual(ct, t2b(test[2])) def testEncrypt2(self): # Verify that encryption fail if plaintext is too long pt = '\x00'*(128-11+1) cipher = PKCS.new(self.key1024) self.assertRaises(ValueError, cipher.encrypt, pt) def testVerify1(self): for test in self._testData: key = RSA.importKey(test[0]) expected_pt = b(test[1]) ct = t2b(test[2]) cipher = PKCS.new(key) # The real test pt = cipher.decrypt(ct, None) self.assertEqual(pt, expected_pt) pt = cipher.decrypt(ct, b'\xFF' * len(expected_pt)) self.assertEqual(pt, expected_pt) def testVerify2(self): # Verify that decryption fails if ciphertext is not as long as # RSA modulus cipher = PKCS.new(self.key1024) self.assertRaises(ValueError, cipher.decrypt, '\x00'*127, "---") self.assertRaises(ValueError, cipher.decrypt, '\x00'*129, "---") # Verify that decryption fails if there are less then 8 non-zero padding # bytes pt = b('\x00\x02' + '\xFF'*7 + '\x00' + '\x45'*118) pt_int = bytes_to_long(pt) ct_int = self.key1024._encrypt(pt_int) ct = long_to_bytes(ct_int, 128) self.assertEqual(b"---", cipher.decrypt(ct, b"---")) def testEncryptVerify1(self): # Encrypt/Verify messages of length [0..RSAlen-11] # and therefore padding [8..117] for pt_len in range(0, 128 - 11 + 1): pt = self.rng(pt_len) cipher = PKCS.new(self.key1024) ct = cipher.encrypt(pt) pt2 = cipher.decrypt(ct, b'\xAA' * pt_len) self.assertEqual(pt, pt2) def test_encrypt_verify_exp_pt_len(self): cipher = PKCS.new(self.key1024) pt = b'5' * 16 ct = cipher.encrypt(pt) sentinel = b'\xAA' * 16 pt_A = cipher.decrypt(ct, sentinel, 16) self.assertEqual(pt, pt_A) pt_B = cipher.decrypt(ct, sentinel, 15) self.assertEqual(sentinel, pt_B) pt_C = cipher.decrypt(ct, sentinel, 17) self.assertEqual(sentinel, pt_C) def testByteArray(self): pt = b"XER" cipher = PKCS.new(self.key1024) ct = cipher.encrypt(bytearray(pt)) pt2 = cipher.decrypt(bytearray(ct), '\xFF' * len(pt)) self.assertEqual(pt, pt2) def testMemoryview(self): pt = b"XER" cipher = PKCS.new(self.key1024) ct = cipher.encrypt(memoryview(bytearray(pt))) pt2 = cipher.decrypt(memoryview(bytearray(ct)), b'\xFF' * len(pt)) self.assertEqual(pt, pt2) def test_return_type(self): pt = b"XYZ" cipher = PKCS.new(self.key1024) ct = cipher.encrypt(pt) self.assertTrue(isinstance(ct, bytes)) pt2 = cipher.decrypt(ct, b'\xAA' * 3) self.assertTrue(isinstance(pt2, bytes)) class TestVectorsWycheproof(unittest.TestCase): def __init__(self, wycheproof_warnings, skip_slow_tests): unittest.TestCase.__init__(self) self._wycheproof_warnings = wycheproof_warnings self._skip_slow_tests = skip_slow_tests self._id = "None" def load_tests(self, filename): def filter_rsa(group): return RSA.import_key(group['privateKeyPem']) result = load_test_vectors_wycheproof(("Cipher", "wycheproof"), filename, "Wycheproof PKCS#1v1.5 (%s)" % filename, group_tag={'rsa_key': filter_rsa} ) return result def setUp(self): self.tv = [] self.tv.extend(self.load_tests("rsa_pkcs1_2048_test.json")) if not self._skip_slow_tests: self.tv.extend(self.load_tests("rsa_pkcs1_3072_test.json")) self.tv.extend(self.load_tests("rsa_pkcs1_4096_test.json")) def shortDescription(self): return self._id def warn(self, tv): if tv.warning and self._wycheproof_warnings: import warnings warnings.warn("Wycheproof warning: %s (%s)" % (self._id, tv.comment)) def test_decrypt(self, tv): self._id = "Wycheproof Decrypt PKCS#1v1.5 Test #%s" % tv.id sentinel = b'\xAA' * max(3, len(tv.msg)) cipher = PKCS.new(tv.rsa_key) try: pt = cipher.decrypt(tv.ct, sentinel=sentinel) except ValueError: assert not tv.valid else: if pt == sentinel: assert not tv.valid else: assert tv.valid self.assertEqual(pt, tv.msg) self.warn(tv) def runTest(self): for tv in self.tv: self.test_decrypt(tv) def get_tests(config={}): skip_slow_tests = not config.get('slow_tests') wycheproof_warnings = config.get('wycheproof_warnings') tests = [] tests += list_test_cases(PKCS1_15_Tests) tests += [TestVectorsWycheproof(wycheproof_warnings, skip_slow_tests)] return tests if __name__ == '__main__': def suite(): return unittest.TestSuite(get_tests()) unittest.main(defaultTest='suite') # vim:set ts=4 sw=4 sts=4 expandtab: