michael@0: /* michael@0: * cipher_driver.c michael@0: * michael@0: * A driver for the generic cipher type michael@0: * michael@0: * David A. McGrew michael@0: * Cisco Systems, Inc. michael@0: */ michael@0: michael@0: /* michael@0: * michael@0: * Copyright (c) 2001-2006, Cisco Systems, Inc. michael@0: * All rights reserved. michael@0: * michael@0: * Redistribution and use in source and binary forms, with or without michael@0: * modification, are permitted provided that the following conditions michael@0: * are met: michael@0: * michael@0: * Redistributions of source code must retain the above copyright michael@0: * notice, this list of conditions and the following disclaimer. michael@0: * michael@0: * Redistributions in binary form must reproduce the above michael@0: * copyright notice, this list of conditions and the following michael@0: * disclaimer in the documentation and/or other materials provided michael@0: * with the distribution. michael@0: * michael@0: * Neither the name of the Cisco Systems, Inc. nor the names of its michael@0: * contributors may be used to endorse or promote products derived michael@0: * from this software without specific prior written permission. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS michael@0: * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE michael@0: * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, michael@0: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES michael@0: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR michael@0: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) michael@0: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, michael@0: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) michael@0: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED michael@0: * OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: * michael@0: */ michael@0: michael@0: #include /* for printf() */ michael@0: #include /* for rand() */ michael@0: #include /* for memset() */ michael@0: #include /* for getopt() */ michael@0: #include "cipher.h" michael@0: #include "aes_icm.h" michael@0: #include "null_cipher.h" michael@0: michael@0: #define PRINT_DEBUG 0 michael@0: michael@0: void michael@0: cipher_driver_test_throughput(cipher_t *c); michael@0: michael@0: err_status_t michael@0: cipher_driver_self_test(cipher_type_t *ct); michael@0: michael@0: michael@0: /* michael@0: * cipher_driver_test_buffering(ct) tests the cipher's output michael@0: * buffering for correctness by checking the consistency of succesive michael@0: * calls michael@0: */ michael@0: michael@0: err_status_t michael@0: cipher_driver_test_buffering(cipher_t *c); michael@0: michael@0: michael@0: /* michael@0: * functions for testing cipher cache thrash michael@0: */ michael@0: err_status_t michael@0: cipher_driver_test_array_throughput(cipher_type_t *ct, michael@0: int klen, int num_cipher); michael@0: michael@0: void michael@0: cipher_array_test_throughput(cipher_t *ca[], int num_cipher); michael@0: michael@0: uint64_t michael@0: cipher_array_bits_per_second(cipher_t *cipher_array[], int num_cipher, michael@0: unsigned octets_in_buffer, int num_trials); michael@0: michael@0: err_status_t michael@0: cipher_array_delete(cipher_t *cipher_array[], int num_cipher); michael@0: michael@0: err_status_t michael@0: cipher_array_alloc_init(cipher_t ***cipher_array, int num_ciphers, michael@0: cipher_type_t *ctype, int klen); michael@0: michael@0: void michael@0: usage(char *prog_name) { michael@0: printf("usage: %s [ -t | -v | -a ]\n", prog_name); michael@0: exit(255); michael@0: } michael@0: michael@0: void michael@0: check_status(err_status_t s) { michael@0: if (s) { michael@0: printf("error (code %d)\n", s); michael@0: exit(s); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * null_cipher, aes_icm, and aes_cbc are the cipher meta-objects michael@0: * defined in the files in crypto/cipher subdirectory. these are michael@0: * declared external so that we can use these cipher types here michael@0: */ michael@0: michael@0: extern cipher_type_t null_cipher; michael@0: extern cipher_type_t aes_icm; michael@0: extern cipher_type_t aes_cbc; michael@0: michael@0: int michael@0: main(int argc, char *argv[]) { michael@0: cipher_t *c = NULL; michael@0: err_status_t status; michael@0: unsigned char test_key[48] = { michael@0: 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, michael@0: 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, michael@0: 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, michael@0: 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, michael@0: 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, michael@0: 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, michael@0: }; michael@0: int q; michael@0: unsigned do_timing_test = 0; michael@0: unsigned do_validation = 0; michael@0: unsigned do_array_timing_test = 0; michael@0: michael@0: /* process input arguments */ michael@0: while (1) { michael@0: q = getopt(argc, argv, "tva"); michael@0: if (q == -1) michael@0: break; michael@0: switch (q) { michael@0: case 't': michael@0: do_timing_test = 1; michael@0: break; michael@0: case 'v': michael@0: do_validation = 1; michael@0: break; michael@0: case 'a': michael@0: do_array_timing_test = 1; michael@0: break; michael@0: default: michael@0: usage(argv[0]); michael@0: } michael@0: } michael@0: michael@0: printf("cipher test driver\n" michael@0: "David A. McGrew\n" michael@0: "Cisco Systems, Inc.\n"); michael@0: michael@0: if (!do_validation && !do_timing_test && !do_array_timing_test) michael@0: usage(argv[0]); michael@0: michael@0: /* arry timing (cache thrash) test */ michael@0: if (do_array_timing_test) { michael@0: int max_num_cipher = 1 << 16; /* number of ciphers in cipher_array */ michael@0: int num_cipher; michael@0: michael@0: for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) michael@0: cipher_driver_test_array_throughput(&null_cipher, 0, num_cipher); michael@0: michael@0: for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) michael@0: cipher_driver_test_array_throughput(&aes_icm, 30, num_cipher); michael@0: michael@0: for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) michael@0: cipher_driver_test_array_throughput(&aes_icm, 46, num_cipher); michael@0: michael@0: for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) michael@0: cipher_driver_test_array_throughput(&aes_cbc, 16, num_cipher); michael@0: michael@0: for (num_cipher=1; num_cipher < max_num_cipher; num_cipher *=8) michael@0: cipher_driver_test_array_throughput(&aes_cbc, 32, num_cipher); michael@0: } michael@0: michael@0: if (do_validation) { michael@0: cipher_driver_self_test(&null_cipher); michael@0: cipher_driver_self_test(&aes_icm); michael@0: cipher_driver_self_test(&aes_cbc); michael@0: } michael@0: michael@0: /* do timing and/or buffer_test on null_cipher */ michael@0: status = cipher_type_alloc(&null_cipher, &c, 0); michael@0: check_status(status); michael@0: michael@0: status = cipher_init(c, NULL, direction_encrypt); michael@0: check_status(status); michael@0: michael@0: if (do_timing_test) michael@0: cipher_driver_test_throughput(c); michael@0: if (do_validation) { michael@0: status = cipher_driver_test_buffering(c); michael@0: check_status(status); michael@0: } michael@0: status = cipher_dealloc(c); michael@0: check_status(status); michael@0: michael@0: michael@0: /* run the throughput test on the aes_icm cipher (128-bit key) */ michael@0: status = cipher_type_alloc(&aes_icm, &c, 30); michael@0: if (status) { michael@0: fprintf(stderr, "error: can't allocate cipher\n"); michael@0: exit(status); michael@0: } michael@0: michael@0: status = cipher_init(c, test_key, direction_encrypt); michael@0: check_status(status); michael@0: michael@0: if (do_timing_test) michael@0: cipher_driver_test_throughput(c); michael@0: michael@0: if (do_validation) { michael@0: status = cipher_driver_test_buffering(c); michael@0: check_status(status); michael@0: } michael@0: michael@0: status = cipher_dealloc(c); michael@0: check_status(status); michael@0: michael@0: /* repeat the tests with 256-bit keys */ michael@0: status = cipher_type_alloc(&aes_icm, &c, 46); michael@0: if (status) { michael@0: fprintf(stderr, "error: can't allocate cipher\n"); michael@0: exit(status); michael@0: } michael@0: michael@0: status = cipher_init(c, test_key, direction_encrypt); michael@0: check_status(status); michael@0: michael@0: if (do_timing_test) michael@0: cipher_driver_test_throughput(c); michael@0: michael@0: if (do_validation) { michael@0: status = cipher_driver_test_buffering(c); michael@0: check_status(status); michael@0: } michael@0: michael@0: status = cipher_dealloc(c); michael@0: check_status(status); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: void michael@0: cipher_driver_test_throughput(cipher_t *c) { michael@0: int i; michael@0: int min_enc_len = 32; michael@0: int max_enc_len = 2048; /* should be a power of two */ michael@0: int num_trials = 1000000; michael@0: michael@0: printf("timing %s throughput, key length %d:\n", c->type->description, c->key_len); michael@0: fflush(stdout); michael@0: for (i=min_enc_len; i <= max_enc_len; i = i * 2) michael@0: printf("msg len: %d\tgigabits per second: %f\n", michael@0: i, cipher_bits_per_second(c, i, num_trials) / 1e9); michael@0: michael@0: } michael@0: michael@0: err_status_t michael@0: cipher_driver_self_test(cipher_type_t *ct) { michael@0: err_status_t status; michael@0: michael@0: printf("running cipher self-test for %s...", ct->description); michael@0: status = cipher_type_self_test(ct); michael@0: if (status) { michael@0: printf("failed with error code %d\n", status); michael@0: exit(status); michael@0: } michael@0: printf("passed\n"); michael@0: michael@0: return err_status_ok; michael@0: } michael@0: michael@0: /* michael@0: * cipher_driver_test_buffering(ct) tests the cipher's output michael@0: * buffering for correctness by checking the consistency of succesive michael@0: * calls michael@0: */ michael@0: michael@0: err_status_t michael@0: cipher_driver_test_buffering(cipher_t *c) { michael@0: int i, j, num_trials = 1000; michael@0: unsigned len, buflen = 1024; michael@0: uint8_t buffer0[buflen], buffer1[buflen], *current, *end; michael@0: uint8_t idx[16] = { michael@0: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, michael@0: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34 michael@0: }; michael@0: err_status_t status; michael@0: michael@0: printf("testing output buffering for cipher %s...", michael@0: c->type->description); michael@0: michael@0: for (i=0; i < num_trials; i++) { michael@0: michael@0: /* set buffers to zero */ michael@0: for (j=0; j < buflen; j++) michael@0: buffer0[j] = buffer1[j] = 0; michael@0: michael@0: /* initialize cipher */ michael@0: status = cipher_set_iv(c, idx); michael@0: if (status) michael@0: return status; michael@0: michael@0: /* generate 'reference' value by encrypting all at once */ michael@0: status = cipher_encrypt(c, buffer0, &buflen); michael@0: if (status) michael@0: return status; michael@0: michael@0: /* re-initialize cipher */ michael@0: status = cipher_set_iv(c, idx); michael@0: if (status) michael@0: return status; michael@0: michael@0: /* now loop over short lengths until buffer1 is encrypted */ michael@0: current = buffer1; michael@0: end = buffer1 + buflen; michael@0: while (current < end) { michael@0: michael@0: /* choose a short length */ michael@0: len = rand() & 0x01f; michael@0: michael@0: /* make sure that len doesn't cause us to overreach the buffer */ michael@0: if (current + len > end) michael@0: len = end - current; michael@0: michael@0: status = cipher_encrypt(c, current, &len); michael@0: if (status) michael@0: return status; michael@0: michael@0: /* advance pointer into buffer1 to reflect encryption */ michael@0: current += len; michael@0: michael@0: /* if buffer1 is all encrypted, break out of loop */ michael@0: if (current == end) michael@0: break; michael@0: } michael@0: michael@0: /* compare buffers */ michael@0: for (j=0; j < buflen; j++) michael@0: if (buffer0[j] != buffer1[j]) { michael@0: #if PRINT_DEBUG michael@0: printf("test case %d failed at byte %d\n", i, j); michael@0: printf("computed: %s\n", octet_string_hex_string(buffer1, buflen)); michael@0: printf("expected: %s\n", octet_string_hex_string(buffer0, buflen)); michael@0: #endif michael@0: return err_status_algo_fail; michael@0: } michael@0: } michael@0: michael@0: printf("passed\n"); michael@0: michael@0: return err_status_ok; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * The function cipher_test_throughput_array() tests the effect of CPU michael@0: * cache thrash on cipher throughput. michael@0: * michael@0: * cipher_array_alloc_init(ctype, array, num_ciphers) creates an array michael@0: * of cipher_t of type ctype michael@0: */ michael@0: michael@0: err_status_t michael@0: cipher_array_alloc_init(cipher_t ***ca, int num_ciphers, michael@0: cipher_type_t *ctype, int klen) { michael@0: int i, j; michael@0: err_status_t status; michael@0: uint8_t *key; michael@0: cipher_t **cipher_array; michael@0: /* pad klen allocation, to handle aes_icm reading 16 bytes for the michael@0: 14-byte salt */ michael@0: int klen_pad = ((klen + 15) >> 4) << 4; michael@0: michael@0: /* allocate array of pointers to ciphers */ michael@0: cipher_array = (cipher_t **) malloc(sizeof(cipher_t *) * num_ciphers); michael@0: if (cipher_array == NULL) michael@0: return err_status_alloc_fail; michael@0: michael@0: /* set ca to location of cipher_array */ michael@0: *ca = cipher_array; michael@0: michael@0: /* allocate key */ michael@0: key = crypto_alloc(klen_pad); michael@0: if (key == NULL) { michael@0: free(cipher_array); michael@0: return err_status_alloc_fail; michael@0: } michael@0: michael@0: /* allocate and initialize an array of ciphers */ michael@0: for (i=0; i < num_ciphers; i++) { michael@0: michael@0: /* allocate cipher */ michael@0: status = cipher_type_alloc(ctype, cipher_array, klen); michael@0: if (status) michael@0: return status; michael@0: michael@0: /* generate random key and initialize cipher */ michael@0: for (j=0; j < klen; j++) michael@0: key[j] = (uint8_t) rand(); michael@0: for (; j < klen_pad; j++) michael@0: key[j] = 0; michael@0: status = cipher_init(*cipher_array, key, direction_encrypt); michael@0: if (status) michael@0: return status; michael@0: michael@0: /* printf("%dth cipher is at %p\n", i, *cipher_array); */ michael@0: /* printf("%dth cipher description: %s\n", i, */ michael@0: /* (*cipher_array)->type->description); */ michael@0: michael@0: /* advance cipher array pointer */ michael@0: cipher_array++; michael@0: } michael@0: michael@0: crypto_free(key); michael@0: michael@0: return err_status_ok; michael@0: } michael@0: michael@0: err_status_t michael@0: cipher_array_delete(cipher_t *cipher_array[], int num_cipher) { michael@0: int i; michael@0: michael@0: for (i=0; i < num_cipher; i++) { michael@0: cipher_dealloc(cipher_array[i]); michael@0: } michael@0: michael@0: free(cipher_array); michael@0: michael@0: return err_status_ok; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * cipher_array_bits_per_second(c, l, t) computes (an estimate of) the michael@0: * number of bits that a cipher implementation can encrypt in a second michael@0: * when distinct keys are used to encrypt distinct messages michael@0: * michael@0: * c is a cipher (which MUST be allocated an initialized already), l michael@0: * is the length in octets of the test data to be encrypted, and t is michael@0: * the number of trials michael@0: * michael@0: * if an error is encountered, the value 0 is returned michael@0: */ michael@0: michael@0: uint64_t michael@0: cipher_array_bits_per_second(cipher_t *cipher_array[], int num_cipher, michael@0: unsigned octets_in_buffer, int num_trials) { michael@0: int i; michael@0: v128_t nonce; michael@0: clock_t timer; michael@0: unsigned char *enc_buf; michael@0: int cipher_index = rand() % num_cipher; michael@0: michael@0: /* Over-alloc, for NIST CBC padding */ michael@0: enc_buf = crypto_alloc(octets_in_buffer+17); michael@0: if (enc_buf == NULL) michael@0: return 0; /* indicate bad parameters by returning null */ michael@0: memset(enc_buf, 0, octets_in_buffer); michael@0: michael@0: /* time repeated trials */ michael@0: v128_set_to_zero(&nonce); michael@0: timer = clock(); michael@0: for(i=0; i < num_trials; i++, nonce.v32[3] = i) { michael@0: /* length parameter to cipher_encrypt is in/out -- out is total, padded michael@0: * length -- so reset it each time. */ michael@0: unsigned octets_to_encrypt = octets_in_buffer; michael@0: michael@0: /* encrypt buffer with cipher */ michael@0: cipher_set_iv(cipher_array[cipher_index], &nonce); michael@0: cipher_encrypt(cipher_array[cipher_index], enc_buf, &octets_to_encrypt); michael@0: michael@0: /* choose a cipher at random from the array*/ michael@0: cipher_index = (*((uint32_t *)enc_buf)) % num_cipher; michael@0: } michael@0: timer = clock() - timer; michael@0: michael@0: free(enc_buf); michael@0: michael@0: if (timer == 0) { michael@0: /* Too fast! */ michael@0: return 0; michael@0: } michael@0: michael@0: return (uint64_t)CLOCKS_PER_SEC * num_trials * 8 * octets_in_buffer / timer; michael@0: } michael@0: michael@0: void michael@0: cipher_array_test_throughput(cipher_t *ca[], int num_cipher) { michael@0: int i; michael@0: int min_enc_len = 16; michael@0: int max_enc_len = 2048; /* should be a power of two */ michael@0: int num_trials = 1000000; michael@0: michael@0: printf("timing %s throughput with key length %d, array size %d:\n", michael@0: (ca[0])->type->description, (ca[0])->key_len, num_cipher); michael@0: fflush(stdout); michael@0: for (i=min_enc_len; i <= max_enc_len; i = i * 4) michael@0: printf("msg len: %d\tgigabits per second: %f\n", i, michael@0: cipher_array_bits_per_second(ca, num_cipher, i, num_trials) / 1e9); michael@0: michael@0: } michael@0: michael@0: err_status_t michael@0: cipher_driver_test_array_throughput(cipher_type_t *ct, michael@0: int klen, int num_cipher) { michael@0: cipher_t **ca = NULL; michael@0: err_status_t status; michael@0: michael@0: status = cipher_array_alloc_init(&ca, num_cipher, ct, klen); michael@0: if (status) { michael@0: printf("error: cipher_array_alloc_init() failed with error code %d\n", michael@0: status); michael@0: return status; michael@0: } michael@0: michael@0: cipher_array_test_throughput(ca, num_cipher); michael@0: michael@0: cipher_array_delete(ca, num_cipher); michael@0: michael@0: return err_status_ok; michael@0: }