mirror of
https://gitlab.com/cryptsetup/cryptsetup.git
synced 2025-12-05 16:00:05 +01:00
Add Argon2 benchmark code.
Code based on patch by Ondrej Mosnacek The new benchmark works as follows: Phase 1: It searches for smallest parameters, such that the duration is 250 ms (this part is quite fast). Then it uses that data point to estimate the paramters that will have the desired duration (and fulfill the basic constraints). Phase 2: The candidate parameters are then measured and if their duration falls within +-5% of the target duration, they are accepted. Otherwise, new candidate parameters are estimated based on the last measurement and phase 2 is repeated. When measuring the duration for given parameters, the measurement is repeated 3 or 4 times and a minimum of the measured durations is used as the final duration (to reduce variance in measurements). A minimum is taken instead of mean, because the measurements definitely have a certain lower bound, but no upper bound (therefore mean value would tend to be higher than the value with highest probability density). The actual "most likely" duration is going to be somewhere just above the minimum measurable value, so minimum over the observations is a better estimate than mean. Signed-off-by: Milan Broz <gmazyland@gmail.com>
This commit is contained in:
@@ -57,15 +57,17 @@ enum { CRYPT_RND_NORMAL = 0, CRYPT_RND_KEY = 1, CRYPT_RND_SALT = 2 };
|
||||
int crypt_backend_rng(char *buffer, size_t length, int quality, int fips);
|
||||
|
||||
/* PBKDF*/
|
||||
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);
|
||||
int crypt_pbkdf(const char *kdf, const char *hash,
|
||||
const char *password, size_t password_length,
|
||||
const char *salt, size_t salt_length,
|
||||
char *key, size_t key_length,
|
||||
uint32_t iterations, uint32_t memory, uint32_t parallel);
|
||||
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);
|
||||
|
||||
#if USE_INTERNAL_PBKDF2
|
||||
/* internal PBKDF2 implementation */
|
||||
|
||||
@@ -20,10 +20,25 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include "crypto_backend.h"
|
||||
|
||||
//#define BENCH_DEBUG
|
||||
|
||||
#ifdef BENCH_DEBUG
|
||||
#include <stdio.h> /* FIXME: debug */
|
||||
#define bench_log(args...) fprintf(stderr, args)
|
||||
#else
|
||||
#define bench_log(args...)
|
||||
#endif
|
||||
|
||||
#define BENCH_MIN_MS 250
|
||||
#define BENCH_PERCENT_ATLEAST 95
|
||||
#define BENCH_PERCENT_ATMOST 105
|
||||
|
||||
static long time_ms(struct rusage *start, struct rusage *end)
|
||||
{
|
||||
int count_kernel_time = 0;
|
||||
@@ -51,8 +66,168 @@ static long time_ms(struct rusage *start, struct rusage *end)
|
||||
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;
|
||||
}
|
||||
|
||||
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 max_m_cost, uint32_t parallel,
|
||||
int target_ms, uint32_t *out_t_cost, uint32_t *out_m_cost)
|
||||
{
|
||||
int r = 0;
|
||||
char *key = NULL;
|
||||
uint32_t t_cost, m_cost, min_m_cost = 8 * parallel;
|
||||
uint64_t num, denom;
|
||||
long ms;
|
||||
long ms_atleast = (long)target_ms * BENCH_PERCENT_ATLEAST / 100;
|
||||
long ms_atmost = (long)target_ms * BENCH_PERCENT_ATMOST / 100;
|
||||
struct timespec tstart, tend;
|
||||
|
||||
if (key_length <= 0 || target_ms <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (max_m_cost < min_m_cost)
|
||||
return -EINVAL;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &tstart);
|
||||
|
||||
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,
|
||||
3, BENCH_MIN_MS, &ms);
|
||||
if (r < 0)
|
||||
goto out;
|
||||
|
||||
bench_log("Pre-initial parameters: t_cost = %lu; m_cost = %lu; ms = %lu\n",
|
||||
(long unsigned)t_cost, (long unsigned)m_cost, ms);
|
||||
|
||||
if (ms >= BENCH_MIN_MS)
|
||||
break;
|
||||
|
||||
if (m_cost == max_m_cost) {
|
||||
t_cost = ms ? (t_cost * BENCH_MIN_MS) / (uint32_t)ms : t_cost * 16;
|
||||
} else {
|
||||
m_cost = ms ? (m_cost * BENCH_MIN_MS) / (uint32_t)ms : m_cost * 16;
|
||||
if (m_cost > max_m_cost) {
|
||||
m_cost = max_m_cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
bench_log("Initial parameters: t_cost = %lu; m_cost = %lu; ms = %lu\n",
|
||||
(long unsigned)t_cost, (long unsigned)m_cost, ms);
|
||||
|
||||
/*
|
||||
* 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 {
|
||||
uint32_t new_m_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;
|
||||
break;
|
||||
}
|
||||
} else if (new_m_cost < min_m_cost) {
|
||||
m_cost = min_m_cost;
|
||||
break;
|
||||
} else {
|
||||
m_cost = new_m_cost;
|
||||
}
|
||||
|
||||
r = measure_argon2(kdf, password, password_length, salt, salt_length,
|
||||
key, key_length, t_cost, m_cost, parallel,
|
||||
4, ms_atleast, &ms);
|
||||
if (r < 0)
|
||||
goto out;
|
||||
|
||||
bench_log("Candidate parameters: t_cost = %lu; m_cost = %lu; ms = %lu\n",
|
||||
(long unsigned)t_cost, (long unsigned)m_cost, ms);
|
||||
} while(ms < ms_atleast || ms > ms_atmost);
|
||||
|
||||
bench_log("Accepted parameters: t_cost = %lu; m_cost = %lu\n",
|
||||
(long unsigned)t_cost, (long unsigned)m_cost);
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &tend);
|
||||
|
||||
bench_log("Benchmark took: %ld ms\n", timespec_ms(&tstart, &tend));
|
||||
|
||||
*out_t_cost = t_cost;
|
||||
*out_m_cost = m_cost;
|
||||
out:
|
||||
if (key) {
|
||||
crypt_backend_memzero(key, key_length);
|
||||
free(key);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/* This code benchmarks PBKDF and returns iterations/second using specified hash */
|
||||
int crypt_pbkdf_check(const char *kdf, const char *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)
|
||||
@@ -115,3 +290,39 @@ out:
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
#define ARGON2_MIN_T_COST 4
|
||||
|
||||
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)
|
||||
{
|
||||
uint32_t iterations_sec;
|
||||
int r;
|
||||
|
||||
if (!kdf)
|
||||
return -EINVAL;
|
||||
|
||||
if (!strcmp(kdf, "pbkdf2")) {
|
||||
if (!iterations_out || memory_out)
|
||||
return -EINVAL;
|
||||
|
||||
r = crypt_pbkdf_check(kdf, hash, password, password_size,
|
||||
salt, salt_size, volume_key_size, &iterations_sec);
|
||||
|
||||
*iterations_out = (uint32_t)((uint64_t)iterations_sec * (uint64_t)time_ms / 1000);
|
||||
} else if (!strncmp(kdf, "argon2", 6)) {
|
||||
if (!iterations_out || !memory_out)
|
||||
return -EINVAL;
|
||||
|
||||
r = crypt_argon2_check(kdf, password, password_size, salt, salt_size,
|
||||
volume_key_size, ARGON2_MIN_T_COST, max_memory_kb,
|
||||
parallel_threads, time_ms, iterations_out, memory_out);
|
||||
} else
|
||||
r = -EINVAL;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -241,26 +241,26 @@ int crypt_benchmark_pbkdf(struct crypt_device *cd,
|
||||
uint32_t *iterations,
|
||||
uint32_t *memory)
|
||||
{
|
||||
uint32_t iterations_sec;
|
||||
int r;
|
||||
|
||||
r = init_crypto(cd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) {
|
||||
if (!iterations || memory)
|
||||
return -EINVAL;
|
||||
/* Hack to not print hash for argon, it is used also for AF later.*/
|
||||
if (pbkdf->hash && !memory)
|
||||
log_dbg("Running %s-%s benchmark.", pbkdf->type, pbkdf->hash);
|
||||
else
|
||||
log_dbg("Running %s benchmark.", pbkdf->type);
|
||||
|
||||
r = crypt_pbkdf_check(pbkdf->type, pbkdf->hash, password, password_size,
|
||||
salt, salt_size, volume_key_size, &iterations_sec);
|
||||
|
||||
*iterations = (uint32_t)((uint64_t)iterations_sec * (uint64_t)pbkdf->time_ms / 1000);
|
||||
if (!r)
|
||||
log_dbg("PBKDF2 benchmark, hash %s: %u iterations per second (%zu-bits key).",
|
||||
pbkdf->hash, iterations_sec, volume_key_size * 8);
|
||||
} else
|
||||
r = -EINVAL;
|
||||
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,
|
||||
iterations, memory);
|
||||
|
||||
if (!r)
|
||||
log_dbg(" %u iterations, %u memory (for %zu-bits key).",
|
||||
iterations ? *iterations : 0, memory ? *memory : 0,
|
||||
volume_key_size * 8);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ static int opt_veracrypt = 0;
|
||||
static int opt_veracrypt_pim = -1;
|
||||
static int opt_veracrypt_query_pim = 0;
|
||||
static int opt_deferred_remove = 0;
|
||||
static const char *opt_pbkdf = CRYPT_KDF_PBKDF2;
|
||||
static long opt_pbkdf_memory = 1024;
|
||||
static long opt_pbkdf_parallel = 2;
|
||||
|
||||
static const char **action_argv;
|
||||
static int action_argc;
|
||||
@@ -548,9 +551,26 @@ static int action_benchmark_kdf(const char *kdf, const char *hash, size_t key_si
|
||||
log_std("PBKDF2-%-9s %7u iterations per second for %zu-bit key\n",
|
||||
hash, kdf_iters, key_size * 8);
|
||||
} else {
|
||||
log_err("Unknown PBKDF '%s'\n", kdf);
|
||||
r = -EINVAL;
|
||||
const struct crypt_pbkdf_type pbkdf = {
|
||||
.type = kdf,
|
||||
.time_ms = opt_iteration_time ?: 800,
|
||||
.max_memory_kb = opt_pbkdf_memory,
|
||||
.parallel_threads = opt_pbkdf_parallel,
|
||||
};
|
||||
uint32_t iters, memory;
|
||||
|
||||
r = crypt_benchmark_pbkdf(NULL, &pbkdf, "foo", 3,
|
||||
"barbarbarbarbarbar", 18, key_size, &iters, &memory);
|
||||
if (r < 0)
|
||||
log_std("%-10s N/A\n", kdf);
|
||||
else
|
||||
log_std("%-10s %4u iterations, %5u memory, "
|
||||
"%1u parallel threads (CPUs) for "
|
||||
"%zu-bit key (%u ms time)\n", kdf,
|
||||
iters, memory, pbkdf.parallel_threads,
|
||||
key_size * 8, (unsigned)pbkdf.time_ms);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -610,7 +630,9 @@ static int action_benchmark(void)
|
||||
int i, r;
|
||||
|
||||
log_std(_("# Tests are approximate using memory only (no storage IO).\n"));
|
||||
if (opt_cipher) {
|
||||
if (!strcmp(opt_pbkdf, CRYPT_KDF_PBKDF2) && opt_hash) {
|
||||
r = action_benchmark_kdf(CRYPT_KDF_PBKDF2, opt_hash, key_size);
|
||||
} else if (opt_cipher) {
|
||||
r = crypt_parse_name_and_mode(opt_cipher, cipher, NULL, cipher_mode);
|
||||
if (r < 0) {
|
||||
log_err(_("No known cipher specification pattern detected.\n"));
|
||||
@@ -644,6 +666,11 @@ static int action_benchmark(void)
|
||||
if (r == -EINTR)
|
||||
break;
|
||||
}
|
||||
|
||||
/* benchmark Argon2: */
|
||||
action_benchmark_kdf(CRYPT_KDF_ARGON2I, NULL, key_size);
|
||||
action_benchmark_kdf(CRYPT_KDF_ARGON2ID, NULL, key_size);
|
||||
|
||||
for (i = 0; bciphers[i].cipher; i++) {
|
||||
r = benchmark_cipher_loop(bciphers[i].cipher, bciphers[i].mode,
|
||||
bciphers[i].key_size, bciphers[i].iv_size,
|
||||
@@ -1601,6 +1628,9 @@ int main(int argc, const char **argv)
|
||||
{ "perf-same_cpu_crypt",'\0', POPT_ARG_NONE, &opt_perf_same_cpu_crypt, 0, N_("Use dm-crypt same_cpu_crypt performance compatibility option."), NULL },
|
||||
{ "perf-submit_from_crypt_cpus",'\0', POPT_ARG_NONE, &opt_perf_submit_from_crypt_cpus,0,N_("Use dm-crypt submit_from_crypt_cpus performance compatibility option."), NULL },
|
||||
{ "deferred", '\0', POPT_ARG_NONE, &opt_deferred_remove, 0, N_("Device removal is deferred until the last user closes it."), NULL },
|
||||
{ "pbkdf", '\0', POPT_ARG_STRING, &opt_pbkdf, 0, N_("Password-based key derivation algorithm (PBKDF) for LUKS2 (argon2/pbkdf2)."), NULL },
|
||||
{ "pbkdf-memory", '\0', POPT_ARG_LONG, &opt_pbkdf_memory, 0, N_("Password-based key derivation algorithm (PBKDF) memory cost limit"), N_("kilobytes") },
|
||||
{ "pbkdf-parallel", '\0', POPT_ARG_LONG, &opt_pbkdf_parallel, 0, N_("Password-based key derivation algorithm (PBKDF) parallel cost "), N_("threads") },
|
||||
POPT_TABLEEND
|
||||
};
|
||||
poptContext popt_context;
|
||||
|
||||
Reference in New Issue
Block a user