Eric Biggers | b7397e8 | 2021-07-08 14:46:46 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # SPDX-License-Identifier: GPL-2.0-only |
| 3 | # |
| 4 | # Copyright 2021 Google LLC |
| 5 | # |
| 6 | # Generate most of the test vectors for the FIPS 140 cryptographic self-tests. |
| 7 | # |
| 8 | # Usage: |
| 9 | # tools/crypto/gen_fips140_testvecs.py > crypto/fips140-generated-testvecs.h |
| 10 | # |
| 11 | # Prerequisites: |
| 12 | # Debian: apt-get install python3-pycryptodome python3-cryptography |
| 13 | # Arch Linux: pacman -S python-pycryptodomex python-cryptography |
| 14 | |
| 15 | import hashlib |
| 16 | import hmac |
| 17 | import os |
| 18 | |
| 19 | import Cryptodome.Cipher.AES |
| 20 | import Cryptodome.Util.Counter |
| 21 | |
| 22 | import cryptography.hazmat.primitives.ciphers |
| 23 | import cryptography.hazmat.primitives.ciphers.algorithms |
| 24 | import cryptography.hazmat.primitives.ciphers.modes |
| 25 | |
| 26 | scriptname = os.path.basename(__file__) |
| 27 | |
| 28 | message = bytes('This is a 32-byte test message.\0', 'ascii') |
| 29 | aes_key = bytes('128-bit AES key\0', 'ascii') |
| 30 | aes_xts_key = bytes('This is an AES-128-XTS key.\0\0\0\0\0', 'ascii') |
Eric Biggers | 17ccefe | 2021-08-04 17:21:56 -0700 | [diff] [blame] | 31 | aes_iv = bytes('ABCDEFGHIJKLMNOP', 'ascii') |
Eric Biggers | b7397e8 | 2021-07-08 14:46:46 -0700 | [diff] [blame] | 32 | assoc = bytes('associated data string', 'ascii') |
| 33 | hmac_key = bytes('128-bit HMAC key', 'ascii') |
| 34 | |
| 35 | def warn_generated(): |
| 36 | print(f'''/* |
| 37 | * This header was automatically generated by {scriptname}. |
| 38 | * Don't edit it directly. |
| 39 | */''') |
| 40 | |
| 41 | def is_string_value(value): |
| 42 | return (value.isascii() and |
| 43 | all(c == '\x00' or c.isprintable() for c in str(value, 'ascii'))) |
| 44 | |
| 45 | def format_value(value, is_string): |
| 46 | if is_string: |
| 47 | return value |
| 48 | hexstr = '' |
| 49 | for byte in value: |
| 50 | hexstr += f'\\x{byte:02x}' |
| 51 | return hexstr |
| 52 | |
| 53 | def print_value(name, value): |
| 54 | is_string = is_string_value(value) |
| 55 | hdr = f'static const u8 fips_{name}[{len(value)}] __initconst =' |
| 56 | print(hdr, end='') |
| 57 | if is_string: |
| 58 | value = str(value, 'ascii').rstrip('\x00') |
| 59 | chars_per_byte = 1 |
| 60 | else: |
| 61 | chars_per_byte = 4 |
| 62 | bytes_per_line = 64 // chars_per_byte |
| 63 | |
| 64 | if len(hdr) + (chars_per_byte * len(value)) + 4 <= 80: |
| 65 | print(f' "{format_value(value, is_string)}"', end='') |
| 66 | else: |
| 67 | for chunk in [value[i:i+bytes_per_line] |
| 68 | for i in range(0, len(value), bytes_per_line)]: |
| 69 | print(f'\n\t"{format_value(chunk, is_string)}"', end='') |
| 70 | print(';') |
| 71 | print('') |
| 72 | |
| 73 | def generate_aes_testvecs(): |
| 74 | print_value('aes_key', aes_key) |
| 75 | print_value('aes_iv', aes_iv) |
| 76 | |
| 77 | cbc = Cryptodome.Cipher.AES.new(aes_key, Cryptodome.Cipher.AES.MODE_CBC, |
| 78 | iv=aes_iv) |
| 79 | print_value('aes_cbc_ciphertext', cbc.encrypt(message)) |
| 80 | |
| 81 | ecb = Cryptodome.Cipher.AES.new(aes_key, Cryptodome.Cipher.AES.MODE_ECB) |
| 82 | print_value('aes_ecb_ciphertext', ecb.encrypt(message)) |
| 83 | |
| 84 | ctr = Cryptodome.Cipher.AES.new(aes_key, Cryptodome.Cipher.AES.MODE_CTR, |
Eric Biggers | 17ccefe | 2021-08-04 17:21:56 -0700 | [diff] [blame] | 85 | nonce=bytes(), initial_value=aes_iv) |
Eric Biggers | b7397e8 | 2021-07-08 14:46:46 -0700 | [diff] [blame] | 86 | print_value('aes_ctr_ciphertext', ctr.encrypt(message)) |
| 87 | |
| 88 | print_value('aes_gcm_assoc', assoc) |
| 89 | gcm = Cryptodome.Cipher.AES.new(aes_key, Cryptodome.Cipher.AES.MODE_GCM, |
| 90 | nonce=aes_iv[:12], mac_len=16) |
| 91 | gcm.update(assoc) |
| 92 | raw_ciphertext, tag = gcm.encrypt_and_digest(message) |
| 93 | print_value('aes_gcm_ciphertext', raw_ciphertext + tag) |
| 94 | |
| 95 | # Unfortunately, pycryptodome doesn't support XTS, so for it we need to use |
| 96 | # a different Python package (the "cryptography" package). |
| 97 | print_value('aes_xts_key', aes_xts_key) |
| 98 | xts = cryptography.hazmat.primitives.ciphers.Cipher( |
| 99 | cryptography.hazmat.primitives.ciphers.algorithms.AES(aes_xts_key), |
| 100 | cryptography.hazmat.primitives.ciphers.modes.XTS(aes_iv)).encryptor() |
| 101 | ciphertext = xts.update(message) + xts.finalize() |
| 102 | print_value('aes_xts_ciphertext', ciphertext) |
| 103 | |
Eric Biggers | 2ee56aa | 2021-08-04 17:21:59 -0700 | [diff] [blame] | 104 | cmac = Cryptodome.Hash.CMAC.new(aes_key, ciphermod=Cryptodome.Cipher.AES) |
| 105 | cmac.update(message) |
| 106 | print_value('aes_cmac_digest', cmac.digest()) |
| 107 | |
Eric Biggers | b7397e8 | 2021-07-08 14:46:46 -0700 | [diff] [blame] | 108 | def generate_sha_testvecs(): |
| 109 | print_value('hmac_key', hmac_key) |
| 110 | for alg in ['sha1', 'sha256', 'hmac_sha256', 'sha512']: |
| 111 | if alg.startswith('hmac_'): |
| 112 | h = hmac.new(hmac_key, message, alg.removeprefix('hmac_')) |
| 113 | else: |
| 114 | h = hashlib.new(alg, message) |
| 115 | print_value(f'{alg}_digest', h.digest()) |
| 116 | |
| 117 | print('/* SPDX-License-Identifier: GPL-2.0-only */') |
| 118 | print('/* Copyright 2021 Google LLC */') |
| 119 | print('') |
| 120 | warn_generated() |
| 121 | print('') |
| 122 | print_value('message', message) |
| 123 | generate_aes_testvecs() |
| 124 | generate_sha_testvecs() |
| 125 | warn_generated() |