X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=lib%2Fcrypto_backend%2Fpbkdf_check.c;h=7444e0aafbac384201b70d830c4ca409dfff090d;hb=322b430a2589cdc7985e98a14ec12322b91c9d5e;hp=f4877bdeccf230c4502a479a302b8928724cd49a;hpb=83f02e66827fa6fa66f9b73a009d2ba51d22352d;p=platform%2Fupstream%2Fcryptsetup.git diff --git a/lib/crypto_backend/pbkdf_check.c b/lib/crypto_backend/pbkdf_check.c index f4877bd..7444e0a 100644 --- a/lib/crypto_backend/pbkdf_check.c +++ b/lib/crypto_backend/pbkdf_check.c @@ -1,7 +1,8 @@ /* * PBKDF performance check - * Copyright (C) 2012, Red Hat, Inc. All rights reserved. - * Copyright (C) 2012, Milan Broz + * Copyright (C) 2012-2020 Red Hat, Inc. All rights reserved. + * Copyright (C) 2012-2020 Milan Broz + * Copyright (C) 2016-2020 Ondrej Mosnacek * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,19 +19,72 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #include +#include +#include #include #include #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->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; + 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; + + /* + * FIXME: 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 (crypt_backend_flags() & CRYPT_BACKEND_KERNEL) { + 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; } @@ -38,35 +92,285 @@ 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; +} + +#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 */ -int crypt_pbkdf_check(const char *kdf, const char *hash, - const char *password, size_t password_size, - const char *salt, size_t salt_size, - uint64_t *iter_secs) +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 buf; - unsigned int iterations; + char *key = NULL; + uint32_t iterations; + double PBKDF2_temp; - if (!kdf || !hash) + if (!kdf || !hash || key_length <= 0) return -EINVAL; + key = malloc(key_length); + if (!key) + return -ENOMEM; + + *iter_secs = 0; iterations = 1 << 15; - while (ms < 500) { - if (getrusage(RUSAGE_SELF, &rstart) < 0) - return -EINVAL; + 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); - r = crypt_pbkdf(kdf, hash, password, password_size, salt, - salt_size, &buf, 1, iterations); if (r < 0) - return r; + goto out; - if (getrusage(RUSAGE_SELF, &rend) < 0) - return -EINVAL; + 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) + return -EINVAL; + *iter_secs = (uint32_t)PBKDF2_temp; + } + + if (progress && progress((uint32_t)ms, usrptr)) { + r = -EINTR; + goto out; + } + if (ms > 500) break; @@ -79,11 +383,53 @@ int crypt_pbkdf_check(const char *kdf, const char *hash, else iterations <<= 1; - if (++step > 10 || !iterations) - return -EINVAL; + 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; + + if (!kdf || !iterations_out || !memory_out) + return -EINVAL; + + /* FIXME: whole limits propagation should be more clear here */ + r = crypt_pbkdf_get_limits(kdf, &pbkdf_limits); + if (r < 0) + return r; + + *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); - if (iter_secs) - *iter_secs = (iterations * 1000) / ms; + else if (!strncmp(kdf, "argon2", 6)) + r = crypt_argon2_check(kdf, password, password_size, + salt, salt_size, volume_key_size, + pbkdf_limits.min_iterations, + pbkdf_limits.min_memory, + max_memory_kb, + parallel_threads, time_ms, iterations_out, + memory_out, progress, usrptr); return r; }