#!/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('ABCDEFGHIJKLMNOP', '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=bytes(), initial_value=aes_iv) 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) cmac = Cryptodome.Hash.CMAC.new(aes_key, ciphermod=Cryptodome.Cipher.AES) cmac.update(message) print_value('aes_cmac_digest', cmac.digest()) 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()