Skip to content

Commit 1025c95

Browse files
committed
Reuse AES key schedule when IV varies; avoid full OpenSSL re-init per row in non-GCM modes
1 parent 8edd852 commit 1025c95

File tree

1 file changed

+44
-6
lines changed

1 file changed

+44
-6
lines changed

src/Functions/FunctionsAES.h

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)