From 607a46d003f472d4bce646f3df6e85725094d68a Mon Sep 17 00:00:00 2001 From: Mohammed Alhabib Date: Tue, 29 Oct 2024 12:00:54 +0300 Subject: [PATCH] apps/speed.c: Fix the benchmarking for AEAD ciphers Fixed the benchmarking for the evp aead interface for ccm, gcm, ocb, and siv, where decryption fails when executing `openssl speed -evp aes-128-ccm -decrypt` and `openssl speed -evp aes-128-gcm -decrypt`. Related issues are [24686](https://github.com/openssl/openssl/issues/24686) and [24250](https://github.com/openssl/openssl/issues/24250). Now both encryption and decryption, with or without AAD, executes correctly without issues. Reviewed-by: Tom Cosgrove Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/25823) --- apps/speed.c | 360 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 285 insertions(+), 75 deletions(-) diff --git a/apps/speed.c b/apps/speed.c index c3bc223a9d7c4..4b24cd37a5a5c 100644 --- a/apps/speed.c +++ b/apps/speed.c @@ -515,6 +515,14 @@ static double sigs_results[MAX_SIG_NUM][3]; /* keygen, sign, verify */ #define COND(unused_cond) (run && count < (testmode ? 1 : INT_MAX)) #define COUNT(d) (count) +#define TAG_LEN 16 + +static unsigned int mode_op; /* AE Mode of operation */ +static unsigned int aead = 0; /* AEAD flag */ +static unsigned char aead_iv[12]; /* For AEAD modes */ +static unsigned char aad[EVP_AEAD_TLS1_AAD_LEN] = { 0xcc }; +static int aead_ivlen = sizeof(aead_iv); + typedef struct loopargs_st { ASYNC_JOB *inprogress_job; ASYNC_WAIT_CTX *wait_ctx; @@ -523,6 +531,7 @@ typedef struct loopargs_st { unsigned char *buf_malloc; unsigned char *buf2_malloc; unsigned char *key; + unsigned char tag[TAG_LEN]; size_t buflen; size_t sigsize; size_t encsize; @@ -875,12 +884,8 @@ static int EVP_Update_loop(void *args) unsigned char *buf = tempargs->buf; EVP_CIPHER_CTX *ctx = tempargs->ctx; int outl, count, rc; - unsigned char faketag[16] = { 0xcc }; if (decrypt) { - if (EVP_CIPHER_get_flags(EVP_CIPHER_CTX_get0_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) { - (void)EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, sizeof(faketag), faketag); - } for (count = 0; COND(c[D_EVP][testnum]); count++) { rc = EVP_DecryptUpdate(ctx, buf, &outl, buf, lengths[testnum]); if (rc != 1) { @@ -908,44 +913,71 @@ static int EVP_Update_loop(void *args) } /* + * To make AEAD benchmarking more relevant perform TLS-like operations, + * 13-byte AAD followed by payload. But don't use TLS-formatted AAD, as + * payload length is not actually limited by 16KB... * CCM does not support streaming. For the purpose of performance measurement, * each message is encrypted using the same (key,iv)-pair. Do not use this * code in your application. */ -static int EVP_Update_loop_ccm(void *args) +static int EVP_Update_loop_aead_enc(void *args) { loopargs_t *tempargs = *(loopargs_t **) args; unsigned char *buf = tempargs->buf; + unsigned char *key = tempargs->key; EVP_CIPHER_CTX *ctx = tempargs->ctx; - int outl, count, realcount = 0, final; - unsigned char tag[12]; + int outl, count, realcount = 0; - if (decrypt) { - for (count = 0; COND(c[D_EVP][testnum]); count++) { - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, sizeof(tag), - tag) > 0 - /* reset iv */ - && EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv) > 0 - /* counter is reset on every update */ - && EVP_DecryptUpdate(ctx, buf, &outl, buf, lengths[testnum]) > 0) - realcount++; + for (count = 0; COND(c[D_EVP][testnum]); count++) { + /* Set length of iv (Doesn't apply to SIV mode) */ + if (mode_op != EVP_CIPH_SIV_MODE) { + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, + aead_ivlen, NULL)) { + BIO_printf(bio_err, "\nFailed to set iv length\n"); + dofail(); + exit(1); + } } - } else { - for (count = 0; COND(c[D_EVP][testnum]); count++) { - /* restore iv length field */ - if (EVP_EncryptUpdate(ctx, NULL, &outl, NULL, lengths[testnum]) > 0 - /* counter is reset on every update */ - && EVP_EncryptUpdate(ctx, buf, &outl, buf, lengths[testnum]) > 0) - realcount++; + /* Set tag_len (Not for GCM/SIV at encryption stage) */ + if (mode_op != EVP_CIPH_GCM_MODE + && mode_op != EVP_CIPH_SIV_MODE + && mode_op != EVP_CIPH_GCM_SIV_MODE) { + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, + TAG_LEN, NULL)) { + BIO_printf(bio_err, "\nFailed to set tag length\n"); + dofail(); + exit(1); + } + } + if (!EVP_CipherInit_ex(ctx, NULL, NULL, key, aead_iv, -1)) { + BIO_printf(bio_err, "\nFailed to set key and iv\n"); + dofail(); + exit(1); + } + /* Set total length of input. Only required for CCM */ + if (mode_op == EVP_CIPH_CCM_MODE) { + if (!EVP_EncryptUpdate(ctx, NULL, &outl, + NULL, lengths[testnum])) { + BIO_printf(bio_err, "\nCouldn't set input text length\n"); + dofail(); + exit(1); + } } + if (aead) { + if (!EVP_EncryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) { + BIO_printf(bio_err, "\nCouldn't insert AAD when encrypting\n"); + dofail(); + exit(1); + } + } + if (!EVP_EncryptUpdate(ctx, buf, &outl, buf, lengths[testnum])) { + BIO_printf(bio_err, "\nFailed to encrypt the data\n"); + dofail(); + exit(1); + } + if (EVP_EncryptFinal_ex(ctx, buf, &outl)) + realcount++; } - if (decrypt) - final = EVP_DecryptFinal_ex(ctx, buf, &outl); - else - final = EVP_EncryptFinal_ex(ctx, buf, &outl); - - if (final == 0) - BIO_printf(bio_err, "Error finalizing ccm loop\n"); return realcount; } @@ -953,34 +985,87 @@ static int EVP_Update_loop_ccm(void *args) * To make AEAD benchmarking more relevant perform TLS-like operations, * 13-byte AAD followed by payload. But don't use TLS-formatted AAD, as * payload length is not actually limited by 16KB... + * CCM does not support streaming. For the purpose of performance measurement, + * each message is decrypted using the same (key,iv)-pair. Do not use this + * code in your application. + * For decryption, we will use buf2 to preserve the input text in buf. */ -static int EVP_Update_loop_aead(void *args) +static int EVP_Update_loop_aead_dec(void *args) { loopargs_t *tempargs = *(loopargs_t **) args; unsigned char *buf = tempargs->buf; + unsigned char *outbuf = tempargs->buf2; + unsigned char *key = tempargs->key; + unsigned char tag[TAG_LEN]; EVP_CIPHER_CTX *ctx = tempargs->ctx; int outl, count, realcount = 0; - unsigned char aad[13] = { 0xcc }; - unsigned char faketag[16] = { 0xcc }; - if (decrypt) { - for (count = 0; COND(c[D_EVP][testnum]); count++) { - if (EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv) > 0 - && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, - sizeof(faketag), faketag) > 0 - && EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad)) > 0 - && EVP_DecryptUpdate(ctx, buf, &outl, buf, lengths[testnum]) > 0 - && EVP_DecryptFinal_ex(ctx, buf + outl, &outl) > 0) - realcount++; + for (count = 0; COND(c[D_EVP][testnum]); count++) { + /* Set the length of iv (Doesn't apply to SIV mode) */ + if (mode_op != EVP_CIPH_SIV_MODE) { + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, + aead_ivlen, NULL)) { + BIO_printf(bio_err, "\nFailed to set iv length\n"); + dofail(); + exit(1); + } } - } else { - for (count = 0; COND(c[D_EVP][testnum]); count++) { - if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv) > 0 - && EVP_EncryptUpdate(ctx, NULL, &outl, aad, sizeof(aad)) > 0 - && EVP_EncryptUpdate(ctx, buf, &outl, buf, lengths[testnum]) > 0 - && EVP_EncryptFinal_ex(ctx, buf + outl, &outl) > 0) - realcount++; + + /* Set the tag length (Doesn't apply to SIV mode) */ + if (mode_op != EVP_CIPH_SIV_MODE + && mode_op != EVP_CIPH_GCM_MODE + && mode_op != EVP_CIPH_GCM_SIV_MODE) { + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, + TAG_LEN, NULL)) { + BIO_printf(bio_err, "\nFailed to set tag length\n"); + dofail(); + exit(1); + } } + if (!EVP_CipherInit_ex(ctx, NULL, NULL, key, aead_iv, -1)) { + BIO_printf(bio_err, "\nFailed to set key and iv\n"); + dofail(); + exit(1); + } + /* Set iv before decryption (Doesn't apply to SIV mode) */ + if (mode_op != EVP_CIPH_SIV_MODE) { + if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, aead_iv)) { + BIO_printf(bio_err, "\nFailed to set iv\n"); + dofail(); + exit(1); + } + } + memcpy(tag, tempargs->tag, TAG_LEN); + + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, + TAG_LEN, tag)) { + BIO_printf(bio_err, "\nFailed to set tag\n"); + dofail(); + exit(1); + } + /* Set the total length of cipher text. Only required for CCM */ + if (mode_op == EVP_CIPH_CCM_MODE) { + if (!EVP_DecryptUpdate(ctx, NULL, &outl, + NULL, lengths[testnum])) { + BIO_printf(bio_err, "\nCouldn't set cipher text length\n"); + dofail(); + exit(1); + } + } + if (aead) { + if (!EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) { + BIO_printf(bio_err, "\nCouldn't insert AAD when decrypting\n"); + dofail(); + exit(1); + } + } + if (!EVP_DecryptUpdate(ctx, outbuf, &outl, buf, lengths[testnum])) { + BIO_printf(bio_err, "\nFailed to decrypt the data\n"); + dofail(); + exit(1); + } + if (EVP_DecryptFinal_ex(ctx, outbuf, &outl)) + realcount++; } return realcount; } @@ -1803,14 +1888,14 @@ int speed_main(int argc, char **argv) OPTION_CHOICE o; int async_init = 0, multiblock = 0, pr_header = 0; uint8_t doit[ALGOR_NUM] = { 0 }; - int ret = 1, misalign = 0, lengths_single = 0, aead = 0; + int ret = 1, misalign = 0, lengths_single = 0; STACK_OF(EVP_KEM) *kem_stack = NULL; STACK_OF(EVP_SIGNATURE) *sig_stack = NULL; long count = 0; unsigned int size_num = SIZE_NUM; unsigned int i, k, loopargs_len = 0, async_jobs = 0; unsigned int idx; - int keylen; + int keylen = 0; int buflen; size_t declen; BIGNUM *bn = NULL; @@ -2839,12 +2924,20 @@ int speed_main(int argc, char **argv) } } + /*- + * There are three scenarios for D_EVP: + * 1- Using authenticated encryption (AE) e.g. CCM, GCM, OCB etc. + * 2- Using AE + associated data (AD) i.e. AEAD using CCM, GCM, OCB etc. + * 3- Not using AE or AD e.g. ECB, CBC, CFB etc. + */ if (doit[D_EVP]) { if (evp_cipher != NULL) { - int (*loopfunc) (void *) = EVP_Update_loop; + int (*loopfunc) (void *); + int outlen = 0; + unsigned int ae_mode = 0; - if (multiblock && (EVP_CIPHER_get_flags(evp_cipher) & - EVP_CIPH_FLAG_TLS1_1_MULTIBLOCK)) { + if (multiblock && (EVP_CIPHER_get_flags(evp_cipher) + & EVP_CIPH_FLAG_TLS1_1_MULTIBLOCK)) { multiblock_speed(evp_cipher, lengths_single, &seconds); ret = 0; goto end; @@ -2852,16 +2945,27 @@ int speed_main(int argc, char **argv) names[D_EVP] = EVP_CIPHER_get0_name(evp_cipher); - if (EVP_CIPHER_get_mode(evp_cipher) == EVP_CIPH_CCM_MODE) { - loopfunc = EVP_Update_loop_ccm; - } else if (aead && (EVP_CIPHER_get_flags(evp_cipher) & - EVP_CIPH_FLAG_AEAD_CIPHER)) { - loopfunc = EVP_Update_loop_aead; + mode_op = EVP_CIPHER_get_mode(evp_cipher); + + if (aead) { if (lengths == lengths_list) { lengths = aead_lengths_list; size_num = OSSL_NELEM(aead_lengths_list); } } + if (mode_op == EVP_CIPH_GCM_MODE + || mode_op == EVP_CIPH_CCM_MODE + || mode_op == EVP_CIPH_OCB_MODE + || mode_op == EVP_CIPH_SIV_MODE + || mode_op == EVP_CIPH_GCM_SIV_MODE) { + ae_mode = 1; + if (decrypt) + loopfunc = EVP_Update_loop_aead_dec; + else + loopfunc = EVP_Update_loop_aead_enc; + } else { + loopfunc = EVP_Update_loop; + } for (testnum = 0; testnum < size_num; testnum++) { print_message(names[D_EVP], lengths[testnum], seconds.sym); @@ -2872,38 +2976,145 @@ int speed_main(int argc, char **argv) BIO_printf(bio_err, "\nEVP_CIPHER_CTX_new failure\n"); exit(1); } + + /* + * For AE modes, we must first encrypt the data to get + * a valid tag that enables us to decrypt. If we don't + * encrypt first, we won't have a valid tag that enables + * authenticity and hence decryption will fail. + */ if (!EVP_CipherInit_ex(loopargs[k].ctx, evp_cipher, NULL, - NULL, iv, decrypt ? 0 : 1)) { - BIO_printf(bio_err, "\nEVP_CipherInit_ex failure\n"); + NULL, NULL, ae_mode ? 1 : !decrypt)) { + BIO_printf(bio_err, "\nCouldn't init the context\n"); dofail(); exit(1); } + /* Padding isn't needed */ EVP_CIPHER_CTX_set_padding(loopargs[k].ctx, 0); keylen = EVP_CIPHER_CTX_get_key_length(loopargs[k].ctx); loopargs[k].key = app_malloc(keylen, "evp_cipher key"); EVP_CIPHER_CTX_rand_key(loopargs[k].ctx, loopargs[k].key); - if (!EVP_CipherInit_ex(loopargs[k].ctx, NULL, NULL, - loopargs[k].key, NULL, -1)) { - BIO_printf(bio_err, "\nEVP_CipherInit_ex failure\n"); - dofail(); - exit(1); - } - OPENSSL_clear_free(loopargs[k].key, keylen); - /* GCM-SIV/SIV mode only allows for a single Update operation */ - if (EVP_CIPHER_get_mode(evp_cipher) == EVP_CIPH_SIV_MODE - || EVP_CIPHER_get_mode(evp_cipher) == EVP_CIPH_GCM_SIV_MODE) - (void)EVP_CIPHER_CTX_ctrl(loopargs[k].ctx, - EVP_CTRL_SET_SPEED, 1, NULL); + if (!ae_mode) { + if (!EVP_CipherInit_ex(loopargs[k].ctx, NULL, NULL, + loopargs[k].key, NULL, -1)) { + BIO_printf(bio_err, "\nFailed to set the key\n"); + dofail(); + exit(1); + } + } else if (mode_op == EVP_CIPH_SIV_MODE + || mode_op == EVP_CIPH_GCM_SIV_MODE) { + EVP_CIPHER_CTX_ctrl(loopargs[k].ctx, + EVP_CTRL_SET_SPEED, 1, NULL); + } + if (ae_mode && decrypt) { + /* Set length of iv (Doesn't apply to SIV mode) */ + if (mode_op != EVP_CIPH_SIV_MODE) { + if (!EVP_CIPHER_CTX_ctrl(loopargs[k].ctx, + EVP_CTRL_AEAD_SET_IVLEN, + aead_ivlen, NULL)) { + BIO_printf(bio_err, "\nFailed to set iv length\n"); + dofail(); + exit(1); + } + } + /* Set tag_len (Not for GCM/SIV at encryption stage) */ + if (mode_op != EVP_CIPH_GCM_MODE + && mode_op != EVP_CIPH_SIV_MODE + && mode_op != EVP_CIPH_GCM_SIV_MODE) { + if (!EVP_CIPHER_CTX_ctrl(loopargs[k].ctx, + EVP_CTRL_AEAD_SET_TAG, + TAG_LEN, NULL)) { + BIO_printf(bio_err, + "\nFailed to set tag length\n"); + dofail(); + exit(1); + } + } + if (!EVP_CipherInit_ex(loopargs[k].ctx, NULL, NULL, + loopargs[k].key, aead_iv, -1)) { + BIO_printf(bio_err, "\nFailed to set the key\n"); + dofail(); + exit(1); + } + /* Set total length of input. Only required for CCM */ + if (mode_op == EVP_CIPH_CCM_MODE) { + if (!EVP_EncryptUpdate(loopargs[k].ctx, NULL, + &outlen, NULL, + lengths[testnum])) { + BIO_printf(bio_err, + "\nCouldn't set input text length\n"); + dofail(); + exit(1); + } + } + if (aead) { + if (!EVP_EncryptUpdate(loopargs[k].ctx, NULL, + &outlen, aad, sizeof(aad))) { + BIO_printf(bio_err, + "\nCouldn't insert AAD when encrypting\n"); + dofail(); + exit(1); + } + } + if (!EVP_EncryptUpdate(loopargs[k].ctx, loopargs[k].buf, + &outlen, loopargs[k].buf, + lengths[testnum])) { + BIO_printf(bio_err, + "\nFailed to to encrypt the data\n"); + dofail(); + exit(1); + } + + if (!EVP_EncryptFinal_ex(loopargs[k].ctx, + loopargs[k].buf, &outlen)) { + BIO_printf(bio_err, + "\nFailed finalize the encryption\n"); + dofail(); + exit(1); + } + + if (!EVP_CIPHER_CTX_ctrl(loopargs[k].ctx, EVP_CTRL_AEAD_GET_TAG, + TAG_LEN, &loopargs[k].tag)) { + BIO_printf(bio_err, "\nFailed to get the tag\n"); + dofail(); + exit(1); + } + + EVP_CIPHER_CTX_free(loopargs[k].ctx); + loopargs[k].ctx = EVP_CIPHER_CTX_new(); + if (loopargs[k].ctx == NULL) { + BIO_printf(bio_err, + "\nEVP_CIPHER_CTX_new failure\n"); + exit(1); + } + if (!EVP_CipherInit_ex(loopargs[k].ctx, evp_cipher, + NULL, NULL, NULL, 0)) { + BIO_printf(bio_err, + "\nFailed initializing the context\n"); + dofail(); + exit(1); + } + + EVP_CIPHER_CTX_set_padding(loopargs[k].ctx, 0); + + /* GCM-SIV/SIV only allows for a single Update operation */ + if (mode_op == EVP_CIPH_SIV_MODE + || mode_op == EVP_CIPH_GCM_SIV_MODE) + EVP_CIPHER_CTX_ctrl(loopargs[k].ctx, + EVP_CTRL_SET_SPEED, 1, NULL); + } } Time_F(START); count = run_benchmark(async_jobs, loopfunc, loopargs); d = Time_F(STOP); - for (k = 0; k < loopargs_len; k++) + for (k = 0; k < loopargs_len; k++) { + OPENSSL_clear_free(loopargs[k].key, keylen); EVP_CIPHER_CTX_free(loopargs[k].ctx); + } print_result(D_EVP, testnum, count, d); } } else if (evp_md_name != NULL) { @@ -4881,7 +5092,6 @@ static void multiblock_speed(const EVP_CIPHER *evp_cipher, int lengths_single, print_message(alg_name, mblengths[j], seconds->sym); Time_F(START); for (count = 0; run && COND(count); count++) { - unsigned char aad[EVP_AEAD_TLS1_AAD_LEN]; EVP_CTRL_TLS1_1_MULTIBLOCK_PARAM mb_param; size_t len = mblengths[j]; int packlen;