The nettle-benchmark program currently uses the openssl low level cipher APIs for benchmarking. This means it always runs the generic software implementation, never able to take advantage of impls optimized for new hardware (eg AES-NI).
Rewriting it to use the higher EVP APIs means we can use the same code for all ciphers, and automatically trigger hardware optimized versions, giving a fairer comparison against openssl as commonly used in applications.
Use of the generic openssl impl can still be forced by setting an env variable OPENSSL_ia32cap="~0x200000200000000"
Signed-off-by: Daniel P. Berrange berrange@redhat.com --- examples/nettle-benchmark.c | 4 + examples/nettle-openssl.c | 247 ++++++++++++++++---------------------------- nettle-internal.h | 1 + 3 files changed, 92 insertions(+), 160 deletions(-)
diff --git a/examples/nettle-benchmark.c b/examples/nettle-benchmark.c index c00486cc..11f62709 100644 --- a/examples/nettle-benchmark.c +++ b/examples/nettle-benchmark.c @@ -723,6 +723,10 @@ main(int argc, char **argv) int c; const char *alg;
+#if WITH_OPENSSL + nettle_openssl_init(); +#endif + const struct nettle_hash *hashes[] = { &nettle_md2, &nettle_md4, &nettle_md5, diff --git a/examples/nettle-openssl.c b/examples/nettle-openssl.c index 86c5321c..3d7d4fa3 100644 --- a/examples/nettle-openssl.c +++ b/examples/nettle-openssl.c @@ -45,11 +45,9 @@
#include <assert.h>
-#include <openssl/aes.h> -#include <openssl/blowfish.h> -#include <openssl/des.h> -#include <openssl/cast.h> -#include <openssl/rc4.h> +#include <openssl/conf.h> +#include <openssl/evp.h> +#include <openssl/err.h>
#include <openssl/md5.h> #include <openssl/sha.h> @@ -64,273 +62,202 @@ static nettle_set_key_func openssl_aes192_set_encrypt_key; static nettle_set_key_func openssl_aes192_set_decrypt_key; static nettle_set_key_func openssl_aes256_set_encrypt_key; static nettle_set_key_func openssl_aes256_set_decrypt_key; + +struct AESCipher { + EVP_CIPHER_CTX *ctx; +}; + +void +nettle_openssl_init(void) +{ + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); +#if OPENSSL_VERSION_NUMBER >= 0x1010000 + CONF_modules_load_file(NULL, NULL, 0); +#else + OPENSSL_config(NULL); +#endif +} + +static void +openssl_evp_set_encrypt_key(void *ctx, const uint8_t *key, const EVP_CIPHER *cipher) +{ + EVP_CIPHER_CTX **ctxptr = ctx; + *ctxptr = EVP_CIPHER_CTX_new(); + assert(EVP_EncryptInit_ex(*ctxptr, cipher, NULL, key, NULL) == 1); + EVP_CIPHER_CTX_set_padding(*ctxptr, 0); +} +static void +openssl_evp_set_decrypt_key(void *ctx, const uint8_t *key, const EVP_CIPHER *cipher) +{ + EVP_CIPHER_CTX **ctxptr = ctx; + *ctxptr = EVP_CIPHER_CTX_new(); + assert(EVP_DecryptInit_ex(*ctxptr, cipher, NULL, key, NULL) == 1); + EVP_CIPHER_CTX_set_padding(*ctxptr, 0); +} static void openssl_aes128_set_encrypt_key(void *ctx, const uint8_t *key) { - AES_set_encrypt_key(key, 128, ctx); + openssl_evp_set_encrypt_key(ctx, key, EVP_aes_128_ecb()); } static void openssl_aes128_set_decrypt_key(void *ctx, const uint8_t *key) { - AES_set_decrypt_key(key, 128, ctx); + openssl_evp_set_decrypt_key(ctx, key, EVP_aes_128_ecb()); }
static void openssl_aes192_set_encrypt_key(void *ctx, const uint8_t *key) { - AES_set_encrypt_key(key, 192, ctx); + openssl_evp_set_encrypt_key(ctx, key, EVP_aes_192_ecb()); } static void openssl_aes192_set_decrypt_key(void *ctx, const uint8_t *key) { - AES_set_decrypt_key(key, 192, ctx); + openssl_evp_set_decrypt_key(ctx, key, EVP_aes_192_ecb()); }
static void openssl_aes256_set_encrypt_key(void *ctx, const uint8_t *key) { - AES_set_encrypt_key(key, 256, ctx); + openssl_evp_set_encrypt_key(ctx, key, EVP_aes_256_ecb()); } static void openssl_aes256_set_decrypt_key(void *ctx, const uint8_t *key) { - AES_set_decrypt_key(key, 256, ctx); + openssl_evp_set_decrypt_key(ctx, key, EVP_aes_256_ecb()); }
-static nettle_cipher_func openssl_aes_encrypt; static void -openssl_aes_encrypt(const void *ctx, size_t length, +openssl_evp_encrypt(const void *ctx, size_t length, uint8_t *dst, const uint8_t *src) { - assert (!(length % AES_BLOCK_SIZE)); - while (length) - { - AES_ecb_encrypt(src, dst, ctx, AES_ENCRYPT); - length -= AES_BLOCK_SIZE; - dst += AES_BLOCK_SIZE; - src += AES_BLOCK_SIZE; - } + EVP_CIPHER_CTX * const*ctxptr = ctx; + int len; + assert(EVP_EncryptUpdate(*ctxptr, dst, &len, src, length) == 1); }
-static nettle_cipher_func openssl_aes_decrypt; static void -openssl_aes_decrypt(const void *ctx, size_t length, +openssl_evp_decrypt(const void *ctx, size_t length, uint8_t *dst, const uint8_t *src) { - assert (!(length % AES_BLOCK_SIZE)); - while (length) - { - AES_ecb_encrypt(src, dst, ctx, AES_DECRYPT); - length -= AES_BLOCK_SIZE; - dst += AES_BLOCK_SIZE; - src += AES_BLOCK_SIZE; - } + EVP_CIPHER_CTX * const*ctxptr = ctx; + int len; + assert(EVP_DecryptUpdate(*ctxptr, dst, &len, src, length) == 1); }
const struct nettle_cipher nettle_openssl_aes128 = { - "openssl aes128", sizeof(AES_KEY), + "openssl aes128", sizeof(EVP_CIPHER_CTX **), 16, 16, openssl_aes128_set_encrypt_key, openssl_aes128_set_decrypt_key, - openssl_aes_encrypt, openssl_aes_decrypt + openssl_evp_encrypt, openssl_evp_decrypt };
const struct nettle_cipher nettle_openssl_aes192 = { - "openssl aes192", sizeof(AES_KEY), - /* Claim no block size, so that the benchmark doesn't try CBC mode - * (as openssl cipher + nettle cbc is somewhat pointless to - * benchmark). */ + "openssl aes192", sizeof(EVP_CIPHER_CTX **), 16, 24, openssl_aes192_set_encrypt_key, openssl_aes192_set_decrypt_key, - openssl_aes_encrypt, openssl_aes_decrypt + openssl_evp_encrypt, openssl_evp_decrypt };
const struct nettle_cipher nettle_openssl_aes256 = { - "openssl aes256", sizeof(AES_KEY), - /* Claim no block size, so that the benchmark doesn't try CBC mode - * (as openssl cipher + nettle cbc is somewhat pointless to - * benchmark). */ + "openssl aes256", sizeof(EVP_CIPHER_CTX **), 16, 32, openssl_aes256_set_encrypt_key, openssl_aes256_set_decrypt_key, - openssl_aes_encrypt, openssl_aes_decrypt + openssl_evp_encrypt, openssl_evp_decrypt };
/* Arcfour */ -static nettle_set_key_func openssl_arcfour128_set_key; static void -openssl_arcfour128_set_key(void *ctx, const uint8_t *key) +openssl_arcfour128_set_encrypt_key(void *ctx, const uint8_t *key) { - RC4_set_key(ctx, 16, key); + openssl_evp_set_encrypt_key(ctx, key, EVP_rc4()); }
-static nettle_crypt_func openssl_arcfour_crypt; static void -openssl_arcfour_crypt(void *ctx, size_t length, - uint8_t *dst, const uint8_t *src) +openssl_arcfour128_set_decrypt_key(void *ctx, const uint8_t *key) { - RC4(ctx, length, src, dst); + openssl_evp_set_decrypt_key(ctx, key, EVP_rc4()); }
const struct nettle_aead nettle_openssl_arcfour128 = { - "openssl arcfour128", sizeof(RC4_KEY), + "openssl arcfour128", sizeof(EVP_CIPHER_CTX **), 1, 16, 0, 0, - openssl_arcfour128_set_key, - openssl_arcfour128_set_key, + openssl_arcfour128_set_encrypt_key, + openssl_arcfour128_set_decrypt_key, NULL, NULL, - openssl_arcfour_crypt, - openssl_arcfour_crypt, + (nettle_crypt_func *)openssl_evp_encrypt, + (nettle_crypt_func *)openssl_evp_decrypt, NULL, };
/* Blowfish */ -static nettle_set_key_func openssl_bf128_set_key; static void -openssl_bf128_set_key(void *ctx, const uint8_t *key) +openssl_bf128_set_encrypt_key(void *ctx, const uint8_t *key) { - BF_set_key(ctx, 16, key); + openssl_evp_set_encrypt_key(ctx, key, EVP_bf_ecb()); }
-static nettle_cipher_func openssl_bf_encrypt; static void -openssl_bf_encrypt(const void *ctx, size_t length, - uint8_t *dst, const uint8_t *src) +openssl_bf128_set_decrypt_key(void *ctx, const uint8_t *key) { - assert (!(length % BF_BLOCK)); - while (length) - { - BF_ecb_encrypt(src, dst, ctx, BF_ENCRYPT); - length -= BF_BLOCK; - dst += BF_BLOCK; - src += BF_BLOCK; - } -} - -static nettle_cipher_func openssl_bf_decrypt; -static void -openssl_bf_decrypt(const void *ctx, size_t length, - uint8_t *dst, const uint8_t *src) -{ - assert (!(length % BF_BLOCK)); - while (length) - { - BF_ecb_encrypt(src, dst, ctx, BF_DECRYPT); - length -= BF_BLOCK; - dst += BF_BLOCK; - src += BF_BLOCK; - } + openssl_evp_set_decrypt_key(ctx, key, EVP_bf_ecb()); }
const struct nettle_cipher nettle_openssl_blowfish128 = { - "openssl bf128", sizeof(BF_KEY), + "openssl bf128", sizeof(EVP_CIPHER_CTX **), 8, 16, - openssl_bf128_set_key, openssl_bf128_set_key, - openssl_bf_encrypt, openssl_bf_decrypt + openssl_bf128_set_encrypt_key, openssl_bf128_set_decrypt_key, + openssl_evp_encrypt, openssl_evp_decrypt };
/* DES */ -static nettle_set_key_func openssl_des_set_key; -static void -openssl_des_set_key(void *ctx, const uint8_t *key) -{ - /* Not sure what "unchecked" means. We want to ignore parity bits, - but it would still make sense to check for weak keys. */ - /* Explicit cast used as I don't want to care about openssl's broken - array typedefs DES_cblock and const_DES_cblock. */ - DES_set_key_unchecked( (void *) key, ctx); -} - -#define DES_BLOCK_SIZE 8 - -static nettle_cipher_func openssl_des_encrypt; static void -openssl_des_encrypt(const void *ctx, size_t length, - uint8_t *dst, const uint8_t *src) +openssl_des_set_encrypt_key(void *ctx, const uint8_t *key) { - assert (!(length % DES_BLOCK_SIZE)); - while (length) - { - DES_ecb_encrypt((void *) src, (void *) dst, - (void *) ctx, DES_ENCRYPT); - length -= DES_BLOCK_SIZE; - dst += DES_BLOCK_SIZE; - src += DES_BLOCK_SIZE; - } + openssl_evp_set_encrypt_key(ctx, key, EVP_des_ecb()); }
-static nettle_cipher_func openssl_des_decrypt; static void -openssl_des_decrypt(const void *ctx, size_t length, - uint8_t *dst, const uint8_t *src) +openssl_des_set_decrypt_key(void *ctx, const uint8_t *key) { - assert (!(length % DES_BLOCK_SIZE)); - while (length) - { - DES_ecb_encrypt((void *) src, (void *) dst, - (void *) ctx, DES_DECRYPT); - length -= DES_BLOCK_SIZE; - dst += DES_BLOCK_SIZE; - src += DES_BLOCK_SIZE; - } + openssl_evp_set_decrypt_key(ctx, key, EVP_des_ecb()); }
const struct nettle_cipher nettle_openssl_des = { - "openssl des", sizeof(DES_key_schedule), + "openssl des", sizeof(EVP_CIPHER_CTX **), 8, 8, - openssl_des_set_key, openssl_des_set_key, - openssl_des_encrypt, openssl_des_decrypt + openssl_des_set_encrypt_key, openssl_des_set_decrypt_key, + openssl_evp_encrypt, openssl_evp_decrypt };
/* Cast128 */ -static nettle_set_key_func openssl_cast128_set_key; static void -openssl_cast128_set_key(void *ctx, const uint8_t *key) +openssl_cast128_set_encrypt_key(void *ctx, const uint8_t *key) { - CAST_set_key(ctx, 16, key); + openssl_evp_set_encrypt_key(ctx, key, EVP_cast5_ecb()); }
-static nettle_cipher_func openssl_cast_encrypt; static void -openssl_cast_encrypt(const void *ctx, size_t length, - uint8_t *dst, const uint8_t *src) +openssl_cast128_set_decrypt_key(void *ctx, const uint8_t *key) { - assert (!(length % CAST_BLOCK)); - while (length) - { - CAST_ecb_encrypt(src, dst, ctx, CAST_ENCRYPT); - length -= CAST_BLOCK; - dst += CAST_BLOCK; - src += CAST_BLOCK; - } -} - -static nettle_cipher_func openssl_cast_decrypt; -static void -openssl_cast_decrypt(const void *ctx, size_t length, - uint8_t *dst, const uint8_t *src) -{ - assert (!(length % CAST_BLOCK)); - while (length) - { - CAST_ecb_encrypt(src, dst, ctx, CAST_DECRYPT); - length -= CAST_BLOCK; - dst += CAST_BLOCK; - src += CAST_BLOCK; - } + openssl_evp_set_decrypt_key(ctx, key, EVP_cast5_ecb()); }
const struct nettle_cipher nettle_openssl_cast128 = { - "openssl cast128", sizeof(CAST_KEY), - 8, CAST_KEY_LENGTH, - openssl_cast128_set_key, openssl_cast128_set_key, - openssl_cast_encrypt, openssl_cast_decrypt + "openssl cast128", sizeof(EVP_CIPHER_CTX **), + 8, 16, + openssl_cast128_set_encrypt_key, openssl_cast128_set_decrypt_key, + openssl_evp_encrypt, openssl_evp_decrypt };
/* Hash functions */ diff --git a/nettle-internal.h b/nettle-internal.h index 9c4c699d..0b0d25c9 100644 --- a/nettle-internal.h +++ b/nettle-internal.h @@ -79,6 +79,7 @@ extern const struct nettle_aead nettle_salsa20r12;
/* Glue to openssl, for comparative benchmarking. Code in * examples/nettle-openssl.c. */ +extern void nettle_openssl_init(void); extern const struct nettle_cipher nettle_openssl_aes128; extern const struct nettle_cipher nettle_openssl_aes192; extern const struct nettle_cipher nettle_openssl_aes256;
"Daniel P. Berrange" berrange@redhat.com writes:
The nettle-benchmark program currently uses the openssl low level cipher APIs for benchmarking. This means it always runs the generic software implementation, never able to take advantage of impls optimized for new hardware (eg AES-NI).
Rewriting it to use the higher EVP APIs means we can use the same code for all ciphers, and automatically trigger hardware optimized versions, giving a fairer comparison against openssl as commonly used in applications.
Use of the generic openssl impl can still be forced by setting an env variable OPENSSL_ia32cap="~0x200000200000000"
I'm about to apply this. On my machine (with an intel "i3-5010U CPU @ 2.10GHz"), I get roughly 1.5 GB/s with nettle's aesni, and with your benchmarking changes, I get twice as much with openssl, 3 GB/s. Benchmarking the simple but not very useful ecb mode. So there's clearly some room for further optimization.
I have a few comments on the code:
+struct AESCipher {
- EVP_CIPHER_CTX *ctx;
+};
This struct seems unused. I'd prefer renaming it to something like openssl_cipher_ctx, and use it in the rest of the code.
+static void +openssl_evp_set_encrypt_key(void *ctx, const uint8_t *key, const EVP_CIPHER *cipher) +{
- EVP_CIPHER_CTX **ctxptr = ctx;
- *ctxptr = EVP_CIPHER_CTX_new();
Then this would be rewritten as
struct openssl_cipher_ctx *openssl = (struct openssl_cipher_ctx *) ctx; openssl->ctx = EVP_CIPHER_CTX_new();
(possibly with some renaming of args, to avoid to many different ctxs.
And similarly with the other juggling of the EVP_CIPHER_CTX** type.
The configure script currently checks for these headers,
AC_CHECK_HEADERS([openssl/blowfish.h openssl/des.h openssl/cast.h openssl/aes.h openssl/ecdsa.h]
I think all the cipher header files should be deleted here, replaced with a check for openssl/evp.h, do you agree?
Finally, it seems appropriate to add you and/or Redhat to the (c)-header of examples/nettle-openssl.c, can you send me the correct line? Do you or Redhat own the copyright for this rewrite?
I've done these fixes, pushed to the branch openssl-benchmark-update in the public repo. I'll merge to master as soon as I have the correct copyright info.
Thanks! /Niels
On Sat, Aug 26, 2017 at 11:06:30AM +0200, Niels Möller wrote:
"Daniel P. Berrange" berrange@redhat.com writes:
The nettle-benchmark program currently uses the openssl low level cipher APIs for benchmarking. This means it always runs the generic software implementation, never able to take advantage of impls optimized for new hardware (eg AES-NI).
Rewriting it to use the higher EVP APIs means we can use the same code for all ciphers, and automatically trigger hardware optimized versions, giving a fairer comparison against openssl as commonly used in applications.
Use of the generic openssl impl can still be forced by setting an env variable OPENSSL_ia32cap="~0x200000200000000"
I'm about to apply this. On my machine (with an intel "i3-5010U CPU @ 2.10GHz"), I get roughly 1.5 GB/s with nettle's aesni, and with your benchmarking changes, I get twice as much with openssl, 3 GB/s. Benchmarking the simple but not very useful ecb mode. So there's clearly some room for further optimization.
I have a few comments on the code:
+struct AESCipher {
- EVP_CIPHER_CTX *ctx;
+};
This struct seems unused. I'd prefer renaming it to something like openssl_cipher_ctx, and use it in the rest of the code.
+static void +openssl_evp_set_encrypt_key(void *ctx, const uint8_t *key, const EVP_CIPHER *cipher) +{
- EVP_CIPHER_CTX **ctxptr = ctx;
- *ctxptr = EVP_CIPHER_CTX_new();
Then this would be rewritten as
struct openssl_cipher_ctx *openssl = (struct openssl_cipher_ctx *) ctx; openssl->ctx = EVP_CIPHER_CTX_new();
(possibly with some renaming of args, to avoid to many different ctxs.
And similarly with the other juggling of the EVP_CIPHER_CTX** type.
Yep, that's fine.
The configure script currently checks for these headers,
AC_CHECK_HEADERS([openssl/blowfish.h openssl/des.h openssl/cast.h openssl/aes.h openssl/ecdsa.h]
I think all the cipher header files should be deleted here, replaced with a check for openssl/evp.h, do you agree?
Yes, I missed that.
Finally, it seems appropriate to add you and/or Redhat to the (c)-header of examples/nettle-openssl.c, can you send me the correct line? Do you or Redhat own the copyright for this rewrite?
It is under Red Hat copyright, so you can add:
Copyright (C) 2017 Red Hat, Inc.
Regards, Daniel
"Daniel P. Berrange" berrange@redhat.com writes:
On Sat, Aug 26, 2017 at 11:06:30AM +0200, Niels Möller wrote:
Finally, it seems appropriate to add you and/or Redhat to the (c)-header of examples/nettle-openssl.c, can you send me the correct line? Do you or Redhat own the copyright for this rewrite?
It is under Red Hat copyright, so you can add:
Copyright (C) 2017 Red Hat, Inc.
Thanks, pushed now.
BTW, with recent openssl (it seems I have version 1.1.0 of the libssl-dev package), I get a deprecation warning on the use of RSA_generate_key in examples/hogweed-benchmark.c. I guess it should be replaced by RSA_new + RSA_generate_key_ex, but I'm not really familiar with openssl interfaces. To keep consistent benchmarks, I think it's best to stick to the good old public exponent of 65537.
Regards, /Niels
nettle-bugs@lists.lysator.liu.se