diff options
Diffstat (limited to 'scripts/crypto')
| -rwxr-xr-x | scripts/crypto/gen-hash-testvecs.py | 147 | 
1 files changed, 147 insertions, 0 deletions
| diff --git a/scripts/crypto/gen-hash-testvecs.py b/scripts/crypto/gen-hash-testvecs.py new file mode 100755 index 000000000000..4ac927d40cf5 --- /dev/null +++ b/scripts/crypto/gen-hash-testvecs.py @@ -0,0 +1,147 @@ +#!/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) | 
