From 8a241661c76e30d58e95654322d0d674b1d16a51 Mon Sep 17 00:00:00 2001 From: Andrew Dinh Date: Thu, 29 Jan 2026 18:08:07 +0700 Subject: [PATCH] Add evp_kdf perftool --- README.md | 24 ++++ source/CMakeLists.txt | 8 ++ source/evp_kdf.c | 288 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 source/evp_kdf.c diff --git a/README.md b/README.md index 69a0981..038751f 100644 --- a/README.md +++ b/README.md @@ -283,3 +283,27 @@ thread-count - number of threads ```sh evp_mac -o evp_shared 10 ``` + +## evp_kdf + +This CLI tool derives keys from random data with SHA-256 digest. +Runs for 5 seconds and prints the average execution time per computation. + +Four modes of operation: +- evp_shared (default): Use EVP API and allow shared data between computations +- evp_isolated: Use EVP API and don't allow shared data between computations +- deprecated_shared: Use legacy API and allow shared data between computations +- deprecated_isolated: Use legacy API and don't allow shared data between computations + +``` +Usage: evp_kdf [-h] [-t] [-o operation] [-V] thread-count +-h - print this help output +-t - terse output +-o operation - mode of operation. One of [evp_isolated, evp_shared, deprecated_isolated, deprecated_shared] (default: evp_shared) +-V - print version information and exit +thread-count - number of threads +``` + +```sh +./evp_kdf -o evp_shared 10 +``` diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 10a332c..d0d27ec 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -215,12 +215,16 @@ target_link_libraries(evp_cipher PRIVATE perf) add_executable(evp_mac evp_mac.c) target_link_libraries(evp_mac PRIVATE perf) +add_executable(evp_kdf evp_kdf.c) +target_link_libraries(evp_kdf PRIVATE perf) + ## Running tests # Options set(run_tests evp_fetch evp_hash evp_cipher evp_mac + evp_kdf evp_setpeer handshake newrawkey @@ -261,6 +265,9 @@ set(run_evp_cipher_algorithms set(run_evp_mac_operations evp_mac "" "" "-o deprecated_isolated" "-o deprecated_shared" "-o evp_isolated" "-o evp_shared" CACHE STRING "Modes of operation for evp_mac") +set(run_evp_kdf_operations + evp_kdf "" "" "-o evp_shared" "-o evp_isolated" "-o deprecated_shared" "-o deprecated_isolated" + CACHE STRING "Modes of operation for evp_kdf") set(run_evp_setpeer_keys evp_setpeer "-k" dh ec256 ec521 x25519 all CACHE STRING "Key types for evp_setpeer") @@ -303,6 +310,7 @@ set(run_opts run_evp_fetch_pqs run_evp_cipher_operations run_evp_cipher_algorithms run_evp_mac_operations + run_evp_kdf_operations run_evp_setpeer_keys run_newrawkey_algos run_pkeyread_keys diff --git a/source/evp_kdf.c b/source/evp_kdf.c new file mode 100644 index 0000000..ea38e7f --- /dev/null +++ b/source/evp_kdf.c @@ -0,0 +1,288 @@ +/* + * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * This CLI tool derives keys from random data with SHA-256 digest. + * Runs for 5 seconds and prints the average execution time per computation. + */ + +#define OPENSSL_SUPPRESS_DEPRECATED + +#include +#include +#ifndef _WIN32 +# include +#else +# include "perflib/getopt.h" +#endif /* _WIN32 */ + +#include +#include +#include +#include +#include "perflib/perflib.h" +#include "perflib/basename.h" + +#define RUN_TIME 5 +#define KEY_SIZE 256 + +static int threadcount; +static OSSL_TIME max_time; + +size_t *counts = NULL; +int run_err = 0; + +typedef enum { + EVP_SHARED = 0, + EVP_ISOLATED, + DEPRECATED_SHARED, + DEPRECATED_ISOLATED, +} operation_type; + +static unsigned char pass[32]; +static unsigned char salt[32]; +static unsigned int iterations = 2048; + +static int evp_isolated() +{ + int ret = 0; + unsigned char key[KEY_SIZE]; + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "PBKDF2", NULL); + EVP_KDF_CTX *ctx = NULL; + OSSL_PARAM params[] = { + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, pass, sizeof(pass)), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, salt, sizeof(salt)), + OSSL_PARAM_construct_uint(OSSL_KDF_PARAM_ITER, &iterations), + OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, "SHA256", 0), + OSSL_PARAM_construct_end() + }; + + if (kdf == NULL + || (ctx = EVP_KDF_CTX_new(kdf)) == NULL + || !EVP_KDF_derive(ctx, key, sizeof(key), params)) + goto err; + + ret = 1; +err: + EVP_KDF_CTX_free(ctx); + EVP_KDF_free(kdf); + return ret; +} + +static void do_evp_isolated(size_t num) +{ + OSSL_TIME time; + size_t count = 0; + + do { + if (!evp_isolated()) { + run_err = 1; + return; + } + + count++; + time = ossl_time_now(); + } while (time.t < max_time.t); + + counts[num] = count; +} + +static void do_evp_shared(size_t num) +{ + OSSL_TIME time; + size_t count = 0; + unsigned char key[KEY_SIZE]; + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "PBKDF2", NULL); + EVP_KDF_CTX *ctx = NULL; + OSSL_PARAM params[] = { + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, pass, sizeof(pass)), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, salt, sizeof(salt)), + OSSL_PARAM_construct_uint(OSSL_KDF_PARAM_ITER, &iterations), + OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, "SHA256", 0), + OSSL_PARAM_construct_end() + }; + + if (kdf == NULL || (ctx = EVP_KDF_CTX_new(kdf)) == NULL) + goto err; + + do { + if (!EVP_KDF_derive(ctx, key, sizeof(key), params)) { + run_err = 1; + goto err; + } + EVP_KDF_CTX_reset(ctx); + + count++; + time = ossl_time_now(); + } while (time.t < max_time.t); + counts[num] = count; + +err: + EVP_KDF_CTX_free(ctx); + EVP_KDF_free(kdf); +} + +static void do_deprecated_isolated(size_t num) +{ + OSSL_TIME time; + size_t count = 0; + unsigned char key[KEY_SIZE]; + + do { + if (!PKCS5_PBKDF2_HMAC((const char *)pass, sizeof(pass), salt, + sizeof(salt), iterations, EVP_sha256(), sizeof(key), key)) { + run_err = 1; + return; + } + + count++; + time = ossl_time_now(); + } while (time.t < max_time.t); + counts[num] = count; +} + +static void do_deprecated_shared(size_t num) +{ + OSSL_TIME time; + size_t count = 0; + unsigned char key[KEY_SIZE]; + const EVP_MD *md = EVP_sha256(); + + do { + if (!PKCS5_PBKDF2_HMAC((const char *)pass, sizeof(pass), salt, + sizeof(salt), iterations, md, sizeof(key), key)) { + run_err = 1; + return; + } + + count++; + time = ossl_time_now(); + } while (time.t < max_time.t); + counts[num] = count; +} + +static void print_help(FILE *file) +{ + fprintf(file, "Usage: evp_kdf [-h] [-t] [-o operation] [-V] thread-count\n"); + fprintf(file, "-h - print this help output\n"); + fprintf(file, "-t - terse output\n"); + fprintf(file, "-o operation - mode of operation. One of [evp_isolated, evp_shared, deprecated_isolated, deprecated_shared] (default: evp_shared)\n"); + fprintf(file, "-V - print version information and exit\n"); + fprintf(file, "thread-count - number of threads\n"); +} + +int main(int argc, char *argv[]) +{ + OSSL_TIME duration; + size_t total_count = 0; + double av; + int terse = 0, operation = EVP_SHARED; + int j, opt, rc = EXIT_FAILURE; + + while ((opt = getopt(argc, argv, "Vhto:")) != -1) { + switch (opt) { + case 't': + terse = 1; + break; + case 'o': + if (strcmp(optarg, "evp_isolated") == 0) { + operation = EVP_ISOLATED; + } else if (strcmp(optarg, "evp_shared") == 0) { + operation = EVP_SHARED; + } else if (strcmp(optarg, "deprecated_isolated") == 0) { + operation = DEPRECATED_ISOLATED; + } else if (strcmp(optarg, "deprecated_shared") == 0) { + operation = DEPRECATED_SHARED; + } else { + fprintf(stderr, "Invalid operation"); + print_help(stderr); + goto err; + } + break; + case 'V': + perflib_print_version(basename(argv[0])); + return EXIT_SUCCESS; + case 'h': + print_help(stdout); + return EXIT_SUCCESS; + default: + print_help(stderr); + goto err; + } + } + + if (argc - optind != 1) { + fprintf(stderr, "Incorrect number of arguments\n"); + print_help(stderr); + goto err; + } + + threadcount = atoi(argv[optind]); + if (threadcount < 1) { + fprintf(stderr, "thread-count must be a positive integer\n"); + print_help(stderr); + goto err; + } + + if (!RAND_bytes((unsigned char *)pass, sizeof(pass)) + || !RAND_bytes((unsigned char *)salt, sizeof(salt))) + goto err; + + counts = OPENSSL_zalloc(sizeof(size_t) * threadcount); + if (counts == NULL) { + fprintf(stderr, "Failed to create counts array\n"); + goto err; + } + + max_time = ossl_time_add(ossl_time_now(), ossl_seconds2time(RUN_TIME)); + + switch (operation) { + case EVP_SHARED: + run_err = !perflib_run_multi_thread_test(do_evp_shared, threadcount, &duration) || run_err; + break; + case EVP_ISOLATED: + run_err = !perflib_run_multi_thread_test(do_evp_isolated, threadcount, &duration) || run_err; + break; + case DEPRECATED_SHARED: + run_err = !perflib_run_multi_thread_test(do_deprecated_shared, threadcount, &duration) || run_err; + break; + case DEPRECATED_ISOLATED: + run_err = !perflib_run_multi_thread_test(do_deprecated_isolated, threadcount, &duration) || run_err; + break; + default: + goto err; + } + + if (run_err) { + fprintf(stderr, "Error during test\n"); + goto err; + } + + for (j = 0; j < threadcount; j++) + total_count += counts[j]; + + /* + * Computation is pretty fast, running in only a few us. But ossl_time2us + * does integer division and so because the average us computed above is + * less than the value of OSSL_TIME_US, we wind up with truncation to zero + * in the math. Instead, manually do the division, casting our values as + * doubles so that we compute the proper time. + */ + av = (double)RUN_TIME * 1e6 * threadcount / total_count; + + if (terse) + printf("%lf\n", av); + else + printf("Average time per computation: %lfus\n", av); + + rc = EXIT_SUCCESS; +err: + OPENSSL_free(counts); + return rc; +}