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