|
1 /* |
|
2 * cipher_driver.c |
|
3 * |
|
4 * A driver for the generic cipher type |
|
5 * |
|
6 * David A. McGrew |
|
7 * Cisco Systems, Inc. |
|
8 */ |
|
9 |
|
10 /* |
|
11 * |
|
12 * Copyright (c) 2001-2006, Cisco Systems, Inc. |
|
13 * All rights reserved. |
|
14 * |
|
15 * Redistribution and use in source and binary forms, with or without |
|
16 * modification, are permitted provided that the following conditions |
|
17 * are met: |
|
18 * |
|
19 * Redistributions of source code must retain the above copyright |
|
20 * notice, this list of conditions and the following disclaimer. |
|
21 * |
|
22 * Redistributions in binary form must reproduce the above |
|
23 * copyright notice, this list of conditions and the following |
|
24 * disclaimer in the documentation and/or other materials provided |
|
25 * with the distribution. |
|
26 * |
|
27 * Neither the name of the Cisco Systems, Inc. nor the names of its |
|
28 * contributors may be used to endorse or promote products derived |
|
29 * from this software without specific prior written permission. |
|
30 * |
|
31 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
32 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
33 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
34 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|
35 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
|
36 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
37 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|
38 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
39 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
|
40 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
|
42 * OF THE POSSIBILITY OF SUCH DAMAGE. |
|
43 * |
|
44 */ |
|
45 |
|
46 #include <stdio.h> /* for printf() */ |
|
47 #include <stdlib.h> /* for rand() */ |
|
48 #include <string.h> /* for memset() */ |
|
49 #include <unistd.h> /* for getopt() */ |
|
50 #include "cipher.h" |
|
51 #include "aes_icm.h" |
|
52 #include "null_cipher.h" |
|
53 |
|
54 #define PRINT_DEBUG 0 |
|
55 |
|
56 void |
|
57 cipher_driver_test_throughput(cipher_t *c); |
|
58 |
|
59 err_status_t |
|
60 cipher_driver_self_test(cipher_type_t *ct); |
|
61 |
|
62 |
|
63 /* |
|
64 * cipher_driver_test_buffering(ct) tests the cipher's output |
|
65 * buffering for correctness by checking the consistency of succesive |
|
66 * calls |
|
67 */ |
|
68 |
|
69 err_status_t |
|
70 cipher_driver_test_buffering(cipher_t *c); |
|
71 |
|
72 |
|
73 /* |
|
74 * functions for testing cipher cache thrash |
|
75 */ |
|
76 err_status_t |
|
77 cipher_driver_test_array_throughput(cipher_type_t *ct, |
|
78 int klen, int num_cipher); |
|
79 |
|
80 void |
|
81 cipher_array_test_throughput(cipher_t *ca[], int num_cipher); |
|
82 |
|
83 uint64_t |
|
84 cipher_array_bits_per_second(cipher_t *cipher_array[], int num_cipher, |
|
85 unsigned octets_in_buffer, int num_trials); |
|
86 |
|
87 err_status_t |
|
88 cipher_array_delete(cipher_t *cipher_array[], int num_cipher); |
|
89 |
|
90 err_status_t |
|
91 cipher_array_alloc_init(cipher_t ***cipher_array, int num_ciphers, |
|
92 cipher_type_t *ctype, int klen); |
|
93 |
|
94 void |
|
95 usage(char *prog_name) { |
|
96 printf("usage: %s [ -t | -v | -a ]\n", prog_name); |
|
97 exit(255); |
|
98 } |
|
99 |
|
100 void |
|
101 check_status(err_status_t s) { |
|
102 if (s) { |
|
103 printf("error (code %d)\n", s); |
|
104 exit(s); |
|
105 } |
|
106 return; |
|
107 } |
|
108 |
|
109 /* |
|
110 * null_cipher, aes_icm, and aes_cbc are the cipher meta-objects |
|
111 * defined in the files in crypto/cipher subdirectory. these are |
|
112 * declared external so that we can use these cipher types here |
|
113 */ |
|
114 |
|
115 extern cipher_type_t null_cipher; |
|
116 extern cipher_type_t aes_icm; |
|
117 extern cipher_type_t aes_cbc; |
|
118 |
|
119 int |
|
120 main(int argc, char *argv[]) { |
|
121 cipher_t *c = NULL; |
|
122 err_status_t status; |
|
123 unsigned char test_key[48] = { |
|
124 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
|
125 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, |
|
126 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, |
|
127 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, |
|
128 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, |
|
129 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, |
|
130 }; |
|
131 int q; |
|
132 unsigned do_timing_test = 0; |
|
133 unsigned do_validation = 0; |
|
134 unsigned do_array_timing_test = 0; |
|
135 |
|
136 /* process input arguments */ |
|
137 while (1) { |
|
138 q = getopt(argc, argv, "tva"); |
|
139 if (q == -1) |
|
140 break; |
|
141 switch (q) { |
|
142 case 't': |
|
143 do_timing_test = 1; |
|
144 break; |
|
145 case 'v': |
|
146 do_validation = 1; |
|
147 break; |
|
148 case 'a': |
|
149 do_array_timing_test = 1; |
|
150 break; |
|
151 default: |
|
152 usage(argv[0]); |
|
153 } |
|
154 } |
|
155 |
|
156 printf("cipher test driver\n" |
|
157 "David A. McGrew\n" |
|
158 "Cisco Systems, Inc.\n"); |
|
159 |
|
160 if (!do_validation && !do_timing_test && !do_array_timing_test) |
|
161 usage(argv[0]); |
|
162 |
|
163 /* arry timing (cache thrash) test */ |
|
164 if (do_array_timing_test) { |
|
165 int max_num_cipher = 1 << 16; /* number of ciphers in cipher_array */ |
|
166 int num_cipher; |
|
167 |
|
168 for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) |
|
169 cipher_driver_test_array_throughput(&null_cipher, 0, num_cipher); |
|
170 |
|
171 for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) |
|
172 cipher_driver_test_array_throughput(&aes_icm, 30, num_cipher); |
|
173 |
|
174 for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) |
|
175 cipher_driver_test_array_throughput(&aes_icm, 46, num_cipher); |
|
176 |
|
177 for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) |
|
178 cipher_driver_test_array_throughput(&aes_cbc, 16, num_cipher); |
|
179 |
|
180 for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) |
|
181 cipher_driver_test_array_throughput(&aes_cbc, 32, num_cipher); |
|
182 } |
|
183 |
|
184 if (do_validation) { |
|
185 cipher_driver_self_test(&null_cipher); |
|
186 cipher_driver_self_test(&aes_icm); |
|
187 cipher_driver_self_test(&aes_cbc); |
|
188 } |
|
189 |
|
190 /* do timing and/or buffer_test on null_cipher */ |
|
191 status = cipher_type_alloc(&null_cipher, &c, 0); |
|
192 check_status(status); |
|
193 |
|
194 status = cipher_init(c, NULL, direction_encrypt); |
|
195 check_status(status); |
|
196 |
|
197 if (do_timing_test) |
|
198 cipher_driver_test_throughput(c); |
|
199 if (do_validation) { |
|
200 status = cipher_driver_test_buffering(c); |
|
201 check_status(status); |
|
202 } |
|
203 status = cipher_dealloc(c); |
|
204 check_status(status); |
|
205 |
|
206 |
|
207 /* run the throughput test on the aes_icm cipher (128-bit key) */ |
|
208 status = cipher_type_alloc(&aes_icm, &c, 30); |
|
209 if (status) { |
|
210 fprintf(stderr, "error: can't allocate cipher\n"); |
|
211 exit(status); |
|
212 } |
|
213 |
|
214 status = cipher_init(c, test_key, direction_encrypt); |
|
215 check_status(status); |
|
216 |
|
217 if (do_timing_test) |
|
218 cipher_driver_test_throughput(c); |
|
219 |
|
220 if (do_validation) { |
|
221 status = cipher_driver_test_buffering(c); |
|
222 check_status(status); |
|
223 } |
|
224 |
|
225 status = cipher_dealloc(c); |
|
226 check_status(status); |
|
227 |
|
228 /* repeat the tests with 256-bit keys */ |
|
229 status = cipher_type_alloc(&aes_icm, &c, 46); |
|
230 if (status) { |
|
231 fprintf(stderr, "error: can't allocate cipher\n"); |
|
232 exit(status); |
|
233 } |
|
234 |
|
235 status = cipher_init(c, test_key, direction_encrypt); |
|
236 check_status(status); |
|
237 |
|
238 if (do_timing_test) |
|
239 cipher_driver_test_throughput(c); |
|
240 |
|
241 if (do_validation) { |
|
242 status = cipher_driver_test_buffering(c); |
|
243 check_status(status); |
|
244 } |
|
245 |
|
246 status = cipher_dealloc(c); |
|
247 check_status(status); |
|
248 |
|
249 return 0; |
|
250 } |
|
251 |
|
252 void |
|
253 cipher_driver_test_throughput(cipher_t *c) { |
|
254 int i; |
|
255 int min_enc_len = 32; |
|
256 int max_enc_len = 2048; /* should be a power of two */ |
|
257 int num_trials = 1000000; |
|
258 |
|
259 printf("timing %s throughput, key length %d:\n", c->type->description, c->key_len); |
|
260 fflush(stdout); |
|
261 for (i=min_enc_len; i <= max_enc_len; i = i * 2) |
|
262 printf("msg len: %d\tgigabits per second: %f\n", |
|
263 i, cipher_bits_per_second(c, i, num_trials) / 1e9); |
|
264 |
|
265 } |
|
266 |
|
267 err_status_t |
|
268 cipher_driver_self_test(cipher_type_t *ct) { |
|
269 err_status_t status; |
|
270 |
|
271 printf("running cipher self-test for %s...", ct->description); |
|
272 status = cipher_type_self_test(ct); |
|
273 if (status) { |
|
274 printf("failed with error code %d\n", status); |
|
275 exit(status); |
|
276 } |
|
277 printf("passed\n"); |
|
278 |
|
279 return err_status_ok; |
|
280 } |
|
281 |
|
282 /* |
|
283 * cipher_driver_test_buffering(ct) tests the cipher's output |
|
284 * buffering for correctness by checking the consistency of succesive |
|
285 * calls |
|
286 */ |
|
287 |
|
288 err_status_t |
|
289 cipher_driver_test_buffering(cipher_t *c) { |
|
290 int i, j, num_trials = 1000; |
|
291 unsigned len, buflen = 1024; |
|
292 uint8_t buffer0[buflen], buffer1[buflen], *current, *end; |
|
293 uint8_t idx[16] = { |
|
294 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|
295 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34 |
|
296 }; |
|
297 err_status_t status; |
|
298 |
|
299 printf("testing output buffering for cipher %s...", |
|
300 c->type->description); |
|
301 |
|
302 for (i=0; i < num_trials; i++) { |
|
303 |
|
304 /* set buffers to zero */ |
|
305 for (j=0; j < buflen; j++) |
|
306 buffer0[j] = buffer1[j] = 0; |
|
307 |
|
308 /* initialize cipher */ |
|
309 status = cipher_set_iv(c, idx); |
|
310 if (status) |
|
311 return status; |
|
312 |
|
313 /* generate 'reference' value by encrypting all at once */ |
|
314 status = cipher_encrypt(c, buffer0, &buflen); |
|
315 if (status) |
|
316 return status; |
|
317 |
|
318 /* re-initialize cipher */ |
|
319 status = cipher_set_iv(c, idx); |
|
320 if (status) |
|
321 return status; |
|
322 |
|
323 /* now loop over short lengths until buffer1 is encrypted */ |
|
324 current = buffer1; |
|
325 end = buffer1 + buflen; |
|
326 while (current < end) { |
|
327 |
|
328 /* choose a short length */ |
|
329 len = rand() & 0x01f; |
|
330 |
|
331 /* make sure that len doesn't cause us to overreach the buffer */ |
|
332 if (current + len > end) |
|
333 len = end - current; |
|
334 |
|
335 status = cipher_encrypt(c, current, &len); |
|
336 if (status) |
|
337 return status; |
|
338 |
|
339 /* advance pointer into buffer1 to reflect encryption */ |
|
340 current += len; |
|
341 |
|
342 /* if buffer1 is all encrypted, break out of loop */ |
|
343 if (current == end) |
|
344 break; |
|
345 } |
|
346 |
|
347 /* compare buffers */ |
|
348 for (j=0; j < buflen; j++) |
|
349 if (buffer0[j] != buffer1[j]) { |
|
350 #if PRINT_DEBUG |
|
351 printf("test case %d failed at byte %d\n", i, j); |
|
352 printf("computed: %s\n", octet_string_hex_string(buffer1, buflen)); |
|
353 printf("expected: %s\n", octet_string_hex_string(buffer0, buflen)); |
|
354 #endif |
|
355 return err_status_algo_fail; |
|
356 } |
|
357 } |
|
358 |
|
359 printf("passed\n"); |
|
360 |
|
361 return err_status_ok; |
|
362 } |
|
363 |
|
364 |
|
365 /* |
|
366 * The function cipher_test_throughput_array() tests the effect of CPU |
|
367 * cache thrash on cipher throughput. |
|
368 * |
|
369 * cipher_array_alloc_init(ctype, array, num_ciphers) creates an array |
|
370 * of cipher_t of type ctype |
|
371 */ |
|
372 |
|
373 err_status_t |
|
374 cipher_array_alloc_init(cipher_t ***ca, int num_ciphers, |
|
375 cipher_type_t *ctype, int klen) { |
|
376 int i, j; |
|
377 err_status_t status; |
|
378 uint8_t *key; |
|
379 cipher_t **cipher_array; |
|
380 /* pad klen allocation, to handle aes_icm reading 16 bytes for the |
|
381 14-byte salt */ |
|
382 int klen_pad = ((klen + 15) >> 4) << 4; |
|
383 |
|
384 /* allocate array of pointers to ciphers */ |
|
385 cipher_array = (cipher_t **) malloc(sizeof(cipher_t *) * num_ciphers); |
|
386 if (cipher_array == NULL) |
|
387 return err_status_alloc_fail; |
|
388 |
|
389 /* set ca to location of cipher_array */ |
|
390 *ca = cipher_array; |
|
391 |
|
392 /* allocate key */ |
|
393 key = crypto_alloc(klen_pad); |
|
394 if (key == NULL) { |
|
395 free(cipher_array); |
|
396 return err_status_alloc_fail; |
|
397 } |
|
398 |
|
399 /* allocate and initialize an array of ciphers */ |
|
400 for (i=0; i < num_ciphers; i++) { |
|
401 |
|
402 /* allocate cipher */ |
|
403 status = cipher_type_alloc(ctype, cipher_array, klen); |
|
404 if (status) |
|
405 return status; |
|
406 |
|
407 /* generate random key and initialize cipher */ |
|
408 for (j=0; j < klen; j++) |
|
409 key[j] = (uint8_t) rand(); |
|
410 for (; j < klen_pad; j++) |
|
411 key[j] = 0; |
|
412 status = cipher_init(*cipher_array, key, direction_encrypt); |
|
413 if (status) |
|
414 return status; |
|
415 |
|
416 /* printf("%dth cipher is at %p\n", i, *cipher_array); */ |
|
417 /* printf("%dth cipher description: %s\n", i, */ |
|
418 /* (*cipher_array)->type->description); */ |
|
419 |
|
420 /* advance cipher array pointer */ |
|
421 cipher_array++; |
|
422 } |
|
423 |
|
424 crypto_free(key); |
|
425 |
|
426 return err_status_ok; |
|
427 } |
|
428 |
|
429 err_status_t |
|
430 cipher_array_delete(cipher_t *cipher_array[], int num_cipher) { |
|
431 int i; |
|
432 |
|
433 for (i=0; i < num_cipher; i++) { |
|
434 cipher_dealloc(cipher_array[i]); |
|
435 } |
|
436 |
|
437 free(cipher_array); |
|
438 |
|
439 return err_status_ok; |
|
440 } |
|
441 |
|
442 |
|
443 /* |
|
444 * cipher_array_bits_per_second(c, l, t) computes (an estimate of) the |
|
445 * number of bits that a cipher implementation can encrypt in a second |
|
446 * when distinct keys are used to encrypt distinct messages |
|
447 * |
|
448 * c is a cipher (which MUST be allocated an initialized already), l |
|
449 * is the length in octets of the test data to be encrypted, and t is |
|
450 * the number of trials |
|
451 * |
|
452 * if an error is encountered, the value 0 is returned |
|
453 */ |
|
454 |
|
455 uint64_t |
|
456 cipher_array_bits_per_second(cipher_t *cipher_array[], int num_cipher, |
|
457 unsigned octets_in_buffer, int num_trials) { |
|
458 int i; |
|
459 v128_t nonce; |
|
460 clock_t timer; |
|
461 unsigned char *enc_buf; |
|
462 int cipher_index = rand() % num_cipher; |
|
463 |
|
464 /* Over-alloc, for NIST CBC padding */ |
|
465 enc_buf = crypto_alloc(octets_in_buffer+17); |
|
466 if (enc_buf == NULL) |
|
467 return 0; /* indicate bad parameters by returning null */ |
|
468 memset(enc_buf, 0, octets_in_buffer); |
|
469 |
|
470 /* time repeated trials */ |
|
471 v128_set_to_zero(&nonce); |
|
472 timer = clock(); |
|
473 for(i=0; i < num_trials; i++, nonce.v32[3] = i) { |
|
474 /* length parameter to cipher_encrypt is in/out -- out is total, padded |
|
475 * length -- so reset it each time. */ |
|
476 unsigned octets_to_encrypt = octets_in_buffer; |
|
477 |
|
478 /* encrypt buffer with cipher */ |
|
479 cipher_set_iv(cipher_array[cipher_index], &nonce); |
|
480 cipher_encrypt(cipher_array[cipher_index], enc_buf, &octets_to_encrypt); |
|
481 |
|
482 /* choose a cipher at random from the array*/ |
|
483 cipher_index = (*((uint32_t *)enc_buf)) % num_cipher; |
|
484 } |
|
485 timer = clock() - timer; |
|
486 |
|
487 free(enc_buf); |
|
488 |
|
489 if (timer == 0) { |
|
490 /* Too fast! */ |
|
491 return 0; |
|
492 } |
|
493 |
|
494 return (uint64_t)CLOCKS_PER_SEC * num_trials * 8 * octets_in_buffer / timer; |
|
495 } |
|
496 |
|
497 void |
|
498 cipher_array_test_throughput(cipher_t *ca[], int num_cipher) { |
|
499 int i; |
|
500 int min_enc_len = 16; |
|
501 int max_enc_len = 2048; /* should be a power of two */ |
|
502 int num_trials = 1000000; |
|
503 |
|
504 printf("timing %s throughput with key length %d, array size %d:\n", |
|
505 (ca[0])->type->description, (ca[0])->key_len, num_cipher); |
|
506 fflush(stdout); |
|
507 for (i=min_enc_len; i <= max_enc_len; i = i * 4) |
|
508 printf("msg len: %d\tgigabits per second: %f\n", i, |
|
509 cipher_array_bits_per_second(ca, num_cipher, i, num_trials) / 1e9); |
|
510 |
|
511 } |
|
512 |
|
513 err_status_t |
|
514 cipher_driver_test_array_throughput(cipher_type_t *ct, |
|
515 int klen, int num_cipher) { |
|
516 cipher_t **ca = NULL; |
|
517 err_status_t status; |
|
518 |
|
519 status = cipher_array_alloc_init(&ca, num_cipher, ct, klen); |
|
520 if (status) { |
|
521 printf("error: cipher_array_alloc_init() failed with error code %d\n", |
|
522 status); |
|
523 return status; |
|
524 } |
|
525 |
|
526 cipher_array_test_throughput(ca, num_cipher); |
|
527 |
|
528 cipher_array_delete(ca, num_cipher); |
|
529 |
|
530 return err_status_ok; |
|
531 } |