| #!/usr/bin/env python3 |
| # SPDX-License-Identifier: GPL-2.0-only |
| # |
| # Copyright 2021 Google LLC |
| # |
| # Generate most of the test vectors for the FIPS 140 cryptographic self-tests. |
| # |
| # Usage: |
| # tools/crypto/gen_fips140_testvecs.py > crypto/fips140-generated-testvecs.h |
| # |
| # Prerequisites: |
| # Debian: apt-get install python3-pycryptodome python3-cryptography |
| # Arch Linux: pacman -S python-pycryptodomex python-cryptography |
| |
| import hashlib |
| import hmac |
| import os |
| |
| import Cryptodome.Cipher.AES |
| import Cryptodome.Util.Counter |
| |
| import cryptography.hazmat.primitives.ciphers |
| import cryptography.hazmat.primitives.ciphers.algorithms |
| import cryptography.hazmat.primitives.ciphers.modes |
| |
| scriptname = os.path.basename(__file__) |
| |
| message = bytes('This is a 32-byte test message.\0', 'ascii') |
| aes_key = bytes('128-bit AES key\0', 'ascii') |
| aes_xts_key = bytes('This is an AES-128-XTS key.\0\0\0\0\0', 'ascii') |
| aes_iv = bytes('ABCDEFGHIJKL\0\0\0\0', 'ascii') |
| assoc = bytes('associated data string', 'ascii') |
| hmac_key = bytes('128-bit HMAC key', 'ascii') |
| |
| def warn_generated(): |
| print(f'''/* |
| * This header was automatically generated by {scriptname}. |
| * Don't edit it directly. |
| */''') |
| |
| def is_string_value(value): |
| return (value.isascii() and |
| all(c == '\x00' or c.isprintable() for c in str(value, 'ascii'))) |
| |
| def format_value(value, is_string): |
| if is_string: |
| return value |
| hexstr = '' |
| for byte in value: |
| hexstr += f'\\x{byte:02x}' |
| return hexstr |
| |
| def print_value(name, value): |
| is_string = is_string_value(value) |
| hdr = f'static const u8 fips_{name}[{len(value)}] __initconst =' |
| print(hdr, end='') |
| if is_string: |
| value = str(value, 'ascii').rstrip('\x00') |
| chars_per_byte = 1 |
| else: |
| chars_per_byte = 4 |
| bytes_per_line = 64 // chars_per_byte |
| |
| if len(hdr) + (chars_per_byte * len(value)) + 4 <= 80: |
| print(f' "{format_value(value, is_string)}"', end='') |
| else: |
| for chunk in [value[i:i+bytes_per_line] |
| for i in range(0, len(value), bytes_per_line)]: |
| print(f'\n\t"{format_value(chunk, is_string)}"', end='') |
| print(';') |
| print('') |
| |
| def generate_aes_testvecs(): |
| print_value('aes_key', aes_key) |
| print_value('aes_iv', aes_iv) |
| |
| cbc = Cryptodome.Cipher.AES.new(aes_key, Cryptodome.Cipher.AES.MODE_CBC, |
| iv=aes_iv) |
| print_value('aes_cbc_ciphertext', cbc.encrypt(message)) |
| |
| ecb = Cryptodome.Cipher.AES.new(aes_key, Cryptodome.Cipher.AES.MODE_ECB) |
| print_value('aes_ecb_ciphertext', ecb.encrypt(message)) |
| |
| ctr = Cryptodome.Cipher.AES.new(aes_key, Cryptodome.Cipher.AES.MODE_CTR, |
| nonce=aes_iv[:12]) |
| print_value('aes_ctr_ciphertext', ctr.encrypt(message)) |
| |
| print_value('aes_gcm_assoc', assoc) |
| gcm = Cryptodome.Cipher.AES.new(aes_key, Cryptodome.Cipher.AES.MODE_GCM, |
| nonce=aes_iv[:12], mac_len=16) |
| gcm.update(assoc) |
| raw_ciphertext, tag = gcm.encrypt_and_digest(message) |
| print_value('aes_gcm_ciphertext', raw_ciphertext + tag) |
| |
| # Unfortunately, pycryptodome doesn't support XTS, so for it we need to use |
| # a different Python package (the "cryptography" package). |
| print_value('aes_xts_key', aes_xts_key) |
| xts = cryptography.hazmat.primitives.ciphers.Cipher( |
| cryptography.hazmat.primitives.ciphers.algorithms.AES(aes_xts_key), |
| cryptography.hazmat.primitives.ciphers.modes.XTS(aes_iv)).encryptor() |
| ciphertext = xts.update(message) + xts.finalize() |
| print_value('aes_xts_ciphertext', ciphertext) |
| |
| def generate_sha_testvecs(): |
| print_value('hmac_key', hmac_key) |
| for alg in ['sha1', 'sha256', 'hmac_sha256', 'sha512']: |
| if alg.startswith('hmac_'): |
| h = hmac.new(hmac_key, message, alg.removeprefix('hmac_')) |
| else: |
| h = hashlib.new(alg, message) |
| print_value(f'{alg}_digest', h.digest()) |
| |
| print('/* SPDX-License-Identifier: GPL-2.0-only */') |
| print('/* Copyright 2021 Google LLC */') |
| print('') |
| warn_generated() |
| print('') |
| print_value('message', message) |
| generate_aes_testvecs() |
| generate_sha_testvecs() |
| warn_generated() |