/src/cryptsetup/lib/crypto_backend/pbkdf_check.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: LGPL-2.1-or-later |
2 | | /* |
3 | | * PBKDF performance check |
4 | | * Copyright (C) 2012-2025 Red Hat, Inc. All rights reserved. |
5 | | * Copyright (C) 2012-2025 Milan Broz |
6 | | * Copyright (C) 2016-2020 Ondrej Mosnacek |
7 | | */ |
8 | | |
9 | | #include <stdlib.h> |
10 | | #include <errno.h> |
11 | | #include <limits.h> |
12 | | #include <time.h> |
13 | | #include <sys/time.h> |
14 | | #include <sys/resource.h> |
15 | | #include "crypto_backend.h" |
16 | | |
17 | | #ifndef CLOCK_MONOTONIC_RAW |
18 | | #define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC |
19 | | #endif |
20 | | |
21 | 0 | #define BENCH_MIN_MS 250 |
22 | 0 | #define BENCH_MIN_MS_FAST 10 |
23 | 0 | #define BENCH_PERCENT_ATLEAST 95 |
24 | 0 | #define BENCH_PERCENT_ATMOST 110 |
25 | 0 | #define BENCH_SAMPLES_FAST 3 |
26 | 0 | #define BENCH_SAMPLES_SLOW 1 |
27 | | |
28 | | /* These PBKDF2 limits must be never violated */ |
29 | | int crypt_pbkdf_get_limits(const char *kdf, struct crypt_pbkdf_limits *limits) |
30 | 6.56k | { |
31 | 6.56k | if (!kdf || !limits) |
32 | 0 | return -EINVAL; |
33 | | |
34 | 6.56k | if (!strcmp(kdf, "pbkdf2")) { |
35 | 2.90k | limits->min_iterations = 1000; /* recommendation in NIST SP 800-132 */ |
36 | 2.90k | limits->max_iterations = UINT32_MAX; |
37 | 2.90k | limits->min_memory = 0; /* N/A */ |
38 | 2.90k | limits->min_bench_memory=0; /* N/A */ |
39 | 2.90k | limits->max_memory = 0; /* N/A */ |
40 | 2.90k | limits->min_parallel = 0; /* N/A */ |
41 | 2.90k | limits->max_parallel = 0; /* N/A */ |
42 | 2.90k | return 0; |
43 | 3.66k | } else if (!strcmp(kdf, "argon2i") || !strcmp(kdf, "argon2id")) { |
44 | 3.66k | limits->min_iterations = 4; |
45 | 3.66k | limits->max_iterations = UINT32_MAX; |
46 | 3.66k | limits->min_memory = 32; /* hard limit */ |
47 | 3.66k | limits->min_bench_memory=64*1024; /* 64 MiB minimum for benchmark */ |
48 | 3.66k | limits->max_memory = 4*1024*1024; /* 4GiB */ |
49 | 3.66k | limits->min_parallel = 1; |
50 | 3.66k | limits->max_parallel = 4; |
51 | 3.66k | return 0; |
52 | 3.66k | } |
53 | | |
54 | 0 | return -EINVAL; |
55 | 6.56k | } |
56 | | |
57 | | static long time_ms(struct rusage *start, struct rusage *end) |
58 | 0 | { |
59 | 0 | int count_kernel_time = 0; |
60 | 0 | long ms; |
61 | |
|
62 | 0 | if (crypt_backend_flags() & CRYPT_BACKEND_KERNEL) |
63 | 0 | count_kernel_time = 1; |
64 | | |
65 | | /* |
66 | | * If there is no self usage info, count system time. |
67 | | * This seem like getrusage() bug in some hypervisors... |
68 | | */ |
69 | 0 | if (!end->ru_utime.tv_sec && !start->ru_utime.tv_sec && |
70 | 0 | !end->ru_utime.tv_usec && !start->ru_utime.tv_usec) |
71 | 0 | count_kernel_time = 1; |
72 | |
|
73 | 0 | ms = (end->ru_utime.tv_sec - start->ru_utime.tv_sec) * 1000; |
74 | 0 | ms += (end->ru_utime.tv_usec - start->ru_utime.tv_usec) / 1000; |
75 | |
|
76 | 0 | if (count_kernel_time) { |
77 | 0 | ms += (end->ru_stime.tv_sec - start->ru_stime.tv_sec) * 1000; |
78 | 0 | ms += (end->ru_stime.tv_usec - start->ru_stime.tv_usec) / 1000; |
79 | 0 | } |
80 | |
|
81 | 0 | return ms; |
82 | 0 | } |
83 | | |
84 | | static long timespec_ms(struct timespec *start, struct timespec *end) |
85 | 0 | { |
86 | 0 | return (end->tv_sec - start->tv_sec) * 1000 + |
87 | 0 | (end->tv_nsec - start->tv_nsec) / (1000 * 1000); |
88 | 0 | } |
89 | | |
90 | | static int measure_argon2(const char *kdf, const char *password, size_t password_length, |
91 | | const char *salt, size_t salt_length, |
92 | | char *key, size_t key_length, |
93 | | uint32_t t_cost, uint32_t m_cost, uint32_t parallel, |
94 | | size_t samples, long ms_atleast, long *out_ms) |
95 | 0 | { |
96 | 0 | long ms, ms_min = LONG_MAX; |
97 | 0 | int r; |
98 | 0 | size_t i; |
99 | |
|
100 | 0 | for (i = 0; i < samples; i++) { |
101 | 0 | struct timespec tstart, tend; |
102 | | |
103 | | /* |
104 | | * NOTE: We must use clock_gettime here, because Argon2 can run over |
105 | | * multiple threads, and thus we care about real time, not CPU time! |
106 | | */ |
107 | 0 | if (clock_gettime(CLOCK_MONOTONIC_RAW, &tstart) < 0) |
108 | 0 | return -EINVAL; |
109 | | |
110 | 0 | r = crypt_pbkdf(kdf, NULL, password, password_length, salt, |
111 | 0 | salt_length, key, key_length, t_cost, m_cost, parallel); |
112 | 0 | if (r < 0) |
113 | 0 | return r; |
114 | | |
115 | 0 | if (clock_gettime(CLOCK_MONOTONIC_RAW, &tend) < 0) |
116 | 0 | return -EINVAL; |
117 | | |
118 | 0 | ms = timespec_ms(&tstart, &tend); |
119 | 0 | if (ms < 0) |
120 | 0 | return -EINVAL; |
121 | | |
122 | 0 | if (ms < ms_atleast) { |
123 | | /* early exit */ |
124 | 0 | ms_min = ms; |
125 | 0 | break; |
126 | 0 | } |
127 | 0 | if (ms < ms_min) { |
128 | 0 | ms_min = ms; |
129 | 0 | } |
130 | 0 | } |
131 | 0 | *out_ms = ms_min; |
132 | 0 | return 0; |
133 | 0 | } |
134 | | |
135 | 0 | #define CONTINUE 0 |
136 | 0 | #define FINAL 1 |
137 | | static int next_argon2_params(uint32_t *t_cost, uint32_t *m_cost, |
138 | | uint32_t min_t_cost, uint32_t min_m_cost, |
139 | | uint32_t max_m_cost, long ms, uint32_t target_ms) |
140 | 0 | { |
141 | 0 | uint32_t old_t_cost, old_m_cost, new_t_cost, new_m_cost; |
142 | 0 | uint64_t num, denom; |
143 | |
|
144 | 0 | old_t_cost = *t_cost; |
145 | 0 | old_m_cost = *m_cost; |
146 | |
|
147 | 0 | if ((uint32_t)ms > target_ms) { |
148 | | /* decreasing, first try to lower t_cost, then m_cost */ |
149 | 0 | num = (uint64_t)*t_cost * (uint64_t)target_ms; |
150 | 0 | denom = (uint64_t)ms; |
151 | 0 | new_t_cost = (uint32_t)(num / denom); |
152 | 0 | if (new_t_cost < min_t_cost) { |
153 | 0 | num = (uint64_t)*t_cost * (uint64_t)*m_cost * |
154 | 0 | (uint64_t)target_ms; |
155 | 0 | denom = (uint64_t)min_t_cost * (uint64_t)ms; |
156 | 0 | *t_cost = min_t_cost; |
157 | 0 | *m_cost = (uint32_t)(num / denom); |
158 | 0 | if (*m_cost < min_m_cost) { |
159 | 0 | *m_cost = min_m_cost; |
160 | 0 | return FINAL; |
161 | 0 | } |
162 | 0 | } else { |
163 | 0 | *t_cost = new_t_cost; |
164 | 0 | } |
165 | 0 | } else { |
166 | | /* increasing, first try to increase m_cost, then t_cost */ |
167 | 0 | num = (uint64_t)*m_cost * (uint64_t)target_ms; |
168 | 0 | denom = (uint64_t)ms; |
169 | 0 | new_m_cost = (uint32_t)(num / denom); |
170 | 0 | if (new_m_cost > max_m_cost) { |
171 | 0 | num = (uint64_t)*t_cost * (uint64_t)*m_cost * |
172 | 0 | (uint64_t)target_ms; |
173 | 0 | denom = (uint64_t)max_m_cost * (uint64_t)ms; |
174 | 0 | *t_cost = (uint32_t)(num / denom); |
175 | 0 | *m_cost = max_m_cost; |
176 | 0 | if (*t_cost <= min_t_cost) { |
177 | 0 | *t_cost = min_t_cost; |
178 | 0 | return FINAL; |
179 | 0 | } |
180 | 0 | } else if (new_m_cost < min_m_cost) { |
181 | 0 | *m_cost = min_m_cost; |
182 | 0 | return FINAL; |
183 | 0 | } else { |
184 | 0 | *m_cost = new_m_cost; |
185 | 0 | } |
186 | 0 | } |
187 | | |
188 | | /* do not continue if it is the same as in the previous run */ |
189 | 0 | if (old_t_cost == *t_cost && old_m_cost == *m_cost) |
190 | 0 | return FINAL; |
191 | | |
192 | 0 | return CONTINUE; |
193 | 0 | } |
194 | | |
195 | | static int crypt_argon2_check(const char *kdf, const char *password, |
196 | | size_t password_length, const char *salt, |
197 | | size_t salt_length, size_t key_length, |
198 | | uint32_t min_t_cost, uint32_t min_m_cost, uint32_t max_m_cost, |
199 | | uint32_t parallel, uint32_t target_ms, |
200 | | uint32_t *out_t_cost, uint32_t *out_m_cost, |
201 | | int (*progress)(uint32_t time_ms, void *usrptr), |
202 | | void *usrptr) |
203 | 0 | { |
204 | 0 | int r = 0; |
205 | 0 | char *key = NULL; |
206 | 0 | uint32_t t_cost, m_cost; |
207 | 0 | long ms; |
208 | 0 | long ms_atleast = (long)target_ms * BENCH_PERCENT_ATLEAST / 100; |
209 | 0 | long ms_atmost = (long)target_ms * BENCH_PERCENT_ATMOST / 100; |
210 | |
|
211 | 0 | if (key_length <= 0 || target_ms <= 0) |
212 | 0 | return -EINVAL; |
213 | | |
214 | 0 | if (min_m_cost < (parallel * 8)) |
215 | 0 | min_m_cost = parallel * 8; |
216 | |
|
217 | 0 | if (max_m_cost < min_m_cost) |
218 | 0 | return -EINVAL; |
219 | | |
220 | 0 | key = malloc(key_length); |
221 | 0 | if (!key) |
222 | 0 | return -ENOMEM; |
223 | | |
224 | 0 | t_cost = min_t_cost; |
225 | 0 | m_cost = min_m_cost; |
226 | | |
227 | | /* 1. Find some small parameters, s. t. ms >= BENCH_MIN_MS: */ |
228 | 0 | while (1) { |
229 | 0 | r = measure_argon2(kdf, password, password_length, salt, salt_length, |
230 | 0 | key, key_length, t_cost, m_cost, parallel, |
231 | 0 | BENCH_SAMPLES_FAST, BENCH_MIN_MS, &ms); |
232 | 0 | if (!r) { |
233 | | /* Update parameters to actual measurement */ |
234 | 0 | *out_t_cost = t_cost; |
235 | 0 | *out_m_cost = m_cost; |
236 | 0 | if (progress && progress((uint32_t)ms, usrptr)) |
237 | 0 | r = -EINTR; |
238 | 0 | } |
239 | |
|
240 | 0 | if (r < 0) |
241 | 0 | goto out; |
242 | | |
243 | 0 | if (ms >= BENCH_MIN_MS) |
244 | 0 | break; |
245 | | |
246 | 0 | if (m_cost == max_m_cost) { |
247 | 0 | if (ms < BENCH_MIN_MS_FAST) |
248 | 0 | t_cost *= 16; |
249 | 0 | else { |
250 | 0 | uint32_t new = (t_cost * BENCH_MIN_MS) / (uint32_t)ms; |
251 | 0 | if (new == t_cost) |
252 | 0 | break; |
253 | | |
254 | 0 | t_cost = new; |
255 | 0 | } |
256 | 0 | } else { |
257 | 0 | if (ms < BENCH_MIN_MS_FAST) |
258 | 0 | m_cost *= 16; |
259 | 0 | else { |
260 | 0 | uint32_t new = (m_cost * BENCH_MIN_MS) / (uint32_t)ms; |
261 | 0 | if (new == m_cost) |
262 | 0 | break; |
263 | | |
264 | 0 | m_cost = new; |
265 | 0 | } |
266 | 0 | if (m_cost > max_m_cost) { |
267 | 0 | m_cost = max_m_cost; |
268 | 0 | } |
269 | 0 | } |
270 | 0 | } |
271 | | /* |
272 | | * 2. Use the params obtained in (1.) to estimate the target params. |
273 | | * 3. Then repeatedly measure the candidate params and if they fall out of |
274 | | * the acceptance range (+-5 %), try to improve the estimate: |
275 | | */ |
276 | 0 | do { |
277 | 0 | if (next_argon2_params(&t_cost, &m_cost, min_t_cost, min_m_cost, |
278 | 0 | max_m_cost, ms, target_ms)) { |
279 | | /* Update parameters to final computation */ |
280 | 0 | *out_t_cost = t_cost; |
281 | 0 | *out_m_cost = m_cost; |
282 | 0 | break; |
283 | 0 | } |
284 | | |
285 | 0 | r = measure_argon2(kdf, password, password_length, salt, salt_length, |
286 | 0 | key, key_length, t_cost, m_cost, parallel, |
287 | 0 | BENCH_SAMPLES_SLOW, ms_atleast, &ms); |
288 | |
|
289 | 0 | if (!r) { |
290 | | /* Update parameters to actual measurement */ |
291 | 0 | *out_t_cost = t_cost; |
292 | 0 | *out_m_cost = m_cost; |
293 | 0 | if (progress && progress((uint32_t)ms, usrptr)) |
294 | 0 | r = -EINTR; |
295 | 0 | } |
296 | |
|
297 | 0 | if (r < 0) |
298 | 0 | break; |
299 | |
|
300 | 0 | } while (ms < ms_atleast || ms > ms_atmost); |
301 | 0 | out: |
302 | 0 | if (key) { |
303 | | /* Key can be derived from a real provided password */ |
304 | 0 | crypt_backend_memzero(key, key_length); |
305 | 0 | free(key); |
306 | 0 | } |
307 | 0 | return r; |
308 | 0 | } |
309 | | |
310 | | /* This code benchmarks PBKDF and returns iterations/second using specified hash */ |
311 | | static int crypt_pbkdf_check(const char *kdf, const char *hash, |
312 | | const char *password, size_t password_length, |
313 | | const char *salt, size_t salt_length, |
314 | | size_t key_length, uint32_t *iter_secs, uint32_t target_ms, |
315 | | int (*progress)(uint32_t time_ms, void *usrptr), void *usrptr) |
316 | | |
317 | 0 | { |
318 | 0 | struct rusage rstart, rend; |
319 | 0 | int r = 0, step = 0; |
320 | 0 | long ms = 0; |
321 | 0 | char *key = NULL; |
322 | 0 | uint32_t iterations; |
323 | 0 | double PBKDF2_temp; |
324 | |
|
325 | 0 | if (!kdf || !hash || key_length <= 0) |
326 | 0 | return -EINVAL; |
327 | | |
328 | 0 | key = malloc(key_length); |
329 | 0 | if (!key) |
330 | 0 | return -ENOMEM; |
331 | | |
332 | 0 | *iter_secs = 0; |
333 | 0 | iterations = 1 << 15; |
334 | 0 | while (1) { |
335 | 0 | if (getrusage(RUSAGE_SELF, &rstart) < 0) { |
336 | 0 | r = -EINVAL; |
337 | 0 | goto out; |
338 | 0 | } |
339 | | |
340 | 0 | r = crypt_pbkdf(kdf, hash, password, password_length, salt, |
341 | 0 | salt_length, key, key_length, iterations, 0, 0); |
342 | |
|
343 | 0 | if (r < 0) |
344 | 0 | goto out; |
345 | | |
346 | 0 | if (getrusage(RUSAGE_SELF, &rend) < 0) { |
347 | 0 | r = -EINVAL; |
348 | 0 | goto out; |
349 | 0 | } |
350 | | |
351 | 0 | ms = time_ms(&rstart, &rend); |
352 | 0 | if (ms) { |
353 | 0 | PBKDF2_temp = (double)iterations * target_ms / ms; |
354 | 0 | if (PBKDF2_temp > UINT32_MAX) { |
355 | 0 | r = -EINVAL; |
356 | 0 | goto out; |
357 | 0 | } |
358 | 0 | *iter_secs = (uint32_t)PBKDF2_temp; |
359 | 0 | } |
360 | | |
361 | 0 | if (progress && progress((uint32_t)ms, usrptr)) { |
362 | 0 | r = -EINTR; |
363 | 0 | goto out; |
364 | 0 | } |
365 | | |
366 | 0 | if (ms > 500) |
367 | 0 | break; |
368 | | |
369 | 0 | if (ms <= 62) |
370 | 0 | iterations <<= 4; |
371 | 0 | else if (ms <= 125) |
372 | 0 | iterations <<= 3; |
373 | 0 | else if (ms <= 250) |
374 | 0 | iterations <<= 2; |
375 | 0 | else |
376 | 0 | iterations <<= 1; |
377 | |
|
378 | 0 | if (++step > 10 || !iterations) { |
379 | 0 | r = -EINVAL; |
380 | 0 | goto out; |
381 | 0 | } |
382 | 0 | } |
383 | 0 | out: |
384 | 0 | if (key) { |
385 | | /* Key can be derived from a real provided password */ |
386 | 0 | crypt_backend_memzero(key, key_length); |
387 | 0 | free(key); |
388 | 0 | } |
389 | 0 | return r; |
390 | 0 | } |
391 | | |
392 | | int crypt_pbkdf_perf(const char *kdf, const char *hash, |
393 | | const char *password, size_t password_size, |
394 | | const char *salt, size_t salt_size, |
395 | | size_t volume_key_size, uint32_t time_ms, |
396 | | uint32_t max_memory_kb, uint32_t parallel_threads, |
397 | | uint32_t *iterations_out, uint32_t *memory_out, |
398 | | int (*progress)(uint32_t time_ms, void *usrptr), void *usrptr) |
399 | 0 | { |
400 | 0 | struct crypt_pbkdf_limits pbkdf_limits; |
401 | 0 | int r = -EINVAL; |
402 | 0 | uint32_t min_memory; |
403 | |
|
404 | 0 | if (!kdf || !iterations_out || !memory_out) |
405 | 0 | return -EINVAL; |
406 | | |
407 | 0 | r = crypt_pbkdf_get_limits(kdf, &pbkdf_limits); |
408 | 0 | if (r < 0) |
409 | 0 | return r; |
410 | | |
411 | 0 | if (parallel_threads > pbkdf_limits.max_parallel) |
412 | 0 | return -EINVAL; |
413 | | |
414 | 0 | min_memory = pbkdf_limits.min_bench_memory; |
415 | 0 | if (min_memory > max_memory_kb) |
416 | 0 | min_memory = max_memory_kb; |
417 | |
|
418 | 0 | *memory_out = 0; |
419 | 0 | *iterations_out = 0; |
420 | |
|
421 | 0 | if (!strcmp(kdf, "pbkdf2")) |
422 | 0 | r = crypt_pbkdf_check(kdf, hash, password, password_size, |
423 | 0 | salt, salt_size, volume_key_size, |
424 | 0 | iterations_out, time_ms, progress, usrptr); |
425 | | |
426 | 0 | else if (!strncmp(kdf, "argon2", 6)) |
427 | 0 | r = crypt_argon2_check(kdf, password, password_size, |
428 | 0 | salt, salt_size, volume_key_size, |
429 | 0 | pbkdf_limits.min_iterations, |
430 | 0 | min_memory, |
431 | 0 | max_memory_kb, |
432 | 0 | parallel_threads, time_ms, iterations_out, |
433 | 0 | memory_out, progress, usrptr); |
434 | 0 | return r; |
435 | 0 | } |