From b85465757f3e3ef093075946a088afdc1112d5f0 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 28 Mar 2026 20:32:13 +0100 Subject: [PATCH] PG-2278 Re-use cipher contexts for SMGR encryption Profiling with perf revealed that EVP_CipherInit_ex() showed up quite a lot when encrypting and decrypting tables, so to decrease how much it shows up we make sure to only allocate and initialize contexts for SMGR encryption once on loading pg_tde and then re-use those contexts but re-initializing them with different keys and IVs on use, which is a lot cheaper than doing a full initialization. This way of calling EVP_CipherInit_ex() is poorly documented in OpenSSL. WAL encryption already has logic for reusing contexts so this optimization is not as relevant there. Also removes an no longer true comment related to the WAL encryption. --- src/encryption/enc_aes.c | 53 ++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/encryption/enc_aes.c b/src/encryption/enc_aes.c index dd6ec6a1..a09f458b 100644 --- a/src/encryption/enc_aes.c +++ b/src/encryption/enc_aes.c @@ -32,26 +32,42 @@ * 16 byte blocks. */ -static const EVP_CIPHER *cipher_cbc_128 = NULL; static const EVP_CIPHER *cipher_gcm_128 = NULL; static const EVP_CIPHER *cipher_ctr_ecb_128 = NULL; -static const EVP_CIPHER *cipher_cbc_256 = NULL; static const EVP_CIPHER *cipher_gcm_256 = NULL; static const EVP_CIPHER *cipher_ctr_ecb_256 = NULL; +static EVP_CIPHER_CTX *ctx_cbc_128 = NULL; +static EVP_CIPHER_CTX *ctx_cbc_256 = NULL; + +static EVP_CIPHER_CTX * +AesCbcInitCtx(const EVP_CIPHER *cipher, const char *name) +{ + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, 1) == 0) + ereport(ERROR, + errmsg("EVP_CipherInit_ex of %s failed. OpenSSL error: %s", + name, ERR_error_string(ERR_get_error(), NULL))); + + EVP_CIPHER_CTX_set_padding(ctx, 0); + + return ctx; +} + void AesInit(void) { OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); - cipher_cbc_128 = EVP_aes_128_cbc(); cipher_gcm_128 = EVP_aes_128_gcm(); cipher_ctr_ecb_128 = EVP_aes_128_ecb(); + ctx_cbc_128 = AesCbcInitCtx(EVP_aes_128_cbc(), "AES-128-CBC"); - cipher_cbc_256 = EVP_aes_256_cbc(); cipher_gcm_256 = EVP_aes_256_gcm(); cipher_ctr_ecb_256 = EVP_aes_256_ecb(); + ctx_cbc_256 = AesCbcInitCtx(EVP_aes_256_cbc(), "AES-256-CBC"); } static void @@ -63,10 +79,6 @@ AesEcbEncrypt(EVP_CIPHER_CTX **ctxPtr, const unsigned char *key, int key_len, co Assert(key_len == 16 || key_len == 32); cipher = key_len == 32 ? cipher_ctr_ecb_256 : cipher_ctr_ecb_128; - /* - * TODO: Currently, only Ecb (WAL) use cached context. This caching was - * done for optimisation. Do we need it anymore? - */ if (*ctxPtr == NULL) { Assert(cipher != NULL); @@ -89,28 +101,29 @@ AesEcbEncrypt(EVP_CIPHER_CTX **ctxPtr, const unsigned char *key, int key_len, co Assert(out_len == in_len); } +/* + * Used to encrypt or decrypt a page in shared buffers + * + * For performance reasons the cipher context is created once on startup and + * re-used with different key and IV. + */ static void AesRunCbc(int enc, const unsigned char *key, int key_len, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out) { int out_len; int out_len_final; - EVP_CIPHER_CTX *ctx = NULL; - const EVP_CIPHER *cipher; + EVP_CIPHER_CTX *ctx; Assert(key_len == 16 || key_len == 32); - cipher = key_len == 32 ? cipher_cbc_256 : cipher_cbc_128; - - Assert(cipher != NULL); - Assert(in_len % EVP_CIPHER_block_size(cipher) == 0); + ctx = key_len == 32 ? ctx_cbc_256 : ctx_cbc_128; - ctx = EVP_CIPHER_CTX_new(); + Assert(ctx != NULL); + Assert(in_len % EVP_CIPHER_CTX_block_size(ctx) == 0); - if (EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, enc) == 0) + if (EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, enc) == 0) ereport(ERROR, errmsg("EVP_CipherInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); - EVP_CIPHER_CTX_set_padding(ctx, 0); - if (EVP_CipherUpdate(ctx, out, &out_len, in, in_len) == 0) ereport(ERROR, errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -121,12 +134,10 @@ AesRunCbc(int enc, const unsigned char *key, int key_len, const unsigned char *i /* * We encrypt one block (16 bytes) Our expectation is that the result - * should also be 16 bytes, without any additional padding + * should also be 16 bytes, without any additional padding. */ out_len += out_len_final; Assert(in_len == out_len); - - EVP_CIPHER_CTX_free(ctx); } void