| #!/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) |