1
0
Fork 0
cryptsetup/lib/crypto_backend/pbkdf_check.c
Daniel Baumann 309c0fd158
Adding upstream version 2:2.7.5.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 10:45:47 +02:00

430 lines
11 KiB
C

// SPDX-License-Identifier: LGPL-2.1-or-later
/*
* PBKDF performance check
* Copyright (C) 2012-2024 Red Hat, Inc. All rights reserved.
* Copyright (C) 2012-2024 Milan Broz
* Copyright (C) 2016-2020 Ondrej Mosnacek
*/
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <time.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "crypto_backend.h"
#ifndef CLOCK_MONOTONIC_RAW
#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC
#endif
#define BENCH_MIN_MS 250
#define BENCH_MIN_MS_FAST 10
#define BENCH_PERCENT_ATLEAST 95
#define BENCH_PERCENT_ATMOST 110
#define BENCH_SAMPLES_FAST 3
#define BENCH_SAMPLES_SLOW 1
/* These PBKDF2 limits must be never violated */
int crypt_pbkdf_get_limits(const char *kdf, struct crypt_pbkdf_limits *limits)
{
if (!kdf || !limits)
return -EINVAL;
if (!strcmp(kdf, "pbkdf2")) {
limits->min_iterations = 1000; /* recommendation in NIST SP 800-132 */
limits->max_iterations = UINT32_MAX;
limits->min_memory = 0; /* N/A */
limits->min_bench_memory=0; /* N/A */
limits->max_memory = 0; /* N/A */
limits->min_parallel = 0; /* N/A */
limits->max_parallel = 0; /* N/A */
return 0;
} else if (!strcmp(kdf, "argon2i") || !strcmp(kdf, "argon2id")) {
limits->min_iterations = 4;
limits->max_iterations = UINT32_MAX;
limits->min_memory = 32; /* hard limit */
limits->min_bench_memory=64*1024; /* 64 MiB minimum for benchmark */
limits->max_memory = 4*1024*1024; /* 4GiB */
limits->min_parallel = 1;
limits->max_parallel = 4;
return 0;
}
return -EINVAL;
}
static long time_ms(struct rusage *start, struct rusage *end)
{
int count_kernel_time = 0;
long ms;
if (crypt_backend_flags() & CRYPT_BACKEND_KERNEL)
count_kernel_time = 1;
/*
* If there is no self usage info, count system time.
* This seem like getrusage() bug in some hypervisors...
*/
if (!end->ru_utime.tv_sec && !start->ru_utime.tv_sec &&
!end->ru_utime.tv_usec && !start->ru_utime.tv_usec)
count_kernel_time = 1;
ms = (end->ru_utime.tv_sec - start->ru_utime.tv_sec) * 1000;
ms += (end->ru_utime.tv_usec - start->ru_utime.tv_usec) / 1000;
if (count_kernel_time) {
ms += (end->ru_stime.tv_sec - start->ru_stime.tv_sec) * 1000;
ms += (end->ru_stime.tv_usec - start->ru_stime.tv_usec) / 1000;
}
return ms;
}
static long timespec_ms(struct timespec *start, struct timespec *end)
{
return (end->tv_sec - start->tv_sec) * 1000 +
(end->tv_nsec - start->tv_nsec) / (1000 * 1000);
}
static int measure_argon2(const char *kdf, const char *password, size_t password_length,
const char *salt, size_t salt_length,
char *key, size_t key_length,
uint32_t t_cost, uint32_t m_cost, uint32_t parallel,
size_t samples, long ms_atleast, long *out_ms)
{
long ms, ms_min = LONG_MAX;
int r;
size_t i;
for (i = 0; i < samples; i++) {
struct timespec tstart, tend;
/*
* NOTE: We must use clock_gettime here, because Argon2 can run over
* multiple threads, and thus we care about real time, not CPU time!
*/
if (clock_gettime(CLOCK_MONOTONIC_RAW, &tstart) < 0)
return -EINVAL;
r = crypt_pbkdf(kdf, NULL, password, password_length, salt,
salt_length, key, key_length, t_cost, m_cost, parallel);
if (r < 0)
return r;
if (clock_gettime(CLOCK_MONOTONIC_RAW, &tend) < 0)
return -EINVAL;
ms = timespec_ms(&tstart, &tend);
if (ms < 0)
return -EINVAL;
if (ms < ms_atleast) {
/* early exit */
ms_min = ms;
break;
}
if (ms < ms_min) {
ms_min = ms;
}
}
*out_ms = ms_min;
return 0;
}
#define CONTINUE 0
#define FINAL 1
static int next_argon2_params(uint32_t *t_cost, uint32_t *m_cost,
uint32_t min_t_cost, uint32_t min_m_cost,
uint32_t max_m_cost, long ms, uint32_t target_ms)
{
uint32_t old_t_cost, old_m_cost, new_t_cost, new_m_cost;
uint64_t num, denom;
old_t_cost = *t_cost;
old_m_cost = *m_cost;
if ((uint32_t)ms > target_ms) {
/* decreasing, first try to lower t_cost, then m_cost */
num = (uint64_t)*t_cost * (uint64_t)target_ms;
denom = (uint64_t)ms;
new_t_cost = (uint32_t)(num / denom);
if (new_t_cost < min_t_cost) {
num = (uint64_t)*t_cost * (uint64_t)*m_cost *
(uint64_t)target_ms;
denom = (uint64_t)min_t_cost * (uint64_t)ms;
*t_cost = min_t_cost;
*m_cost = (uint32_t)(num / denom);
if (*m_cost < min_m_cost) {
*m_cost = min_m_cost;
return FINAL;
}
} else {
*t_cost = new_t_cost;
}
} else {
/* increasing, first try to increase m_cost, then t_cost */
num = (uint64_t)*m_cost * (uint64_t)target_ms;
denom = (uint64_t)ms;
new_m_cost = (uint32_t)(num / denom);
if (new_m_cost > max_m_cost) {
num = (uint64_t)*t_cost * (uint64_t)*m_cost *
(uint64_t)target_ms;
denom = (uint64_t)max_m_cost * (uint64_t)ms;
*t_cost = (uint32_t)(num / denom);
*m_cost = max_m_cost;
if (*t_cost <= min_t_cost) {
*t_cost = min_t_cost;
return FINAL;
}
} else if (new_m_cost < min_m_cost) {
*m_cost = min_m_cost;
return FINAL;
} else {
*m_cost = new_m_cost;
}
}
/* do not continue if it is the same as in the previous run */
if (old_t_cost == *t_cost && old_m_cost == *m_cost)
return FINAL;
return CONTINUE;
}
static int crypt_argon2_check(const char *kdf, const char *password,
size_t password_length, const char *salt,
size_t salt_length, size_t key_length,
uint32_t min_t_cost, uint32_t min_m_cost, uint32_t max_m_cost,
uint32_t parallel, uint32_t target_ms,
uint32_t *out_t_cost, uint32_t *out_m_cost,
int (*progress)(uint32_t time_ms, void *usrptr),
void *usrptr)
{
int r = 0;
char *key = NULL;
uint32_t t_cost, m_cost;
long ms;
long ms_atleast = (long)target_ms * BENCH_PERCENT_ATLEAST / 100;
long ms_atmost = (long)target_ms * BENCH_PERCENT_ATMOST / 100;
if (key_length <= 0 || target_ms <= 0)
return -EINVAL;
if (min_m_cost < (parallel * 8))
min_m_cost = parallel * 8;
if (max_m_cost < min_m_cost)
return -EINVAL;
key = malloc(key_length);
if (!key)
return -ENOMEM;
t_cost = min_t_cost;
m_cost = min_m_cost;
/* 1. Find some small parameters, s. t. ms >= BENCH_MIN_MS: */
while (1) {
r = measure_argon2(kdf, password, password_length, salt, salt_length,
key, key_length, t_cost, m_cost, parallel,
BENCH_SAMPLES_FAST, BENCH_MIN_MS, &ms);
if (!r) {
/* Update parameters to actual measurement */
*out_t_cost = t_cost;
*out_m_cost = m_cost;
if (progress && progress((uint32_t)ms, usrptr))
r = -EINTR;
}
if (r < 0)
goto out;
if (ms >= BENCH_MIN_MS)
break;
if (m_cost == max_m_cost) {
if (ms < BENCH_MIN_MS_FAST)
t_cost *= 16;
else {
uint32_t new = (t_cost * BENCH_MIN_MS) / (uint32_t)ms;
if (new == t_cost)
break;
t_cost = new;
}
} else {
if (ms < BENCH_MIN_MS_FAST)
m_cost *= 16;
else {
uint32_t new = (m_cost * BENCH_MIN_MS) / (uint32_t)ms;
if (new == m_cost)
break;
m_cost = new;
}
if (m_cost > max_m_cost) {
m_cost = max_m_cost;
}
}
}
/*
* 2. Use the params obtained in (1.) to estimate the target params.
* 3. Then repeatedly measure the candidate params and if they fall out of
* the acceptance range (+-5 %), try to improve the estimate:
*/
do {
if (next_argon2_params(&t_cost, &m_cost, min_t_cost, min_m_cost,
max_m_cost, ms, target_ms)) {
/* Update parameters to final computation */
*out_t_cost = t_cost;
*out_m_cost = m_cost;
break;
}
r = measure_argon2(kdf, password, password_length, salt, salt_length,
key, key_length, t_cost, m_cost, parallel,
BENCH_SAMPLES_SLOW, ms_atleast, &ms);
if (!r) {
/* Update parameters to actual measurement */
*out_t_cost = t_cost;
*out_m_cost = m_cost;
if (progress && progress((uint32_t)ms, usrptr))
r = -EINTR;
}
if (r < 0)
break;
} while (ms < ms_atleast || ms > ms_atmost);
out:
if (key) {
crypt_backend_memzero(key, key_length);
free(key);
}
return r;
}
/* This code benchmarks PBKDF and returns iterations/second using specified hash */
static int crypt_pbkdf_check(const char *kdf, const char *hash,
const char *password, size_t password_length,
const char *salt, size_t salt_length,
size_t key_length, uint32_t *iter_secs, uint32_t target_ms,
int (*progress)(uint32_t time_ms, void *usrptr), void *usrptr)
{
struct rusage rstart, rend;
int r = 0, step = 0;
long ms = 0;
char *key = NULL;
uint32_t iterations;
double PBKDF2_temp;
if (!kdf || !hash || key_length <= 0)
return -EINVAL;
key = malloc(key_length);
if (!key)
return -ENOMEM;
*iter_secs = 0;
iterations = 1 << 15;
while (1) {
if (getrusage(RUSAGE_SELF, &rstart) < 0) {
r = -EINVAL;
goto out;
}
r = crypt_pbkdf(kdf, hash, password, password_length, salt,
salt_length, key, key_length, iterations, 0, 0);
if (r < 0)
goto out;
if (getrusage(RUSAGE_SELF, &rend) < 0) {
r = -EINVAL;
goto out;
}
ms = time_ms(&rstart, &rend);
if (ms) {
PBKDF2_temp = (double)iterations * target_ms / ms;
if (PBKDF2_temp > UINT32_MAX) {
r = -EINVAL;
goto out;
}
*iter_secs = (uint32_t)PBKDF2_temp;
}
if (progress && progress((uint32_t)ms, usrptr)) {
r = -EINTR;
goto out;
}
if (ms > 500)
break;
if (ms <= 62)
iterations <<= 4;
else if (ms <= 125)
iterations <<= 3;
else if (ms <= 250)
iterations <<= 2;
else
iterations <<= 1;
if (++step > 10 || !iterations) {
r = -EINVAL;
goto out;
}
}
out:
if (key) {
crypt_backend_memzero(key, key_length);
free(key);
}
return r;
}
int crypt_pbkdf_perf(const char *kdf, const char *hash,
const char *password, size_t password_size,
const char *salt, size_t salt_size,
size_t volume_key_size, uint32_t time_ms,
uint32_t max_memory_kb, uint32_t parallel_threads,
uint32_t *iterations_out, uint32_t *memory_out,
int (*progress)(uint32_t time_ms, void *usrptr), void *usrptr)
{
struct crypt_pbkdf_limits pbkdf_limits;
int r = -EINVAL;
uint32_t min_memory;
if (!kdf || !iterations_out || !memory_out)
return -EINVAL;
r = crypt_pbkdf_get_limits(kdf, &pbkdf_limits);
if (r < 0)
return r;
min_memory = pbkdf_limits.min_bench_memory;
if (min_memory > max_memory_kb)
min_memory = max_memory_kb;
*memory_out = 0;
*iterations_out = 0;
if (!strcmp(kdf, "pbkdf2"))
r = crypt_pbkdf_check(kdf, hash, password, password_size,
salt, salt_size, volume_key_size,
iterations_out, time_ms, progress, usrptr);
else if (!strncmp(kdf, "argon2", 6))
r = crypt_argon2_check(kdf, password, password_size,
salt, salt_size, volume_key_size,
pbkdf_limits.min_iterations,
min_memory,
max_memory_kb,
parallel_threads, time_ms, iterations_out,
memory_out, progress, usrptr);
return r;
}