@@ -305,10 +305,12 @@ class FunctionEncrypt : public IFunction
305305 const bool iv_is_constant = iv_column && isColumnConst (*iv_column);
306306 // / Avoid re-initializing cipher with the same key/IV on every row (expensive with OpenSSL 3).
307307 const bool can_reuse_context = (mode != CipherMode::RFC5116_AEAD_AES_GCM) && key_is_constant && (!iv_column || iv_is_constant);
308+ // / Reuse only key schedule even if IV changes per row (still cheaper than full init).
309+ const bool can_reuse_key_schedule = (mode != CipherMode::RFC5116_AEAD_AES_GCM) && key_is_constant;
308310
309311 StringRef const_key_value{};
310312 StringRef const_iv_value{};
311- if (can_reuse_context )
313+ if (can_reuse_key_schedule )
312314 {
313315 const_key_value = key_holder.setKey (key_size, key_column->getDataAt (0 ));
314316 if constexpr (mode != CipherMode::MySQLCompatibility)
@@ -317,7 +319,7 @@ class FunctionEncrypt : public IFunction
317319 throw Exception (ErrorCodes::BAD_ARGUMENTS, " Invalid key size {} != expected size {}" , const_key_value.size , key_size);
318320 }
319321
320- if (iv_column)
322+ if (iv_column && iv_is_constant )
321323 {
322324 const_iv_value = iv_column->getDataAt (0 );
323325 if (const_iv_value.size == 0 )
@@ -347,6 +349,22 @@ class FunctionEncrypt : public IFunction
347349 reinterpret_cast <const unsigned char *>(iv_value.data )) != 1 )
348350 onError (" EVP_EncryptInit_ex" );
349351 }
352+ else if (can_reuse_key_schedule)
353+ {
354+ key_value = const_key_value;
355+ if (iv_column)
356+ {
357+ iv_value = iv_column->getDataAt (row_idx);
358+ if (iv_value.size == 0 )
359+ iv_value.data = nullptr ;
360+ }
361+
362+ validateIV<mode>(iv_value, iv_size);
363+
364+ if (EVP_EncryptInit_ex (evp_ctx, nullptr , nullptr , nullptr ,
365+ reinterpret_cast <const unsigned char *>(iv_value.data )) != 1 )
366+ onError (" EVP_EncryptInit_ex" );
367+ }
350368 else
351369 {
352370 key_value = key_holder.setKey (key_size, key_column->getDataAt (row_idx));
@@ -379,7 +397,7 @@ class FunctionEncrypt : public IFunction
379397 // Avoid extra work on empty ciphertext/plaintext for some ciphers
380398 if (!(input_value.size == 0 && block_size == 1 && mode != CipherMode::RFC5116_AEAD_AES_GCM))
381399 {
382- if (!can_reuse_context)
400+ if (!can_reuse_context && !can_reuse_key_schedule )
383401 {
384402 // 1: Init CTX
385403 if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
@@ -636,10 +654,12 @@ class FunctionDecrypt : public IFunction
636654 const bool iv_is_constant = iv_column && isColumnConst (*iv_column);
637655 // / Avoid re-initializing cipher with the same key/IV on every row (expensive with OpenSSL 3).
638656 const bool can_reuse_context = (mode != CipherMode::RFC5116_AEAD_AES_GCM) && key_is_constant && (!iv_column || iv_is_constant);
657+ // / Reuse only key schedule even if IV changes per row.
658+ const bool can_reuse_key_schedule = (mode != CipherMode::RFC5116_AEAD_AES_GCM) && key_is_constant;
639659
640660 StringRef const_key_value{};
641661 StringRef const_iv_value{};
642- if (can_reuse_context )
662+ if (can_reuse_key_schedule )
643663 {
644664 const_key_value = key_holder.setKey (key_size, key_column->getDataAt (0 ));
645665 if constexpr (mode != CipherMode::MySQLCompatibility)
@@ -648,7 +668,7 @@ class FunctionDecrypt : public IFunction
648668 throw Exception (ErrorCodes::BAD_ARGUMENTS, " Invalid key size {} != expected size {}" , const_key_value.size , key_size);
649669 }
650670
651- if (iv_column)
671+ if (iv_column && iv_is_constant )
652672 {
653673 const_iv_value = iv_column->getDataAt (0 );
654674
@@ -681,6 +701,24 @@ class FunctionDecrypt : public IFunction
681701 reinterpret_cast <const unsigned char *>(iv_value.data )) != 1 )
682702 onError (" EVP_DecryptInit_ex" );
683703 }
704+ else if (can_reuse_key_schedule)
705+ {
706+ key_value = const_key_value;
707+ if (iv_column)
708+ {
709+ iv_value = iv_column->getDataAt (row_idx);
710+
711+ // / If the length is zero (empty string is passed) it should be treat as no IV.
712+ if (iv_value.size == 0 )
713+ iv_value.data = nullptr ;
714+ }
715+
716+ validateIV<mode>(iv_value, iv_size);
717+
718+ if (EVP_DecryptInit_ex (evp_ctx, nullptr , nullptr , nullptr ,
719+ reinterpret_cast <const unsigned char *>(iv_value.data )) != 1 )
720+ onError (" EVP_DecryptInit_ex" );
721+ }
684722 else
685723 {
686724 key_value = key_holder.setKey (key_size, key_column->getDataAt (row_idx));
@@ -729,7 +767,7 @@ class FunctionDecrypt : public IFunction
729767 // / This makes sense for default implementation for NULLs.
730768 if (input_value.size > 0 )
731769 {
732- if (!can_reuse_context)
770+ if (!can_reuse_context && !can_reuse_key_schedule )
733771 {
734772 // 1: Init CTX
735773 if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
0 commit comments