utils_benchmark.c 5.73 KB
Newer Older
1
/*
Andrea Gelmini's avatar
Andrea Gelmini committed
2
 * libcryptsetup - cryptsetup library, cipher benchmark
3
 *
Milan Broz's avatar
Milan Broz committed
4 5
 * Copyright (C) 2012-2019 Red Hat, Inc. All rights reserved.
 * Copyright (C) 2012-2019 Milan Broz
6 7 8
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
9 10
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#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)
{
36 37
	void *buffer = NULL;
	char *iv = NULL, *key = NULL, mode[MAX_CIPHER_LEN], *c;
38 39
	int r;

Milan Broz's avatar
Milan Broz committed
40
	if (!cipher || !cipher_mode || !volume_key_size || !encryption_mbs || !decryption_mbs)
41 42 43 44 45 46 47
		return -EINVAL;

	r = init_crypto(cd);
	if (r < 0)
		return r;

	r = -ENOMEM;
48 49 50
	if (posix_memalign(&buffer, crypt_getpagesize(), buffer_size))
		goto out;

51
	if (iv_size) {
52 53
		iv = malloc(iv_size);
		if (!iv)
54
			goto out;
55
		crypt_random_get(cd, iv, iv_size, CRYPT_RND_NORMAL);
56 57
	}

58 59
	key = malloc(volume_key_size);
	if (!key)
60 61
		goto out;

62
	crypt_random_get(cd, key, volume_key_size, CRYPT_RND_NORMAL);
63

64
	strncpy(mode, cipher_mode, sizeof(mode)-1);
65
	/* Ignore IV generator */
66
	if ((c  = strchr(mode, '-')))
67 68
		*c = '\0';

69 70 71 72 73 74 75 76
	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 == -ENOTSUP || r == -ENOENT)
		log_dbg(cd, "Cannot initialise cipher %s, mode %s.", cipher, cipher_mode);

77
out:
78 79 80 81
	free(buffer);
	free(key);
	free(iv);

82 83
	return r;
}
Milan Broz's avatar
Milan Broz committed
84

Milan Broz's avatar
Milan Broz committed
85
int crypt_benchmark_pbkdf(struct crypt_device *cd,
86
	struct crypt_pbkdf_type *pbkdf,
Milan Broz's avatar
Milan Broz committed
87 88 89 90
	const char *password,
	size_t password_size,
	const char *salt,
	size_t salt_size,
Milan Broz's avatar
Milan Broz committed
91
	size_t volume_key_size,
92
	int (*progress)(uint32_t time_ms, void *usrptr),
93
	void *usrptr)
Milan Broz's avatar
Milan Broz committed
94
{
Milan Broz's avatar
Milan Broz committed
95
	int r;
Milan Broz's avatar
Milan Broz committed
96
	const char *kdf_opt;
Milan Broz's avatar
Milan Broz committed
97

Milan Broz's avatar
Milan Broz committed
98 99 100
	if (!pbkdf || (!password && password_size))
		return -EINVAL;

Milan Broz's avatar
Milan Broz committed
101 102 103 104
	r = init_crypto(cd);
	if (r < 0)
		return r;

Milan Broz's avatar
Milan Broz committed
105 106
	kdf_opt = !strcmp(pbkdf->type, CRYPT_KDF_PBKDF2) ? pbkdf->hash : "";

107
	log_dbg(cd, "Running %s(%s) benchmark.", pbkdf->type, kdf_opt);
Milan Broz's avatar
Milan Broz committed
108 109 110 111

	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,
112
			     &pbkdf->iterations, &pbkdf->max_memory_kb, progress, usrptr);
Milan Broz's avatar
Milan Broz committed
113 114

	if (!r)
115
		log_dbg(cd, "Benchmark returns %s(%s) %u iterations, %u memory, %u threads (for %zu-bits key).",
116
			pbkdf->type, kdf_opt, pbkdf->iterations, pbkdf->max_memory_kb,
Milan Broz's avatar
Milan Broz committed
117
			pbkdf->parallel_threads, volume_key_size * 8);
Milan Broz's avatar
Milan Broz committed
118 119
	return r;
}
120

121 122 123 124 125
struct benchmark_usrptr {
	struct crypt_device *cd;
	struct crypt_pbkdf_type *pbkdf;
};

126
static int benchmark_callback(uint32_t time_ms, void *usrptr)
127
{
128
	struct benchmark_usrptr *u = usrptr;
129

130 131 132
	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);
133 134 135 136 137 138 139

	return 0;
}

/*
 * Used in internal places to benchmark crypt_device context PBKDF.
 * Once requested parameters are benchmarked, iterations attribute is set,
Andrea Gelmini's avatar
Andrea Gelmini committed
140 141
 * and the benchmarked values can be reused.
 * Note that memory cost can be changed after benchmark (if used).
142 143 144 145 146 147
 * 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)
{
148
	struct crypt_pbkdf_limits pbkdf_limits;
149 150 151
	double PBKDF2_tmp;
	uint32_t ms_tmp;
	int r = -EINVAL;
152 153 154 155
	struct benchmark_usrptr u = {
		.cd = cd,
		.pbkdf = pbkdf
	};
156

157 158 159
	r = crypt_pbkdf_get_limits(pbkdf->type, &pbkdf_limits);
	if (r)
		return r;
160

161
	if (pbkdf->flags & CRYPT_PBKDF_NO_BENCHMARK) {
162
		if (pbkdf->iterations) {
163
			log_dbg(cd, "Reusing PBKDF values (no benchmark flag is set).");
164 165
			return 0;
		}
166
		log_err(cd, _("PBKDF benchmark disabled but iterations not set."));
167 168 169
		return -EINVAL;
	}

170
	/* For PBKDF2 run benchmark always. Also note it depends on volume_key_size! */
171 172
	if (!strcmp(pbkdf->type, CRYPT_KDF_PBKDF2)) {
		/*
Andrea Gelmini's avatar
Andrea Gelmini committed
173 174
		 * For PBKDF2 it is enough to run benchmark for only 1 second
		 * and interpolate final iterations value from it.
175 176 177 178 179 180 181
		 */
		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, "foo", 3, "bar", 3,
182
					volume_key_size, &benchmark_callback, &u);
183 184
		pbkdf->time_ms = ms_tmp;
		if (r < 0) {
185
			log_err(cd, _("Not compatible PBKDF2 options (using hash algorithm %s)."),
186 187 188 189 190 191 192
				pbkdf->hash);
			return r;
		}

		PBKDF2_tmp = ((double)pbkdf->iterations * pbkdf->time_ms / 1000.);
		if (PBKDF2_tmp > (double)UINT32_MAX)
			return -EINVAL;
193
		pbkdf->iterations = at_least((uint32_t)PBKDF2_tmp, pbkdf_limits.min_iterations);
194
	} else {
195 196
		/* Already benchmarked */
		if (pbkdf->iterations) {
197
			log_dbg(cd, "Reusing PBKDF values.");
198 199 200
			return 0;
		}

201 202
		r = crypt_benchmark_pbkdf(cd, pbkdf, "foo", 3,
			"0123456789abcdef0123456789abcdef", 32,
203
			volume_key_size, &benchmark_callback, &u);
204
		if (r < 0)
205
			log_err(cd, _("Not compatible PBKDF options."));
206 207 208 209
	}

	return r;
}