diff options
Diffstat (limited to 'lib/utils_benchmark.c')
-rw-r--r-- | lib/utils_benchmark.c | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/lib/utils_benchmark.c b/lib/utils_benchmark.c new file mode 100644 index 0000000..6b2efdc --- /dev/null +++ b/lib/utils_benchmark.c @@ -0,0 +1,215 @@ +/* + * libcryptsetup - cryptsetup library, cipher benchmark + * + * Copyright (C) 2012-2021 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2021 Milan Broz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <errno.h> + +#include "internal.h" + +int crypt_benchmark(struct crypt_device *cd, + const char *cipher, + const char *cipher_mode, + size_t volume_key_size, + size_t iv_size, + size_t buffer_size, + double *encryption_mbs, + double *decryption_mbs) +{ + void *buffer = NULL; + char *iv = NULL, *key = NULL, mode[MAX_CIPHER_LEN], *c; + int r; + + if (!cipher || !cipher_mode || !volume_key_size || !encryption_mbs || !decryption_mbs) + return -EINVAL; + + r = init_crypto(cd); + if (r < 0) + return r; + + r = -ENOMEM; + if (posix_memalign(&buffer, crypt_getpagesize(), buffer_size)) + goto out; + + r = crypt_cipher_ivsize(cipher, cipher_mode); + if (r >= 0 && iv_size != (size_t)r) { + log_dbg(cd, "IV length for benchmark adjusted to %i bytes (requested %zu).", r, iv_size); + iv_size = r; + } + + if (iv_size) { + iv = malloc(iv_size); + if (!iv) + goto out; + crypt_random_get(cd, iv, iv_size, CRYPT_RND_NORMAL); + } + + key = malloc(volume_key_size); + if (!key) + goto out; + + crypt_random_get(cd, key, volume_key_size, CRYPT_RND_NORMAL); + + strncpy(mode, cipher_mode, sizeof(mode)-1); + /* Ignore IV generator */ + if ((c = strchr(mode, '-'))) + *c = '\0'; + + r = crypt_cipher_perf_kernel(cipher, cipher_mode, buffer, buffer_size, key, volume_key_size, + iv, iv_size, encryption_mbs, decryption_mbs); + + if (r == -ERANGE) + log_dbg(cd, "Measured cipher runtime is too low."); + else if (r) + log_dbg(cd, "Cannot initialize cipher %s, mode %s, key size %zu, IV size %zu.", + cipher, cipher_mode, volume_key_size, iv_size); +out: + free(buffer); + free(key); + free(iv); + + return r; +} + +int crypt_benchmark_pbkdf(struct crypt_device *cd, + struct crypt_pbkdf_type *pbkdf, + const char *password, + size_t password_size, + const char *salt, + size_t salt_size, + size_t volume_key_size, + int (*progress)(uint32_t time_ms, void *usrptr), + void *usrptr) +{ + int r; + const char *kdf_opt; + + if (!pbkdf || (!password && password_size)) + return -EINVAL; + + r = init_crypto(cd); + if (r < 0) + return r; + + kdf_opt = !strcmp(pbkdf->type, CRYPT_KDF_PBKDF2) ? pbkdf->hash : ""; + + log_dbg(cd, "Running %s(%s) benchmark.", pbkdf->type, kdf_opt); + + r = crypt_pbkdf_perf(pbkdf->type, pbkdf->hash, password, password_size, + salt, salt_size, volume_key_size, pbkdf->time_ms, + pbkdf->max_memory_kb, pbkdf->parallel_threads, + &pbkdf->iterations, &pbkdf->max_memory_kb, progress, usrptr); + + if (!r) + log_dbg(cd, "Benchmark returns %s(%s) %u iterations, %u memory, %u threads (for %zu-bits key).", + pbkdf->type, kdf_opt, pbkdf->iterations, pbkdf->max_memory_kb, + pbkdf->parallel_threads, volume_key_size * 8); + return r; +} + +struct benchmark_usrptr { + struct crypt_device *cd; + struct crypt_pbkdf_type *pbkdf; +}; + +static int benchmark_callback(uint32_t time_ms, void *usrptr) +{ + struct benchmark_usrptr *u = usrptr; + + log_dbg(u->cd, "PBKDF benchmark: memory cost = %u, iterations = %u, " + "threads = %u (took %u ms)", u->pbkdf->max_memory_kb, + u->pbkdf->iterations, u->pbkdf->parallel_threads, time_ms); + + return 0; +} + +/* + * Used in internal places to benchmark crypt_device context PBKDF. + * Once requested parameters are benchmarked, iterations attribute is set, + * and the benchmarked values can be reused. + * Note that memory cost can be changed after benchmark (if used). + * NOTE: You need to check that you are benchmarking for the same key size. + */ +int crypt_benchmark_pbkdf_internal(struct crypt_device *cd, + struct crypt_pbkdf_type *pbkdf, + size_t volume_key_size) +{ + struct crypt_pbkdf_limits pbkdf_limits; + double PBKDF2_tmp; + uint32_t ms_tmp; + int r = -EINVAL; + struct benchmark_usrptr u = { + .cd = cd, + .pbkdf = pbkdf + }; + + r = crypt_pbkdf_get_limits(pbkdf->type, &pbkdf_limits); + if (r) + return r; + + if (pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK) { + if (pbkdf->iterations) { + log_dbg(cd, "Reusing PBKDF values (no benchmark flag is set)."); + return 0; + } + log_err(cd, _("PBKDF benchmark disabled but iterations not set.")); + return -EINVAL; + } + + /* For PBKDF2 run benchmark always. Also note it depends on volume_key_size! */ + if (!strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) { + /* + * For PBKDF2 it is enough to run benchmark for only 1 second + * and interpolate final iterations value from it. + */ + ms_tmp = pbkdf->time_ms; + pbkdf->time_ms = 1000; + pbkdf->parallel_threads = 0; /* N/A in PBKDF2 */ + pbkdf->max_memory_kb = 0; /* N/A in PBKDF2 */ + + r = crypt_benchmark_pbkdf(cd, pbkdf, "foo", 3, "bar", 3, + volume_key_size, &benchmark_callback, &u); + pbkdf->time_ms = ms_tmp; + if (r < 0) { + log_err(cd, _("Not compatible PBKDF2 options (using hash algorithm %s)."), + pbkdf->hash); + return r; + } + + PBKDF2_tmp = ((double)pbkdf->iterations * pbkdf->time_ms / 1000.); + if (PBKDF2_tmp > (double)UINT32_MAX) + return -EINVAL; + pbkdf->iterations = at_least((uint32_t)PBKDF2_tmp, pbkdf_limits.min_iterations); + } else { + /* Already benchmarked */ + if (pbkdf->iterations) { + log_dbg(cd, "Reusing PBKDF values."); + return 0; + } + + r = crypt_benchmark_pbkdf(cd, pbkdf, "foo", 3, + "0123456789abcdef0123456789abcdef", 32, + volume_key_size, &benchmark_callback, &u); + if (r < 0) + log_err(cd, _("Not compatible PBKDF options.")); + } + + return r; +} |