214 lines
5.7 KiB
C
214 lines
5.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* libcryptsetup - cryptsetup library, cipher benchmark
|
|
*
|
|
* Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved.
|
|
* Copyright (C) 2012-2024 Milan Broz
|
|
*/
|
|
|
|
#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;
|
|
memset(buffer, 0, buffer_size);
|
|
|
|
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, priority;
|
|
const char *kdf_opt;
|
|
uint32_t memory_kb;
|
|
|
|
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);
|
|
|
|
memory_kb = pbkdf_adjusted_phys_memory_kb();
|
|
if (memory_kb < pbkdf->max_memory_kb) {
|
|
log_dbg(cd, "Not enough physical memory detected, "
|
|
"PBKDF max memory decreased from %dkB to %dkB.",
|
|
pbkdf->max_memory_kb, memory_kb);
|
|
pbkdf->max_memory_kb = memory_kb;
|
|
}
|
|
|
|
crypt_process_priority(cd, &priority, true);
|
|
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);
|
|
crypt_process_priority(cd, &priority, false);
|
|
|
|
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, "foobarfo", 8, "01234567890abcdef", 16,
|
|
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, "foobarfo", 8,
|
|
"0123456789abcdef0123456789abcdef", 32,
|
|
volume_key_size, &benchmark_callback, &u);
|
|
if (r < 0)
|
|
log_err(cd, _("Not compatible PBKDF options."));
|
|
}
|
|
|
|
return r;
|
|
}
|