diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 9bb1e022..0e1bd37f 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -5,7 +5,6 @@ import struct import itertools import contextlib import hashlib -from binascii import unhexlify as unhex try: from math import gcd except ImportError: @@ -28,6 +27,9 @@ def nbits(n): toret += 1 return toret +def unhex(s): + return s.replace(" ", "").replace("\n", "").decode("hex") + def ssh_uint32(n): return struct.pack(">L", n) def ssh_string(s): @@ -753,6 +755,132 @@ class crypt(unittest.TestCase): self.assertEqual( fp, b"768 96:12:c8:bc:e6:03:75:86:e8:c7:b9:af:d8:0c:15:75") + def testAES(self): + # My own test cases, generated by a mostly independent + # reference implementation of AES in Python. ('Mostly' + # independent in that it was written by me.) + + def vector(cipher, key, iv, plaintext, ciphertext): + c = ssh2_cipher_new(cipher) + ssh2_cipher_setkey(c, key) + ssh2_cipher_setiv(c, iv) + self.assertEqual(ssh2_cipher_encrypt(c, plaintext), ciphertext) + ssh2_cipher_setiv(c, iv) + self.assertEqual(ssh2_cipher_decrypt(c, ciphertext), plaintext) + + # Tests of CBC mode. + + key = unhex( + '98483c6eb40b6c31a448c22a66ded3b5e5e8d5119cac8327b655c8b5c4836489') + iv = unhex('38f87b0b9b736160bfc0cbd8447af6ee') + plaintext = unhex(''' + ee16271827b12d828f61d56fddccc38ccaa69601da2b36d3af1a34c51947b71a + 362f05e07bf5e7766c24599799b252ad2d5954353c0c6ca668c46779c2659c94 + 8df04e4179666e335470ff042e213c8bcff57f54842237fbf9f3c7e6111620ac + 1c007180edd25f0e337c2a49d890a7173f6b52d61e3d2a21ddc8e41513a0e825 + afd5932172270940b01014b5b7fb8495946151520a126518946b44ea32f9b2a9 + ''') + + vector('aes128', key[:16], iv, plaintext, unhex(''' + 547ee90514cb6406d5bb00855c8092892c58299646edda0b4e7c044247795c8d + 3c3eb3d91332e401215d4d528b94a691969d27b7890d1ae42fe3421b91c989d5 + 113fefa908921a573526259c6b4f8e4d90ea888e1d8b7747457ba3a43b5b79b9 + 34873ebf21102d14b51836709ee85ed590b7ca618a1e884f5c57c8ea73fe3d0d + 6bf8c082dd602732bde28131159ed0b6e9cf67c353ffdd010a5a634815aaa963''')) + + vector('aes192', key[:24], iv, plaintext, unhex(''' + e3dee5122edd3fec5fab95e7db8c784c0cb617103e2a406fba4ae3b4508dd608 + 4ff5723a670316cc91ed86e413c11b35557c56a6f5a7a2c660fc6ee603d73814 + 73a287645be0f297cdda97aef6c51faeb2392fec9d33adb65138d60f954babd9 + 8ee0daab0d1decaa8d1e07007c4a3c7b726948025f9fb72dd7de41f74f2f36b4 + 23ac6a5b4b6b39682ec74f57d9d300e547f3c3e467b77f5e4009923b2f94c903''')) + + vector('aes256', key[:32], iv, plaintext, unhex(''' + 088c6d4d41997bea79c408925255266f6c32c03ea465a5f607c2f076ec98e725 + 7e0beed79609b3577c16ebdf17d7a63f8865278e72e859e2367de81b3b1fe9ab + 8f045e1d008388a3cfc4ff87daffedbb47807260489ad48566dbe73256ce9dd4 + ae1689770a883b29695928f5983f33e8d7aec4668f64722e943b0b671c365709 + dfa86c648d5fb00544ff11bd29121baf822d867e32da942ba3a0d26299bcee13''')) + + # Tests of SDCTR mode, one with a random IV and one with an IV + # about to wrap round. More vigorous tests of IV carry and + # wraparound behaviour are in the testAESSDCTR method. + + sdctrIVs = [ + unhex('38f87b0b9b736160bfc0cbd8447af6ee'), + unhex('fffffffffffffffffffffffffffffffe'), + ] + + vector('aes128_ctr', key[:16], sdctrIVs[0], plaintext[:64], unhex(''' + d0061d7b6e8c4ef4fe5614b95683383f46cdd2766e66b6fb0b0f0b3a24520b2d + 15d869b06cbf685ede064bcf8fb5fb6726cfd68de7016696a126e9e84420af38''')) + vector('aes128_ctr', key[:16], sdctrIVs[1], plaintext[:64], unhex(''' + 49ac67164fd9ce8701caddbbc9a2b06ac6524d4aa0fdac95253971974b8f3bc2 + bb8d7c970f6bcd79b25218cc95582edf7711aae2384f6cf91d8d07c9d9b370bc''')) + + vector('aes192_ctr', key[:24], sdctrIVs[0], plaintext[:64], unhex(''' + 0baa86acbe8580845f0671b7ebad4856ca11b74e5108f515e34e54fa90f87a9a + c6eee26686253c19156f9be64957f0dbc4f8ecd7cabb1f4e0afefe33888faeec''')) + vector('aes192_ctr', key[:24], sdctrIVs[1], plaintext[:64], unhex(''' + 2da1791250100dc0d1461afe1bbfad8fa0320253ba5d7905d837386ba0a3a41f + 01965c770fcfe01cf307b5316afb3981e0e4aa59a6e755f0a5784d9accdc52be''')) + + vector('aes256_ctr', key[:32], sdctrIVs[0], plaintext[:64], unhex(''' + 49c7b284222d408544c770137b6ef17ef770c47e24f61fa66e7e46cae4888882 + f980a0f2446956bf47d2aed55ebd2e0694bfc46527ed1fd33efe708fec2f8b1f''')) + vector('aes256_ctr', key[:32], sdctrIVs[1], plaintext[:64], unhex(''' + f1d013c3913ccb4fc0091e25d165804480fb0a1d5c741bf012bba144afda6db2 + c512f3942018574bd7a8fdd88285a73d25ef81e621aebffb6e9b8ecc8e2549d4''')) + + def testAESSDCTR(self): + # A thorough test of the IV-incrementing component of SDCTR + # mode. We set up an AES-SDCTR cipher object with the given + # input IV; we encrypt two all-zero blocks, expecting the + # return values to be the AES-ECB encryptions of the input IV + # and the incremented version. Then we decrypt each of them by + # feeding them to an AES-CBC cipher object with its IV set to + # zero. + + def increment(keylen, iv): + key = b'\xab' * (keylen//8) + sdctr = ssh2_cipher_new("aes{}_ctr".format(keylen)) + ssh2_cipher_setkey(sdctr, key) + cbc = ssh2_cipher_new("aes{}".format(keylen)) + ssh2_cipher_setkey(cbc, key) + + ssh2_cipher_setiv(sdctr, iv) + ec0 = ssh2_cipher_encrypt(sdctr, b'\x00' * 16) + ec1 = ssh2_cipher_encrypt(sdctr, b'\x00' * 16) + ssh2_cipher_setiv(cbc, b'\x00' * 16) + dc0 = ssh2_cipher_decrypt(cbc, ec0) + ssh2_cipher_setiv(cbc, b'\x00' * 16) + dc1 = ssh2_cipher_decrypt(cbc, ec1) + self.assertEqual(iv, dc0) + return dc1 + + def test(keylen, ivInteger): + mask = (1 << 128) - 1 + ivInteger &= mask + ivBinary = unhex("{:032x}".format(ivInteger)) + ivIntegerInc = (ivInteger + 1) & mask + ivBinaryInc = unhex("{:032x}".format((ivIntegerInc))) + actualResult = increment(keylen, ivBinary) + self.assertEqual(actualResult, ivBinaryInc) + + # Check every input IV you can make by gluing together 32-bit + # pieces of the form 0, 1 or -1. This should test all the + # places where carry propagation within the 128-bit integer + # can go wrong. + # + # We also test this at all three AES key lengths, in case the + # core cipher routines are written separately for each one. + + for keylen in [128, 192, 256]: + hexTestValues = ["00000000", "00000001", "ffffffff"] + for ivHexBytes in itertools.product(*([hexTestValues] * 4)): + ivInteger = int("".join(ivHexBytes), 16) + test(keylen, ivInteger) + class standard_test_vectors(unittest.TestCase): def testAES(self): def vector(cipher, key, plaintext, ciphertext):