|  | #!/usr/bin/env python3 | 
|  | # SPDX-License-Identifier: GPL-2.0-or-later | 
|  | # | 
|  | # Script that generates test vectors for the given cryptographic hash function. | 
|  | # | 
|  | # Copyright 2025 Google LLC | 
|  |  | 
|  | import hashlib | 
|  | import hmac | 
|  | import sys | 
|  |  | 
|  | DATA_LENS = [0, 1, 2, 3, 16, 32, 48, 49, 63, 64, 65, 127, 128, 129, 256, 511, | 
|  | 513, 1000, 3333, 4096, 4128, 4160, 4224, 16384] | 
|  |  | 
|  | # Generate the given number of random bytes, using the length itself as the seed | 
|  | # for a simple linear congruential generator (LCG).  The C test code uses the | 
|  | # same LCG with the same seeding strategy to reconstruct the data, ensuring | 
|  | # reproducibility without explicitly storing the data in the test vectors. | 
|  | def rand_bytes(length): | 
|  | seed = length | 
|  | out = [] | 
|  | for _ in range(length): | 
|  | seed = (seed * 25214903917 + 11) % 2**48 | 
|  | out.append((seed >> 16) % 256) | 
|  | return bytes(out) | 
|  |  | 
|  | POLY1305_KEY_SIZE = 32 | 
|  |  | 
|  | # A straightforward, unoptimized implementation of Poly1305. | 
|  | # Reference: https://cr.yp.to/mac/poly1305-20050329.pdf | 
|  | class Poly1305: | 
|  | def __init__(self, key): | 
|  | assert len(key) == POLY1305_KEY_SIZE | 
|  | self.h = 0 | 
|  | rclamp = 0x0ffffffc0ffffffc0ffffffc0fffffff | 
|  | self.r = int.from_bytes(key[:16], byteorder='little') & rclamp | 
|  | self.s = int.from_bytes(key[16:], byteorder='little') | 
|  |  | 
|  | # Note: this supports partial blocks only at the end. | 
|  | def update(self, data): | 
|  | for i in range(0, len(data), 16): | 
|  | chunk = data[i:i+16] | 
|  | c = int.from_bytes(chunk, byteorder='little') + 2**(8 * len(chunk)) | 
|  | self.h = ((self.h + c) * self.r) % (2**130 - 5) | 
|  | return self | 
|  |  | 
|  | # Note: gen_additional_poly1305_testvecs() relies on this being | 
|  | # nondestructive, i.e. not changing any field of self. | 
|  | def digest(self): | 
|  | m = (self.h + self.s) % 2**128 | 
|  | return m.to_bytes(16, byteorder='little') | 
|  |  | 
|  | def hash_init(alg): | 
|  | if alg == 'poly1305': | 
|  | # Use a fixed random key here, to present Poly1305 as an unkeyed hash. | 
|  | # This allows all the test cases for unkeyed hashes to work on Poly1305. | 
|  | return Poly1305(rand_bytes(POLY1305_KEY_SIZE)) | 
|  | return hashlib.new(alg) | 
|  |  | 
|  | def hash_update(ctx, data): | 
|  | ctx.update(data) | 
|  |  | 
|  | def hash_final(ctx): | 
|  | return ctx.digest() | 
|  |  | 
|  | def compute_hash(alg, data): | 
|  | ctx = hash_init(alg) | 
|  | hash_update(ctx, data) | 
|  | return hash_final(ctx) | 
|  |  | 
|  | def print_bytes(prefix, value, bytes_per_line): | 
|  | for i in range(0, len(value), bytes_per_line): | 
|  | line = prefix + ''.join(f'0x{b:02x}, ' for b in value[i:i+bytes_per_line]) | 
|  | print(f'{line.rstrip()}') | 
|  |  | 
|  | def print_static_u8_array_definition(name, value): | 
|  | print('') | 
|  | print(f'static const u8 {name} = {{') | 
|  | print_bytes('\t', value, 8) | 
|  | print('};') | 
|  |  | 
|  | def print_c_struct_u8_array_field(name, value): | 
|  | print(f'\t\t.{name} = {{') | 
|  | print_bytes('\t\t\t', value, 8) | 
|  | print('\t\t},') | 
|  |  | 
|  | def gen_unkeyed_testvecs(alg): | 
|  | print('') | 
|  | print('static const struct {') | 
|  | print('\tsize_t data_len;') | 
|  | print(f'\tu8 digest[{alg.upper()}_DIGEST_SIZE];') | 
|  | print('} hash_testvecs[] = {') | 
|  | for data_len in DATA_LENS: | 
|  | data = rand_bytes(data_len) | 
|  | print('\t{') | 
|  | print(f'\t\t.data_len = {data_len},') | 
|  | print_c_struct_u8_array_field('digest', compute_hash(alg, data)) | 
|  | print('\t},') | 
|  | print('};') | 
|  |  | 
|  | data = rand_bytes(4096) | 
|  | ctx = hash_init(alg) | 
|  | for data_len in range(len(data) + 1): | 
|  | hash_update(ctx, compute_hash(alg, data[:data_len])) | 
|  | print_static_u8_array_definition( | 
|  | f'hash_testvec_consolidated[{alg.upper()}_DIGEST_SIZE]', | 
|  | hash_final(ctx)) | 
|  |  | 
|  | def gen_hmac_testvecs(alg): | 
|  | ctx = hmac.new(rand_bytes(32), digestmod=alg) | 
|  | data = rand_bytes(4096) | 
|  | for data_len in range(len(data) + 1): | 
|  | ctx.update(data[:data_len]) | 
|  | key_len = data_len % 293 | 
|  | key = rand_bytes(key_len) | 
|  | mac = hmac.digest(key, data[:data_len], alg) | 
|  | ctx.update(mac) | 
|  | print_static_u8_array_definition( | 
|  | f'hmac_testvec_consolidated[{alg.upper()}_DIGEST_SIZE]', | 
|  | ctx.digest()) | 
|  |  | 
|  | def gen_additional_poly1305_testvecs(): | 
|  | key = b'\xff' * POLY1305_KEY_SIZE | 
|  | data = b'' | 
|  | ctx = Poly1305(key) | 
|  | for _ in range(32): | 
|  | for j in range(0, 4097, 16): | 
|  | ctx.update(b'\xff' * j) | 
|  | data += ctx.digest() | 
|  | print_static_u8_array_definition( | 
|  | 'poly1305_allones_macofmacs[POLY1305_DIGEST_SIZE]', | 
|  | Poly1305(key).update(data).digest()) | 
|  |  | 
|  | if len(sys.argv) != 2: | 
|  | sys.stderr.write('Usage: gen-hash-testvecs.py ALGORITHM\n') | 
|  | sys.stderr.write('ALGORITHM may be any supported by Python hashlib, or poly1305.\n') | 
|  | sys.stderr.write('Example: gen-hash-testvecs.py sha512\n') | 
|  | sys.exit(1) | 
|  |  | 
|  | alg = sys.argv[1] | 
|  | print('/* SPDX-License-Identifier: GPL-2.0-or-later */') | 
|  | print(f'/* This file was generated by: {sys.argv[0]} {" ".join(sys.argv[1:])} */') | 
|  | gen_unkeyed_testvecs(alg) | 
|  | if alg == 'poly1305': | 
|  | gen_additional_poly1305_testvecs() | 
|  | else: | 
|  | gen_hmac_testvecs(alg) |