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