diff --git a/src/Functions/FunctionsAES.cpp b/src/Functions/FunctionsAES.cpp index ed59bc901f56..f39c47db310d 100644 --- a/src/Functions/FunctionsAES.cpp +++ b/src/Functions/FunctionsAES.cpp @@ -37,14 +37,37 @@ std::string_view foldEncryptionKeyInMySQLCompatitableMode(size_t cipher_key_size return std::string_view(folded_key.data(), cipher_key_size); } -const EVP_CIPHER * getCipherByName(std::string_view cipher_name) +EVP_CIPHER_ptr fetchCipher(std::string_view cipher_name) { - // NOTE: cipher obtained not via EVP_CIPHER_fetch() would cause extra work on each context reset - // with EVP_CIPHER_CTX_reset() or EVP_EncryptInit_ex(), but using EVP_CIPHER_fetch() - // causes data race, so we stick to the slower but safer alternative here. + /// A cipher obtained via EVP_get_cipherbyname has prov == NULL, which makes OpenSSL 3.x + /// implicitly call EVP_CIPHER_fetch (with locking and provider lookups) on every + /// EVP_EncryptInit_ex / EVP_DecryptInit_ex invocation. Fetching the cipher explicitly + /// here returns a provider-backed cipher and avoids that per-row overhead. + /// Returns nullptr for an unknown cipher name; the caller reports it as an invalid mode. + /// We need a zero-terminated string here: + auto * fetched = EVP_CIPHER_fetch(nullptr, std::string{cipher_name}.c_str(), nullptr); + if (!fetched) + /// For an unknown name EVP_CIPHER_fetch pushes an "unsupported" entry onto the + /// thread-local OpenSSL error queue. The caller turns nullptr into a BAD_ARGUMENTS + /// "Invalid mode" without draining the queue via getOpenSSLErrors, so clear it here to + /// avoid leaking this stale entry into an unrelated OpenSSL error later reported on the + /// same thread (unlike EVP_get_cipherbyname, which never touched the queue). + ERR_clear_error(); + return EVP_CIPHER_ptr(fetched, EVP_CIPHER_free); +} - /// We need zero-terminated string here: - return EVP_get_cipherbyname(std::string{cipher_name}.c_str()); +std::string_view ecbEquivalentCipherName(std::string_view mode) +{ + /// Only plain AES block ciphers. The list is intentionally exact: e.g. + /// aes-128-cbc-hmac-sha1 also reports EVP_CIPH_CBC_MODE but is not a plain + /// block cipher and must not take the block-composed fast path. + if (mode == "aes-128-ecb" || mode == "aes-128-cbc") + return "aes-128-ecb"; + if (mode == "aes-192-ecb" || mode == "aes-192-cbc") + return "aes-192-ecb"; + if (mode == "aes-256-ecb" || mode == "aes-256-cbc") + return "aes-256-ecb"; + return {}; } } diff --git a/src/Functions/FunctionsAES.h b/src/Functions/FunctionsAES.h index 6967f50c6447..a4e9ec0b66b6 100644 --- a/src/Functions/FunctionsAES.h +++ b/src/Functions/FunctionsAES.h @@ -33,6 +33,7 @@ namespace DB namespace ErrorCodes { extern const int BAD_ARGUMENTS; + extern const int OPENSSL_ERROR; } @@ -41,7 +42,62 @@ namespace OpenSSLDetails [[noreturn]] void onError(std::string error_message); std::string_view foldEncryptionKeyInMySQLCompatitableMode(size_t cipher_key_size, std::string_view key, std::array & folded_key); -const EVP_CIPHER * getCipherByName(std::string_view name); +using EVP_CIPHER_ptr = std::unique_ptr; + +/// Fetches a provider-backed cipher via EVP_CIPHER_fetch, which (unlike a legacy cipher with +/// prov == NULL) avoids implicit EVP_CIPHER_fetch calls on every EVP_EncryptInit_ex / +/// EVP_DecryptInit_ex invocation in OpenSSL 3.x. Returns nullptr for an unknown cipher name. +EVP_CIPHER_ptr fetchCipher(std::string_view name); + +/// For plain AES block cipher modes (aes-{128,192,256}-{ecb,cbc}) returns the name of the +/// ECB cipher with the same key size, used by the block-composed fast path. Returns an +/// empty view for everything else. +std::string_view ecbEquivalentCipherName(std::string_view mode); + +/// Constant-time byte comparisons for PKCS#7 padding validation: 0xFF when the condition +/// holds, 0 otherwise, with no data-dependent branches. +inline UInt8 constantTimeGE(UInt8 a, UInt8 b) +{ + return static_cast(~((static_cast(a) - b) >> 8)); +} + +inline UInt8 constantTimeEQ(UInt8 a, UInt8 b) +{ + return static_cast((static_cast(a ^ b) - 1) >> 8); +} + +/// Remembers the key an EVP_CIPHER_CTX has been initialized with, so rows repeating the +/// same key can re-initialize only the IV (passing a null cipher and key), skipping the +/// cipher setup and key schedule expansion that dominate the per-row cost in OpenSSL 3.x. +struct CachedKeyState +{ + bool matches(std::string_view key_value) const + { + return initialized && key_value.size() == key_size + && memcmp(key_value.data(), key.data(), key_size) == 0; + } + + void remember(std::string_view key_value) + { + chassert(key_value.size() <= key.size()); + memcpy(key.data(), key_value.data(), key_value.size()); + key_size = key_value.size(); + initialized = true; + } + + /// Whether the context has been initialized with the cipher at least once. After + /// that, a key change only needs the new key (with a null cipher), which replaces + /// the key schedule but keeps the provider context and its parameters; re-passing + /// the cipher would repeat the full context setup. + bool hasContext() const { return initialized; } + + void reset() { initialized = false; } + +private: + bool initialized = false; + size_t key_size = 0; + std::array key{}; +}; enum class CompatibilityMode : uint8_t { @@ -194,9 +250,13 @@ class FunctionEncrypt : public IFunction if (mode.empty() || !mode.starts_with("aes-")) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Invalid mode: {}", mode); - const auto * evp_cipher = getCipherByName(mode); - if (evp_cipher == nullptr) + /// Fetch a provider-backed cipher once per block to avoid implicit EVP_CIPHER_fetch + /// on every EVP_EncryptInit_ex call in the per-row loop. + /// See https://github.com/ClickHouse/ClickHouse/issues/65116 + auto fetched_cipher = fetchCipher(mode); + if (fetched_cipher == nullptr) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Invalid mode: {}", mode); + const auto * evp_cipher = fetched_cipher.get(); const auto cipher_mode = EVP_CIPHER_mode(evp_cipher); @@ -205,9 +265,19 @@ class FunctionEncrypt : public IFunction OpenSSLDetails::validateCipherMode(evp_cipher); + /// Plain AES ECB/CBC modes take a fast path composed on top of a streaming ECB + /// cipher context (see doEncryptBlockCipher). + EVP_CIPHER_ptr ecb_cipher(nullptr, EVP_CIPHER_free); + if (const auto ecb_name = ecbEquivalentCipherName(mode); !ecb_name.empty()) + { + ecb_cipher = fetchCipher(ecb_name); + if (ecb_cipher == nullptr) + throw Exception(ErrorCodes::OPENSSL_ERROR, "Cannot fetch cipher {}", ecb_name); + } + ColumnPtr result_column; if (arguments.size() <= 3) - result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, nullptr, nullptr); + result_column = doEncrypt(fetched_cipher.get(), ecb_cipher.get(), input_rows_count, input_column, key_column, nullptr, nullptr); else { const auto iv_column = arguments[3].column; @@ -216,7 +286,7 @@ class FunctionEncrypt : public IFunction if (arguments.size() <= 4) { - result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, nullptr); + result_column = doEncrypt(fetched_cipher.get(), ecb_cipher.get(), input_rows_count, input_column, key_column, iv_column, nullptr); } else { @@ -224,7 +294,7 @@ class FunctionEncrypt : public IFunction throw Exception(ErrorCodes::BAD_ARGUMENTS, "AAD can be only set for GCM-mode"); const auto aad_column = arguments[4].column; - result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column); + result_column = doEncrypt(fetched_cipher.get(), ecb_cipher.get(), input_rows_count, input_column, key_column, iv_column, aad_column); } } @@ -233,6 +303,7 @@ class FunctionEncrypt : public IFunction static ColumnPtr doEncrypt( const EVP_CIPHER * evp_cipher, + const EVP_CIPHER * ecb_cipher, size_t input_rows_count, const ColumnPtr & input_column, const ColumnPtr & key_column, @@ -241,6 +312,9 @@ class FunctionEncrypt : public IFunction { if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::MySQL) { + if (ecb_cipher) + return doEncryptBlockCipher(evp_cipher, ecb_cipher, input_rows_count, input_column, key_column, iv_column); + return doEncryptImpl(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column); } else @@ -250,6 +324,9 @@ class FunctionEncrypt : public IFunction return doEncryptImpl(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column); } + if (ecb_cipher) + return doEncryptBlockCipher(evp_cipher, ecb_cipher, input_rows_count, input_column, key_column, iv_column); + return doEncryptImpl( evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column); } @@ -257,6 +334,176 @@ class FunctionEncrypt : public IFunction return nullptr; } + /// Fast path for plain AES ECB and CBC modes (see ecbEquivalentCipherName). + /// + /// OpenSSL 3.x spends most of the per-row time in EVP_EncryptInit_ex: the provider + /// dispatch and AES key schedule expansion cost an order of magnitude more than + /// actually encrypting a short value. Instead of re-initializing a cipher context + /// per row, this path keeps a single streaming ECB context with padding disabled, + /// initialized once per distinct key, and composes the block mode manually: PKCS#7 + /// padding and, for CBC, the chaining XOR. For longer CBC rows the per-block + /// EVP_EncryptUpdate calls lose to OpenSSL's stitched AES-NI CBC implementation, + /// so those rows go through a CBC context that is re-initialized with only the IV + /// (keeping the expanded key schedule) between rows. + /// See https://github.com/ClickHouse/ClickHouse/issues/65116 + template + static ColumnPtr doEncryptBlockCipher( + const EVP_CIPHER * evp_cipher, + const EVP_CIPHER * ecb_cipher, + size_t input_rows_count, + const ColumnPtr & input_column, + const ColumnPtr & key_column, + const ColumnPtr & iv_column) + { + using namespace OpenSSLDetails; + + static_assert(mode != CipherMode::RFC5116_AEAD_AES_GCM); + + /// The AES block size, for both ECB and CBC. + constexpr size_t block_size = 16; + /// Below this input size composing CBC from per-block ECB updates beats the + /// stitched CBC implementation re-initialized per row (the measured crossover + /// is between 64 and 96 bytes). + constexpr size_t stitched_cbc_threshold = 64; + + const bool is_cbc = EVP_CIPHER_mode(evp_cipher) == EVP_CIPH_CBC_MODE; + const auto key_size = static_cast(EVP_CIPHER_key_length(evp_cipher)); + const auto iv_size = static_cast(EVP_CIPHER_iv_length(evp_cipher)); + chassert(static_cast(EVP_CIPHER_block_size(evp_cipher)) == block_size); + + auto ecb_ctx_ptr = std::unique_ptr(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free); + auto cbc_ctx_ptr = std::unique_ptr(is_cbc ? EVP_CIPHER_CTX_new() : nullptr, &EVP_CIPHER_CTX_free); + if (!ecb_ctx_ptr || (is_cbc && !cbc_ctx_ptr)) + throw Exception(ErrorCodes::OPENSSL_ERROR, "EVP_CIPHER_CTX_new failed"); + auto * ecb_ctx = ecb_ctx_ptr.get(); + auto * cbc_ctx = cbc_ctx_ptr.get(); + + auto encrypted_result_column = ColumnString::create(); + auto & encrypted_result_column_data = encrypted_result_column->getChars(); + auto & encrypted_result_column_offsets = encrypted_result_column->getOffsets(); + + { + size_t resulting_size = 0; + for (size_t row_idx = 0; row_idx < input_rows_count; ++row_idx) + resulting_size += (input_column->getDataAt(row_idx).size() / block_size + 1) * block_size; + encrypted_result_column_data.resize(resulting_size); + } + + auto * encrypted = encrypted_result_column_data.data(); + + KeyHolder key_holder{}; + CachedKeyState ecb_key_state; + CachedKeyState cbc_key_state; + static constexpr unsigned char zero_iv[EVP_MAX_IV_LENGTH]{}; + + for (size_t row_idx = 0; row_idx < input_rows_count; ++row_idx) + { + const auto key_value = key_holder.setKey(key_size, key_column->getDataAt(row_idx)); + auto iv_value = std::string_view{}; + if (iv_column) + iv_value = iv_column->getDataAt(row_idx); + + validateIV(iv_value, iv_size); + /// A fresh cipher context starts from a zero IV when no IV is given, so an + /// absent IV is passed as an explicit zero IV here. + const auto * iv_ptr = iv_value.empty() ? zero_iv : reinterpret_cast(iv_value.data()); + + const std::string_view input_value = input_column->getDataAt(row_idx); + const auto * input = reinterpret_cast(input_value.data()); + const size_t input_size = input_value.size(); + [[maybe_unused]] const auto * row_begin = encrypted; + + int output_len = 0; + if (is_cbc && input_size >= stitched_cbc_threshold) + { + if (cbc_key_state.matches(key_value)) + { + /// Reset only the IV, keeping the expanded key schedule. + if (EVP_EncryptInit_ex(cbc_ctx, nullptr, nullptr, nullptr, iv_ptr) != 1) + onError("EVP_EncryptInit_ex"); + } + else + { + if (EVP_EncryptInit_ex(cbc_ctx, cbc_key_state.hasContext() ? nullptr : evp_cipher, nullptr, + reinterpret_cast(key_value.data()), iv_ptr) != 1) + onError("EVP_EncryptInit_ex"); + cbc_key_state.remember(key_value); + } + + if (EVP_EncryptUpdate(cbc_ctx, reinterpret_cast(encrypted), &output_len, + input, static_cast(input_size)) != 1) + onError("EVP_EncryptUpdate"); + encrypted += output_len; + if (EVP_EncryptFinal_ex(cbc_ctx, reinterpret_cast(encrypted), &output_len) != 1) + onError("EVP_EncryptFinal_ex"); + encrypted += output_len; + } + else + { + if (!ecb_key_state.matches(key_value)) + { + const bool first_init = !ecb_key_state.hasContext(); + if (EVP_EncryptInit_ex(ecb_ctx, first_init ? ecb_cipher : nullptr, nullptr, + reinterpret_cast(key_value.data()), nullptr) != 1) + onError("EVP_EncryptInit_ex"); + /// The context produces raw blocks; PKCS#7 padding is applied manually below. + /// Key-only re-initialization keeps the setting. + if (first_init && EVP_CIPHER_CTX_set_padding(ecb_ctx, 0) != 1) + onError("EVP_CIPHER_CTX_set_padding"); + ecb_key_state.remember(key_value); + } + + const size_t full_blocks = input_size / block_size; + unsigned char chain[block_size]; + + if (is_cbc) + { + memcpy(chain, iv_ptr, block_size); + + for (size_t block = 0; block < full_blocks; ++block) + { + unsigned char buf[block_size]; + const auto * src = input + block * block_size; + for (size_t i = 0; i < block_size; ++i) + buf[i] = src[i] ^ chain[i]; + if (EVP_EncryptUpdate(ecb_ctx, reinterpret_cast(encrypted), &output_len, buf, block_size) != 1) + onError("EVP_EncryptUpdate"); + memcpy(chain, encrypted, block_size); + encrypted += block_size; + } + } + else if (full_blocks > 0) + { + if (EVP_EncryptUpdate(ecb_ctx, reinterpret_cast(encrypted), &output_len, + input, static_cast(full_blocks * block_size)) != 1) + onError("EVP_EncryptUpdate"); + encrypted += output_len; + } + + /// The last block: remaining input bytes plus PKCS#7 padding. + const size_t remaining = input_size - full_blocks * block_size; + unsigned char last[block_size]; + if (remaining > 0) + memcpy(last, input + full_blocks * block_size, remaining); + memset(last + remaining, static_cast(block_size - remaining), block_size - remaining); + if (is_cbc) + { + for (size_t i = 0; i < block_size; ++i) + last[i] ^= chain[i]; + } + if (EVP_EncryptUpdate(ecb_ctx, reinterpret_cast(encrypted), &output_len, last, block_size) != 1) + onError("EVP_EncryptUpdate"); + encrypted += block_size; + } + + __msan_unpoison(row_begin, encrypted - row_begin); /// OpenSSL uses assembly which evades msan's analysis + encrypted_result_column_offsets.push_back(encrypted - encrypted_result_column_data.data()); + } + + encrypted_result_column->validate(); + return encrypted_result_column; + } + template static ColumnPtr doEncryptImpl( const EVP_CIPHER * evp_cipher, @@ -299,6 +546,7 @@ class FunctionEncrypt : public IFunction auto * encrypted = encrypted_result_column_data.data(); KeyHolder key_holder; + CachedKeyState ctx_key_state; for (size_t row_idx = 0; row_idx < input_rows_count; ++row_idx) { @@ -362,10 +610,28 @@ class FunctionEncrypt : public IFunction // 1.b: Init CTX validateIV(iv_value, iv_size); - if (EVP_EncryptInit_ex(evp_ctx, evp_cipher, nullptr, - reinterpret_cast(key_value.data()), - reinterpret_cast(iv_value.data())) != 1) - onError("EVP_EncryptInit_ex"); + /// A fresh full initialization starts from a zero IV when no IV is + /// given, while an IV-only re-initialization with a null IV pointer + /// would keep the previous row's advanced IV state, so an absent IV + /// is passed as an explicit zero IV. + static constexpr unsigned char zero_iv[EVP_MAX_IV_LENGTH]{}; + const auto * iv_ptr = iv_value.empty() ? zero_iv : reinterpret_cast(iv_value.data()); + + if (ctx_key_state.matches(key_value)) + { + /// The context already holds the expanded key schedule for this + /// key: reset only the IV, skipping the cipher setup and key + /// schedule expansion that dominate the per-row cost in OpenSSL 3.x. + if (EVP_EncryptInit_ex(evp_ctx, nullptr, nullptr, nullptr, iv_ptr) != 1) + onError("EVP_EncryptInit_ex"); + } + else + { + if (EVP_EncryptInit_ex(evp_ctx, ctx_key_state.hasContext() ? nullptr : evp_cipher, nullptr, + reinterpret_cast(key_value.data()), iv_ptr) != 1) + onError("EVP_EncryptInit_ex"); + ctx_key_state.remember(key_value); + } } int output_len = 0; @@ -469,19 +735,33 @@ class FunctionDecrypt : public IFunction if (mode.empty() || !mode.starts_with("aes-")) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Invalid mode: {}", mode); - const auto * evp_cipher = getCipherByName(mode); - if (evp_cipher == nullptr) + /// Fetch a provider-backed cipher once per block to avoid implicit EVP_CIPHER_fetch + /// on every EVP_DecryptInit_ex call in the per-row loop. + /// See https://github.com/ClickHouse/ClickHouse/issues/65116 + auto fetched_cipher = fetchCipher(mode); + if (fetched_cipher == nullptr) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Invalid mode: {}", mode); + const auto * evp_cipher = fetched_cipher.get(); OpenSSLDetails::validateCipherMode(evp_cipher); + /// Plain AES ECB/CBC modes take a fast path composed on top of a streaming ECB + /// cipher context (see doDecryptBlockCipher). + EVP_CIPHER_ptr ecb_cipher(nullptr, EVP_CIPHER_free); + if (const auto ecb_name = ecbEquivalentCipherName(mode); !ecb_name.empty()) + { + ecb_cipher = fetchCipher(ecb_name); + if (ecb_cipher == nullptr) + throw Exception(ErrorCodes::OPENSSL_ERROR, "Cannot fetch cipher {}", ecb_name); + } + const auto input_column = arguments[1].column; const auto key_column = arguments[2].column; ColumnPtr result_column; if (arguments.size() <= 3) { - result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, nullptr, nullptr); + result_column = doDecrypt(fetched_cipher.get(), ecb_cipher.get(), input_rows_count, input_column, key_column, nullptr, nullptr); } else { @@ -491,7 +771,7 @@ class FunctionDecrypt : public IFunction if (arguments.size() <= 4) { - result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, nullptr); + result_column = doDecrypt(fetched_cipher.get(), ecb_cipher.get(), input_rows_count, input_column, key_column, iv_column, nullptr); } else { @@ -499,7 +779,7 @@ class FunctionDecrypt : public IFunction throw Exception(ErrorCodes::BAD_ARGUMENTS, "AAD can be only set for GCM-mode"); const auto aad_column = arguments[4].column; - result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column); + result_column = doDecrypt(fetched_cipher.get(), ecb_cipher.get(), input_rows_count, input_column, key_column, iv_column, aad_column); } } @@ -508,6 +788,7 @@ class FunctionDecrypt : public IFunction template static ColumnPtr doDecrypt( const EVP_CIPHER * evp_cipher, + const EVP_CIPHER * ecb_cipher, size_t input_rows_count, const ColumnPtr & input_column, const ColumnPtr & key_column, @@ -516,6 +797,9 @@ class FunctionDecrypt : public IFunction { if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::MySQL) { + if (ecb_cipher) + return doDecryptBlockCipher(evp_cipher, ecb_cipher, input_rows_count, input_column, key_column, iv_column); + return doDecryptImpl(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column); } else @@ -526,6 +810,9 @@ class FunctionDecrypt : public IFunction return doDecryptImpl(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column); } + if (ecb_cipher) + return doDecryptBlockCipher(evp_cipher, ecb_cipher, input_rows_count, input_column, key_column, iv_column); + return doDecryptImpl( evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column); } @@ -533,6 +820,158 @@ class FunctionDecrypt : public IFunction return nullptr; } + /// Counterpart of FunctionEncrypt::doEncryptBlockCipher for plain AES ECB and CBC. + /// + /// Unlike encryption, block-mode decryption has no sequential dependency between + /// blocks, so each row is decrypted with a single EVP_DecryptUpdate call on a + /// streaming ECB context (with padding disabled), initialized once per distinct + /// key. The CBC chaining XOR is applied afterwards, and PKCS#7 padding is + /// validated manually, checking all padding bytes like EVP_DecryptFinal_ex does. + /// See https://github.com/ClickHouse/ClickHouse/issues/65116 + template + static ColumnPtr doDecryptBlockCipher( + const EVP_CIPHER * evp_cipher, + const EVP_CIPHER * ecb_cipher, + size_t input_rows_count, + const ColumnPtr & input_column, + const ColumnPtr & key_column, + const ColumnPtr & iv_column) + { + using namespace OpenSSLDetails; + + static_assert(mode != CipherMode::RFC5116_AEAD_AES_GCM); + + /// The AES block size, for both ECB and CBC. + constexpr size_t block_size = 16; + + const bool is_cbc = EVP_CIPHER_mode(evp_cipher) == EVP_CIPH_CBC_MODE; + const auto key_size = static_cast(EVP_CIPHER_key_length(evp_cipher)); + const auto iv_size = static_cast(EVP_CIPHER_iv_length(evp_cipher)); + chassert(static_cast(EVP_CIPHER_block_size(evp_cipher)) == block_size); + + auto ecb_ctx_ptr = std::unique_ptr(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free); + if (!ecb_ctx_ptr) + throw Exception(ErrorCodes::OPENSSL_ERROR, "EVP_CIPHER_CTX_new failed"); + auto * ecb_ctx = ecb_ctx_ptr.get(); + + auto decrypted_result_column = ColumnString::create(); + auto null_map = ColumnUInt8::create(); + auto & decrypted_result_column_data = decrypted_result_column->getChars(); + auto & decrypted_result_column_offsets = decrypted_result_column->getOffsets(); + + { + size_t resulting_size = 0; + for (size_t row_idx = 0; row_idx < input_rows_count; ++row_idx) + resulting_size += input_column->getDataAt(row_idx).size(); + decrypted_result_column_data.resize(resulting_size); + } + + auto * decrypted = decrypted_result_column_data.data(); + + KeyHolder key_holder{}; + CachedKeyState ecb_key_state; + static constexpr unsigned char zero_iv[EVP_MAX_IV_LENGTH]{}; + + for (size_t row_idx = 0; row_idx < input_rows_count; ++row_idx) + { + const auto key_value = key_holder.setKey(key_size, key_column->getDataAt(row_idx)); + auto iv_value = std::string_view{}; + if (iv_column) + iv_value = iv_column->getDataAt(row_idx); + + const std::string_view input_value = input_column->getDataAt(row_idx); + const auto * input = reinterpret_cast(input_value.data()); + const size_t input_size = input_value.size(); + + bool decrypt_fail = false; + /// Always decrypt empty to empty, like the generic implementation. + if (!input_value.empty()) + { + validateIV(iv_value, iv_size); + const auto * iv_ptr = iv_value.empty() ? zero_iv : reinterpret_cast(iv_value.data()); + + if (input_size % block_size != 0) + { + if constexpr (!use_null_when_decrypt_fail) + throw Exception(ErrorCodes::OPENSSL_ERROR, + "Cannot decrypt: data size {} is not a multiple of the cipher block size {}", input_size, block_size); + decrypt_fail = true; + } + else + { + if (!ecb_key_state.matches(key_value)) + { + const bool first_init = !ecb_key_state.hasContext(); + if (EVP_DecryptInit_ex(ecb_ctx, first_init ? ecb_cipher : nullptr, nullptr, + reinterpret_cast(key_value.data()), nullptr) != 1) + onError("EVP_DecryptInit_ex"); + /// The context consumes raw blocks; PKCS#7 padding is validated manually below. + /// Key-only re-initialization keeps the setting. + if (first_init && EVP_CIPHER_CTX_set_padding(ecb_ctx, 0) != 1) + onError("EVP_CIPHER_CTX_set_padding"); + ecb_key_state.remember(key_value); + } + + int output_len = 0; + if (EVP_DecryptUpdate(ecb_ctx, + reinterpret_cast(decrypted), &output_len, + input, static_cast(input_size)) != 1) + onError("EVP_DecryptUpdate"); + chassert(static_cast(output_len) == input_size); + __msan_unpoison(decrypted, output_len); /// OpenSSL uses assembly which evades msan's analysis + + if (is_cbc) + { + /// Undo the chaining: the first block is XORed with the IV, + /// every following block with the previous ciphertext block. + for (size_t i = 0; i < block_size; ++i) + decrypted[i] ^= iv_ptr[i]; + for (size_t i = block_size; i < input_size; ++i) + decrypted[i] ^= input[i - block_size]; + } + + /// Constant-time PKCS#7 validation over the last block, like + /// EVP_DecryptFinal_ex: every byte of the block participates and + /// nothing branches on the decrypted data, so invalid padding is + /// not distinguishable by timing. + const auto * last_block = decrypted + input_size - block_size; + const UInt8 pad = last_block[block_size - 1]; + UInt8 padding_good = constantTimeGE(block_size, pad) & constantTimeGE(pad, 1); + for (size_t i = 0; i < block_size; ++i) + { + /// Byte i of the last block is a padding byte iff pad >= block_size - i. + const UInt8 is_pad_byte = constantTimeGE(pad, static_cast(block_size - i)); + padding_good &= static_cast(~is_pad_byte) | constantTimeEQ(last_block[i], pad); + } + const bool padding_valid = padding_good != 0; + + if (!padding_valid) + { + if constexpr (!use_null_when_decrypt_fail) + throw Exception(ErrorCodes::OPENSSL_ERROR, "Cannot decrypt: invalid PKCS#7 padding"); + decrypt_fail = true; + } + else + decrypted += input_size - pad; + } + } + + decrypted_result_column_offsets.push_back(decrypted - decrypted_result_column_data.data()); + if constexpr (use_null_when_decrypt_fail) + null_map->insertValue(decrypt_fail ? 1 : 0); + } + + /// The padding stripped from the last row leaves the buffer overallocated. + if (!decrypted_result_column_offsets.empty() && decrypted_result_column_data.size() > decrypted_result_column_offsets.back()) + decrypted_result_column_data.resize(decrypted_result_column_offsets.back()); + + decrypted_result_column->validate(); + if constexpr (use_null_when_decrypt_fail) + return ColumnNullable::create(std::move(decrypted_result_column), std::move(null_map)); + else + return decrypted_result_column; + } + template static ColumnPtr doDecryptImpl(const EVP_CIPHER * evp_cipher, size_t input_rows_count, @@ -582,6 +1021,8 @@ class FunctionDecrypt : public IFunction auto * decrypted = decrypted_result_column_data.data(); KeyHolder key_holder; + [[maybe_unused]] CachedKeyState ctx_key_state; + for (size_t row_idx = 0; row_idx < input_rows_count; ++row_idx) { // 0: prepare key if required @@ -661,10 +1102,28 @@ class FunctionDecrypt : public IFunction // 1.b: Init CTX validateIV(iv_value, iv_size); - if (EVP_DecryptInit_ex(evp_ctx, evp_cipher, nullptr, - reinterpret_cast(key_value.data()), - reinterpret_cast(iv_value.data())) != 1) - onError("EVP_DecryptInit_ex"); + /// A fresh full initialization starts from a zero IV when no IV is + /// given, while an IV-only re-initialization with a null IV pointer + /// would keep the previous row's advanced IV state, so an absent IV + /// is passed as an explicit zero IV. + static constexpr unsigned char zero_iv[EVP_MAX_IV_LENGTH]{}; + const auto * iv_ptr = iv_value.empty() ? zero_iv : reinterpret_cast(iv_value.data()); + + if (ctx_key_state.matches(key_value)) + { + /// The context already holds the expanded key schedule for this + /// key: reset only the IV, skipping the cipher setup and key + /// schedule expansion that dominate the per-row cost in OpenSSL 3.x. + if (EVP_DecryptInit_ex(evp_ctx, nullptr, nullptr, nullptr, iv_ptr) != 1) + onError("EVP_DecryptInit_ex"); + } + else + { + if (EVP_DecryptInit_ex(evp_ctx, ctx_key_state.hasContext() ? nullptr : evp_cipher, nullptr, + reinterpret_cast(key_value.data()), iv_ptr) != 1) + onError("EVP_DecryptInit_ex"); + ctx_key_state.remember(key_value); + } } // 2: Feed the data to the cipher @@ -704,6 +1163,11 @@ class FunctionDecrypt : public IFunction } } + /// A failed EVP_DecryptUpdate / EVP_DecryptFinal_ex can leave buffered input + /// in the context, so force a full re-initialization on the next row. + if (decrypt_fail) + ctx_key_state.reset(); + decrypted_result_column_offsets.push_back(decrypted - decrypted_result_column_data.data()); if constexpr (use_null_when_decrypt_fail) { diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index d5d107b5dd00..1d4845086eb4 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -249,13 +249,36 @@ struct HalfMD5Impl } buf; using EVP_MD_CTX_ptr = std::unique_ptr; - const auto ctx = EVP_MD_CTX_ptr(EVP_MD_CTX_new(), EVP_MD_CTX_free); + + /// A context is initialized with the MD5 digest once, then only copied on each call + /// (the same approach as in FunctionsStringHashFixedString.cpp). Copying an already + /// initialized context with `EVP_MD_CTX_copy_ex` is faster than re-initializing with + /// `EVP_md5` every time: in OpenSSL 3.x the latter re-fetches the digest from the + /// provider method store under a read lock, and with this function called once per row + /// all hashing threads serialize on it (with musl's `pthread_rwlock` on aarch64 this + /// doubled `cryptographic_hashes` times in CI, `__pthread_rwlock_tryrdlock` dominating + /// the profile). + static const EVP_MD_CTX_ptr ctx_template = [] + { + EVP_MD_CTX_ptr new_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); + if (!new_ctx) + throw Exception(ErrorCodes::OPENSSL_ERROR, "EVP_MD_CTX_new failed: {}", getOpenSSLErrors()); + if (EVP_DigestInit_ex(new_ctx.get(), EVP_md5(), nullptr) != 1) + throw Exception(ErrorCodes::OPENSSL_ERROR, "EVP_DigestInit_ex failed: {}", getOpenSSLErrors()); + return new_ctx; + }(); + + thread_local EVP_MD_CTX_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); if (!ctx) - throw Exception(ErrorCodes::OPENSSL_ERROR, "EVP_MD_CTX_new failed: {}", getOpenSSLErrors()); + { + ctx.reset(EVP_MD_CTX_new()); + if (!ctx) + throw Exception(ErrorCodes::OPENSSL_ERROR, "EVP_MD_CTX_new failed: {}", getOpenSSLErrors()); + } - if (EVP_DigestInit_ex(ctx.get(), EVP_md5(), nullptr) != 1) - throw Exception(ErrorCodes::OPENSSL_ERROR, "EVP_DigestInit_ex failed: {}", getOpenSSLErrors()); + if (EVP_MD_CTX_copy_ex(ctx.get(), ctx_template.get()) != 1) + throw Exception(ErrorCodes::OPENSSL_ERROR, "EVP_MD_CTX_copy_ex failed: {}", getOpenSSLErrors()); if (EVP_DigestUpdate(ctx.get(), begin, size) != 1) throw Exception(ErrorCodes::OPENSSL_ERROR, "EVP_DigestUpdate failed: {}", getOpenSSLErrors()); diff --git a/tests/performance/encrypt_decrypt.xml b/tests/performance/encrypt_decrypt.xml new file mode 100644 index 000000000000..0513426de895 --- /dev/null +++ b/tests/performance/encrypt_decrypt.xml @@ -0,0 +1,56 @@ + + + + + + func + + + encrypt('aes-128-ecb', materialize(plaintext), key16) + encrypt('aes-128-cbc', materialize(plaintext), key16, iv16) + encrypt('aes-128-ctr', materialize(plaintext), key16, iv16) + encrypt('aes-128-gcm', materialize(plaintext), key16, iv12) + + + decrypt('aes-128-ecb', encrypt('aes-128-ecb', materialize(plaintext), key16), key16) + decrypt('aes-128-cbc', encrypt('aes-128-cbc', materialize(plaintext), key16, iv16), key16, iv16) + decrypt('aes-128-gcm', encrypt('aes-128-gcm', materialize(plaintext), key16, iv12), key16, iv12) + + + encrypt('aes-128-cbc', materialize(plaintext), randomString(16), iv16) + encrypt('aes-128-ecb', materialize(plaintext), randomString(16)) + + + encrypt('aes-128-cbc', materialize(plaintext), concat(repeat('k', 15), toString(number % 2)), iv16) + + + encrypt('aes-128-cbc', materialize(plaintext), key16, randomString(16)) + + + decrypt('aes-128-cbc', encrypt('aes-128-cbc', materialize(plaintext), vkey16, iv16), vkey16, iv16) + decrypt('aes-128-ecb', encrypt('aes-128-ecb', materialize(plaintext), vkey16), vkey16) + decrypt('aes-128-cbc', encrypt('aes-128-cbc', materialize(plaintext), key16, viv16), key16, viv16) + + + + table + + numbers(3000000) + + + + plaintext + + toString(number) + + + + + + WITH {plaintext} as plaintext, repeat('k', 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12, substring(concat(toString(number), key16), 1, 16) as vkey16, substring(concat(toString(number), iv16), 1, 16) as viv16 SELECT count() FROM {table} WHERE NOT ignore({func}) LIMIT 1 + + WITH {plaintext} as plaintext, repeat('k', 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12, substring(concat(toString(number), key16), 1, 16) as vkey16, substring(concat(toString(number), iv16), 1, 16) as viv16 SELECT count() FROM {table} WHERE NOT ignore({func}) + diff --git a/tests/queries/0_stateless/04327_encrypt_decrypt_constant_key_no_iv.reference b/tests/queries/0_stateless/04327_encrypt_decrypt_constant_key_no_iv.reference new file mode 100644 index 000000000000..d64f5bb41824 --- /dev/null +++ b/tests/queries/0_stateless/04327_encrypt_decrypt_constant_key_no_iv.reference @@ -0,0 +1,9 @@ +aes-128-cbc 1 1 1 +aes-128-cbc 1 1 1 +aes-128-cbc 1 1 1 +aes-128-ctr 1 1 1 +aes-128-ctr 1 1 1 +aes-128-ctr 1 1 1 +aes-128-ofb 1 1 1 +aes-128-ofb 1 1 1 +aes-128-ofb 1 1 1 diff --git a/tests/queries/0_stateless/04327_encrypt_decrypt_constant_key_no_iv.sql b/tests/queries/0_stateless/04327_encrypt_decrypt_constant_key_no_iv.sql new file mode 100644 index 000000000000..dfdf846d44df --- /dev/null +++ b/tests/queries/0_stateless/04327_encrypt_decrypt_constant_key_no_iv.sql @@ -0,0 +1,40 @@ +-- Tags: no-fasttest +-- Tag no-fasttest: Depends on OpenSSL + +-- Encrypting/decrypting with a constant key (and absent or empty IV) must produce exactly +-- the same result as with a non-constant key, for every row. This guards against any +-- constant-key specialization diverging from the generic path -- e.g. by reusing a single +-- cipher context across rows and leaking the IV state of the previous row into the next. +-- The per-row (correct) path is forced by materializing the key into a non-constant column, +-- so every comparison below must be 1, independently of the data, for all stateful modes. +-- https://github.com/ClickHouse/ClickHouse/pull/99105 + +-- The input has a repeated row so that a leak of IV state across rows would change the +-- second occurrence and break the equality. + +WITH unhex('00112233445566778899aabbccddeeff') AS key +SELECT + 'aes-128-cbc' AS mode, + hex(encrypt('aes-128-cbc', p, key)) = hex(encrypt('aes-128-cbc', p, materialize(key))) AS encrypt_no_iv_matches, + hex(encrypt('aes-128-cbc', p, key, '')) = hex(encrypt('aes-128-cbc', p, materialize(key), '')) AS encrypt_empty_iv_matches, + decrypt('aes-128-cbc', encrypt('aes-128-cbc', p, key), key) = p AS roundtrip_no_iv_ok +FROM (SELECT arrayJoin(['the quick brown!', 'the quick brown!', 'lazy dog jumped!']) AS p) +ORDER BY p; + +WITH unhex('00112233445566778899aabbccddeeff') AS key +SELECT + 'aes-128-ctr' AS mode, + hex(encrypt('aes-128-ctr', p, key)) = hex(encrypt('aes-128-ctr', p, materialize(key))) AS encrypt_no_iv_matches, + hex(encrypt('aes-128-ctr', p, key, '')) = hex(encrypt('aes-128-ctr', p, materialize(key), '')) AS encrypt_empty_iv_matches, + decrypt('aes-128-ctr', encrypt('aes-128-ctr', p, key), key) = p AS roundtrip_no_iv_ok +FROM (SELECT arrayJoin(['the quick brown!', 'the quick brown!', 'lazy dog jumped!']) AS p) +ORDER BY p; + +WITH unhex('00112233445566778899aabbccddeeff') AS key +SELECT + 'aes-128-ofb' AS mode, + hex(encrypt('aes-128-ofb', p, key)) = hex(encrypt('aes-128-ofb', p, materialize(key))) AS encrypt_no_iv_matches, + hex(encrypt('aes-128-ofb', p, key, '')) = hex(encrypt('aes-128-ofb', p, materialize(key), '')) AS encrypt_empty_iv_matches, + decrypt('aes-128-ofb', encrypt('aes-128-ofb', p, key), key) = p AS roundtrip_no_iv_ok +FROM (SELECT arrayJoin(['the quick brown!', 'the quick brown!', 'lazy dog jumped!']) AS p) +ORDER BY p; diff --git a/tests/queries/0_stateless/04337_encrypt_decrypt_block_modes.reference b/tests/queries/0_stateless/04337_encrypt_decrypt_block_modes.reference new file mode 100644 index 000000000000..8105f25ef106 --- /dev/null +++ b/tests/queries/0_stateless/04337_encrypt_decrypt_block_modes.reference @@ -0,0 +1,109 @@ +0 659725796A48898D387A0479E312DFED 1C266089B8FDB7F388EF327CB8073AEC 659725796A48898D387A0479E312DFED 7882988A17A187B74A87DD844668B019 2A872AB50E2D6CDF793E50688CA81AB2 1FCB22633BC2DAAF72DDEC31F4D33649 659725796A48898D387A0479E312DFED 659725796A48898D387A0479E312DFED 7882988A17A187B74A87DD844668B019 5E237837FBA67B7DC14FE48BC939E402 +1 C485AD27EB3C204C0A80B9ED54F6900B 04621C19FFD1CE12E18CBA660B5971D3 C485AD27EB3C204C0A80B9ED54F6900B 1CF4E1E70546A8A3E0C0F386435DA251 8B4560C2A9BB2E06855013A5CF057267 723AF238C2D39C4FC22E4FC82A212E2E C485AD27EB3C204C0A80B9ED54F6900B C485AD27EB3C204C0A80B9ED54F6900B 1CF4E1E70546A8A3E0C0F386435DA251 3B8B0367358AB6F7FBC46F978C0F270B +15 CF4209E326AB1FC93CE31C1673F4F2BE EDBCCB82409AF1DDBEDBE972AC93D828 CF4209E326AB1FC93CE31C1673F4F2BE 5A619530DA40778BE550BE1ED4FC6B33 C13D7A5AD61F5C95D1A28E1606F7ED88 11CB5DFB742C59F1A89A01348A472212 CF4209E326AB1FC93CE31C1673F4F2BE CF4209E326AB1FC93CE31C1673F4F2BE 5A619530DA40778BE550BE1ED4FC6B33 AA14E7883A0F89C1E682D3223B259304 +16 B749D37076286B65F31A5BC450954471659725796A48898D387A0479E312DFED A9AE7258D41FC79B3CB7CD897EAB197A1C266089B8FDB7F388EF327CB8073AEC B749D37076286B65F31A5BC45095447139C275EE60F052DF3F248A4A8ADEF6EB 095CB6A4F17DED9F67F25F190B04F28F48A602921ADFD86109E872E9C6AF9914 BE2FC4A11AF06E44EBA73E7580976742A77A3A1F7D402A47C8D735F9F58B4961 16C6609938C7C38FA1FC6D14DE7C82095D1E0F1745D9D303F11DAB5B49E1E83E B749D37076286B65F31A5BC45095447139C275EE60F052DF3F248A4A8ADEF6EB B749D37076286B65F31A5BC450954471659725796A48898D387A0479E312DFED 095CB6A4F17DED9F67F25F190B04F28F48A602921ADFD86109E872E9C6AF9914 45BDF4D9D1C4DD3B10B5BF8A28DAA4569DA2647E21CA2BD4C99ACA08B9C7541E +17 B749D37076286B65F31A5BC450954471C485AD27EB3C204C0A80B9ED54F6900B A9AE7258D41FC79B3CB7CD897EAB197A04621C19FFD1CE12E18CBA660B5971D3 B749D37076286B65F31A5BC450954471EE8E10C4E23DA419E3875CF06B25C32F 095CB6A4F17DED9F67F25F190B04F28F989A3E1088C8AE1C18301087DF462A99 BE2FC4A11AF06E44EBA73E758097674232C4B65ECDA63B784C3C82525059EF15 16C6609938C7C38FA1FC6D14DE7C8209D80A630BAB0E281A50F698F32CDC141C B749D37076286B65F31A5BC450954471EE8E10C4E23DA419E3875CF06B25C32F B749D37076286B65F31A5BC450954471C485AD27EB3C204C0A80B9ED54F6900B 095CB6A4F17DED9F67F25F190B04F28F989A3E1088C8AE1C18301087DF462A99 45BDF4D9D1C4DD3B10B5BF8A28DAA4564CA6284FEA75D240494D1AF638870DF8 +31 B749D37076286B65F31A5BC450954471CF4209E326AB1FC93CE31C1673F4F2BE A9AE7258D41FC79B3CB7CD897EAB197AEDBCCB82409AF1DDBEDBE972AC93D828 B749D37076286B65F31A5BC4509544711516B0B6F44074A554D3516A5FCD6B10 095CB6A4F17DED9F67F25F190B04F28FF5CC008B7E3E3A932FA0152E2361496B BE2FC4A11AF06E44EBA73E7580976742730FAADE30C3EEC9EED76E76B34F872E 16C6609938C7C38FA1FC6D14DE7C8209AADAFEC86997887DC8882F627C43353D B749D37076286B65F31A5BC4509544711516B0B6F44074A554D3516A5FCD6B10 B749D37076286B65F31A5BC450954471CF4209E326AB1FC93CE31C1673F4F2BE 095CB6A4F17DED9F67F25F190B04F28FF5CC008B7E3E3A932FA0152E2361496B 45BDF4D9D1C4DD3B10B5BF8A28DAA456488A32E6DDA9932E43795AFDA6D8D76B +32 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471659725796A48898D387A0479E312DFED A9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197A1C266089B8FDB7F388EF327CB8073AEC B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36B007925C4689B1B5D63866A942926FCC 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD191582D5F415F54133C9E6097472DA4C6 BE2FC4A11AF06E44EBA73E7580976742EEE45429E30708F537BD4612C773E7C43F166CB117F9B77C310866A57514660F 16C6609938C7C38FA1FC6D14DE7C82097554C4C5605CF3348927BB521440B73794F07AE497E87FA3179CF0BC4EC676C1 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36B007925C4689B1B5D63866A942926FCC B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471659725796A48898D387A0479E312DFED 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD191582D5F415F54133C9E6097472DA4C6 45BDF4D9D1C4DD3B10B5BF8A28DAA45633F053B0BAFFBE6DE34BD3B33CF1E1A14A11A777E511CBD205D52DDDC75E0034 +33 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471C485AD27EB3C204C0A80B9ED54F6900B A9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197A04621C19FFD1CE12E18CBA660B5971D3 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C3608FA661F5F7C21C4E2EC27899EA4DB21 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD1E6FC98B9C8E54BD989826C9C37BF1537 BE2FC4A11AF06E44EBA73E7580976742EEE45429E30708F537BD4612C773E7C4B7722C18D17A852CED4B2AC67A4D7158 16C6609938C7C38FA1FC6D14DE7C82097554C4C5605CF3348927BB521440B737FF7BFBB00C07795E8800928AA8EF1CD5 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C3608FA661F5F7C21C4E2EC27899EA4DB21 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471C485AD27EB3C204C0A80B9ED54F6900B 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD1E6FC98B9C8E54BD989826C9C37BF1537 45BDF4D9D1C4DD3B10B5BF8A28DAA45633F053B0BAFFBE6DE34BD3B33CF1E1A11B73F389D0B81BF0FDB9DB1EAED02090 +63 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471CF4209E326AB1FC93CE31C1673F4F2BE A9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AEDBCCB82409AF1DDBEDBE972AC93D828 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCAA08EC6B8E9B54CF3CECDD37A377CF9F 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D675D95D5068E752718B51A63C4369AC80F BE2FC4A11AF06E44EBA73E7580976742EEE45429E30708F537BD4612C773E7C420B4061B6DA31FC2DFD5B743378BB6D7E504B344D56ED7632E2A1BB511519164 16C6609938C7C38FA1FC6D14DE7C82097554C4C5605CF3348927BB521440B73755C0C8E43F75ED1C69A1E4C457865B5C591471B1AC255036A0C0E2A9D06A4DC1 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCAA08EC6B8E9B54CF3CECDD37A377CF9F B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471CF4209E326AB1FC93CE31C1673F4F2BE 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D675D95D5068E752718B51A63C4369AC80F 45BDF4D9D1C4DD3B10B5BF8A28DAA45633F053B0BAFFBE6DE34BD3B33CF1E1A1C73E837E67DA0A1FD7B9E135EC0444D3357579A077775F7B6582EA37DD47A0D8 +64 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471659725796A48898D387A0479E312DFED A9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197A1C266089B8FDB7F388EF327CB8073AEC B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569C5255804448E3EF680E8D4E5B5C0CEEF4 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3C9393DE5F9DD25F789C29FE00335DE56 BE2FC4A11AF06E44EBA73E7580976742EEE45429E30708F537BD4612C773E7C420B4061B6DA31FC2DFD5B743378BB6D7139A824875A0123E46B5E2A60A092F3590A1B1746833FB1291B1425D7261A02C 16C6609938C7C38FA1FC6D14DE7C82097554C4C5605CF3348927BB521440B73755C0C8E43F75ED1C69A1E4C457865B5C5752E604FB33B99FFEE5DA1AC198C775C37621B5AFF473BDB6D1B62BD84FD789 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569C5255804448E3EF680E8D4E5B5C0CEEF4 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471659725796A48898D387A0479E312DFED 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3C9393DE5F9DD25F789C29FE00335DE56 45BDF4D9D1C4DD3B10B5BF8A28DAA45633F053B0BAFFBE6DE34BD3B33CF1E1A1C73E837E67DA0A1FD7B9E135EC0444D312BF117500241902A787853FB1872B7EA8B3B2ABDD0FE392C9385BDFFFFFCF52 +65 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471C485AD27EB3C204C0A80B9ED54F6900B A9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197A04621C19FFD1CE12E18CBA660B5971D3 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569C7C39A39FC069833BC3072D484170CBB8 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3D6C36DD1CFFDC405BD64CE7B84AA4C73 BE2FC4A11AF06E44EBA73E7580976742EEE45429E30708F537BD4612C773E7C420B4061B6DA31FC2DFD5B743378BB6D7139A824875A0123E46B5E2A60A092F3516CD10B8A187AB6B104A1030A1CA70B9 16C6609938C7C38FA1FC6D14DE7C82097554C4C5605CF3348927BB521440B73755C0C8E43F75ED1C69A1E4C457865B5C5752E604FB33B99FFEE5DA1AC198C77529E76191A73AA5DC858CDA584F377228 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569C7C39A39FC069833BC3072D484170CBB8 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471C485AD27EB3C204C0A80B9ED54F6900B 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3D6C36DD1CFFDC405BD64CE7B84AA4C73 45BDF4D9D1C4DD3B10B5BF8A28DAA45633F053B0BAFFBE6DE34BD3B33CF1E1A1C73E837E67DA0A1FD7B9E135EC0444D312BF117500241902A787853FB1872B7EC6556EDC2904B5428926480D422A2296 +100 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC45095447196D6E06EEFF60CD630E2E38B71F1B1C5 A9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197ADF04B00E226FBFE41740540216508EA5 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569CAF71389BA8805F378DF8569E0C6EA4EFBDB4ED454AA31E6752642F0DE6D6D0F6D43C9B367ECF39A61DBA4EE14BFB9DB1 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3B4931B48B87B162EC0CAC2E6FA007CA5FB92B5E916071EB8822A19966D13F9E03C1AA5F31D49676EAAFD076317B010F1 BE2FC4A11AF06E44EBA73E7580976742EEE45429E30708F537BD4612C773E7C420B4061B6DA31FC2DFD5B743378BB6D7139A824875A0123E46B5E2A60A092F35E9455DC08078BABC7F01E6C4B521053B55B7B7EFC5B91E22996E7F4F6FA7627CFD6B580C9A06E9524ABE4720D3641021 16C6609938C7C38FA1FC6D14DE7C82097554C4C5605CF3348927BB521440B73755C0C8E43F75ED1C69A1E4C457865B5C5752E604FB33B99FFEE5DA1AC198C775D2465EB2E2BBA7D141A58B8D1CDE6BF3E7636AE188352E409F652249D5E4855E14E03AA445B424F4BDF52CBC89EC7787 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569CAF71389BA8805F378DF8569E0C6EA4EFBDB4ED454AA31E6752642F0DE6D6D0F6D43C9B367ECF39A61DBA4EE14BFB9DB1 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC45095447196D6E06EEFF60CD630E2E38B71F1B1C5 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3B4931B48B87B162EC0CAC2E6FA007CA5FB92B5E916071EB8822A19966D13F9E03C1AA5F31D49676EAAFD076317B010F1 45BDF4D9D1C4DD3B10B5BF8A28DAA45633F053B0BAFFBE6DE34BD3B33CF1E1A1C73E837E67DA0A1FD7B9E135EC0444D312BF117500241902A787853FB1872B7E2AD561C0DBE59DAB1682B47346CC3310554458BF1CF8745ECCC3EC9CC4E5E89989F16E0D14F0AFBBB75A631B4D757CC8 +256 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471659725796A48898D387A0479E312DFED A9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197A1C266089B8FDB7F388EF327CB8073AEC B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569CAF71389BA8805F378DF8569E0C6EA4EFBDB4ED454AA31E6752642F0DE6D6D0F686F546610931D3DD08BAEF9D73E0143FC9EEFD727DA23FAD15B3B7D0EFA6FBA9B2A09CA98EC2FF3C7E6C752DCBDF8039959706F750598D29A80792FEFCE50777670585EA610706C77A1754EE53D547DE2F72521F201B1370BDB93DE3CE228482C26765BD16888CC27AA3D5770A7EEC71CC37DE41587C650FD74ABE1516764B399DED8D91BD893EC9BFB68DABB23E3C10E91C96DE083AB317979040BF09A519768E47ADE64B0B043B4AF2CF1492901577 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3B4931B48B87B162EC0CAC2E6FA007CA5FB92B5E916071EB8822A19966D13F9E06A92652EC08224C91258A7C382B39AFC7E56BC2D08B6489955F1694C7E5C8CD979E3FD3F6DC4DAD116EDC580A3067B2237BAF1DEC9A97F16C6DA4C3F077D8E434F67DC0FE7071A9D545665CB7F4338C05D7D981435E9BA71264D97FD5D5C81200989913B3D0012E98B279ACD7617401CF5F24E4A26251F3DAB00D10D3F091C388471F9A51775745CFD9A6189CA24EA05614356AF70EA5D3CDD06BE0AB5AAADD18FCDE278C004144180A6EC82EBD702C4 BE2FC4A11AF06E44EBA73E7580976742EEE45429E30708F537BD4612C773E7C420B4061B6DA31FC2DFD5B743378BB6D7139A824875A0123E46B5E2A60A092F35E9455DC08078BABC7F01E6C4B521053B55B7B7EFC5B91E22996E7F4F6FA7627C148BDB313FCC235999299941BD80F2593DCBF48442B2E675F397E23AB37896A24346EF27A0C90F5BBD80E937458C2416323E50482E434D5C61B9434E13105CABC67ED98E5C59B8ED7DA37D6807E7EA78A0B9DE07F82F0B9B7D7F22B6B6BC48C5E2A9B21A2913932DB44A5F191C9ADC1F7CBB6DDF72889F00653EEB49C9FAA9BB37D36F4BB921E1352B4AB5B34AFF3D196EF68C4D996687D5E1DD7D037112725A800DE6B2EFCDE9126A52626F42766EFF 16C6609938C7C38FA1FC6D14DE7C82097554C4C5605CF3348927BB521440B73755C0C8E43F75ED1C69A1E4C457865B5C5752E604FB33B99FFEE5DA1AC198C775D2465EB2E2BBA7D141A58B8D1CDE6BF3E7636AE188352E409F652249D5E4855EFD48F8EB6E58CBBAE1944E50C97CCF055B0975ED59B87E75F5458B0255BDB72BD75EDAB4EF3DF41374A1D347D71054524C1983B505F5B189995D7C16B1FD07B576EE941E194E0F26572D9C0B9AA479C9B356122D457A14506D236F18D7EC5AFABEEDF8206CC16DC5A4AB81671F1605AF9DED49817FCFA85A45145837DAEC60C9D71BB8F51C6523F942BBE8CC5C8C5FF17B132CB01EC7380FEAECC498E2851E96C9FD56E8381ACC9894B10C8A5490878A B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569CAF71389BA8805F378DF8569E0C6EA4EFBDB4ED454AA31E6752642F0DE6D6D0F686F546610931D3DD08BAEF9D73E0143FC9EEFD727DA23FAD15B3B7D0EFA6FBA9B2A09CA98EC2FF3C7E6C752DCBDF8039959706F750598D29A80792FEFCE50777670585EA610706C77A1754EE53D547DE2F72521F201B1370BDB93DE3CE228482C26765BD16888CC27AA3D5770A7EEC71CC37DE41587C650FD74ABE1516764B399DED8D91BD893EC9BFB68DABB23E3C10E91C96DE083AB317979040BF09A519768E47ADE64B0B043B4AF2CF1492901577 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471659725796A48898D387A0479E312DFED 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3B4931B48B87B162EC0CAC2E6FA007CA5FB92B5E916071EB8822A19966D13F9E06A92652EC08224C91258A7C382B39AFC7E56BC2D08B6489955F1694C7E5C8CD979E3FD3F6DC4DAD116EDC580A3067B2237BAF1DEC9A97F16C6DA4C3F077D8E434F67DC0FE7071A9D545665CB7F4338C05D7D981435E9BA71264D97FD5D5C81200989913B3D0012E98B279ACD7617401CF5F24E4A26251F3DAB00D10D3F091C388471F9A51775745CFD9A6189CA24EA05614356AF70EA5D3CDD06BE0AB5AAADD18FCDE278C004144180A6EC82EBD702C4 45BDF4D9D1C4DD3B10B5BF8A28DAA45633F053B0BAFFBE6DE34BD3B33CF1E1A1C73E837E67DA0A1FD7B9E135EC0444D312BF117500241902A787853FB1872B7E2AD561C0DBE59DAB1682B47346CC3310554458BF1CF8745ECCC3EC9CC4E5E899E730897B7577C9A805D84344979BBE1E07DB53391A8FA1BC7C307F4AB141DFF5A605A6E9BE3B3CE3114444348C86B5302BE9BB966F899895981499EA63AD9EC5AD687F517136C4445910C9BA871E299E69DC0FFA87775A27BA738139130BF02729F736517DF4DF7A7B0B79CE00C90CA87542A00CDB8D14B8268A06B349689519833868C1C73EA3188FB5596BE818C853C9D0C6E281C627906E89C25F55C5CD9F79B152CFF909B76C34042AF602A1E1AA +1000 B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC4509544714961DD81E307363FFA211483C09D90EB A9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197AA9AE7258D41FC79B3CB7CD897EAB197A72060A3A140767AC8D60422D6AEA7367 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569CAF71389BA8805F378DF8569E0C6EA4EFBDB4ED454AA31E6752642F0DE6D6D0F686F546610931D3DD08BAEF9D73E0143FC9EEFD727DA23FAD15B3B7D0EFA6FBA9B2A09CA98EC2FF3C7E6C752DCBDF8039959706F750598D29A80792FEFCE50777670585EA610706C77A1754EE53D547DE2F72521F201B1370BDB93DE3CE228482C26765BD16888CC27AA3D5770A7EEC71CC37DE41587C650FD74ABE1516764B399DED8D91BD893EC9BFB68DABB23E3C10E91C96DE083AB317979040BF09A51976E6F741CDD24059AF0F1752A1176AB0572569FF4F601C16C45D97E5AB8EED441DA0BC68DAB9BCACA763347F784C27141DD21A8BBE2FE4BD8C1020313688440DE1AFF1E6CEACE97EAF41247D155CB83D217415D16101984981A31DFB10DC0F850779303B70BED9D7F8529120A1815D96C02BECAD36B7888CE3BAA61AC70DE421264B482035956CCA89BE5459FB4FA4533A452A4B13F9F1AB655E417EA68878B1B58D562C12D7C22A9BC9CA1E543633CC68CDF7C2A88B769DF8E01C194B264F4E541859DB9250CB5A9C1079A452D4315A8468A2BAB08E8F9E9BC00AD2CB66A61CEE851ED58CF0DB9B2B3C3B384A271AD717F0CF8C27E956177036E5FDB315D8F72B8C3532584CB396AD08E7423D4386FD1DDE3457623C1F1B20178A811895FCEC93A58203DA3206CAA9658D71DFA73E2A819F1B73E54BDCC39484392B529E490CA1891C7C213F23BC9AC58F92CDBEC852D3FE2061936B25339EF64FDD550C199761A87277421FE659ED8CA0346ABDC7DF25EDB48B08019ABE39168A4112DF019534315E8A7C272D19B8D5013B7E89E0D1119637B11C274EC2B06D3CFFD61EB5D1544C0B1D6F2798B00F1D76DCB82B0C8F9FC16E1355571C0C4BB46F6B94213C32277D4625984495331D77B38652791D698DF4C86C9A1DB43C2DF73D54D05061C3FE85E7400B871CFF3DEF40DBB6096ED2EEEA1E33CC924419540DCBA69A81DA930DFE0677EAEC8638FC70DA768745CBC76AE0C52080C96C80160CD01620FAB7EF3B6636BCA936F2A289BAB897FF1D15090699E2784CBAF103EB1FC495BB76E669F76ACF9636B02719C655C88C0CFD2250DF5AA4E8105BA25F0C4C2D8EB0D767FBAB1F1778F1092DD82AC5584E196440FF0988046465E73582778BF54CC21316346EC20FF1D6DF458FC95C41D73CDDCFF910CFF0649ABBFAAA228709A85A4BC9AB63C359B1AD1D6346656BAA0494E125797C55685ED10CD5F452269586B61BF16CD231FDDEAEDC4B8D2B918AAC82992624B9E41E65B9312921B96EF212DDEC733F35AB8183E89ABE6F7F515EDB4285AA812E 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3B4931B48B87B162EC0CAC2E6FA007CA5FB92B5E916071EB8822A19966D13F9E06A92652EC08224C91258A7C382B39AFC7E56BC2D08B6489955F1694C7E5C8CD979E3FD3F6DC4DAD116EDC580A3067B2237BAF1DEC9A97F16C6DA4C3F077D8E434F67DC0FE7071A9D545665CB7F4338C05D7D981435E9BA71264D97FD5D5C81200989913B3D0012E98B279ACD7617401CF5F24E4A26251F3DAB00D10D3F091C388471F9A51775745CFD9A6189CA24EA05614356AF70EA5D3CDD06BE0AB5AAADD1E5454A94D96DB99C8DA95D792BE868FBF387CFE56E440CEBD5E1E5FA7E57782C804DEB3FC766D6F3474C27A29E8EA7D98A38DDBE92610EA422D70C439894ECF25528FDA1A0248465F830B5F8AAF2BFBA167AE9BCB7ECCDBCFDD91E5DF4407E9D8FFC813841064CBD7AC66248A7BF9638A31D1EEDC76CABFC0825928C4B6F9EE40DCEF36B44CFE48C3120BB91778AEC979C8D7AC4FCA2D2E8AA9A957968FF5ED41EF8749D50803BDE611FAC9DB93F1B0BD9CCD1413366D7DF4A270159B283AC21E801EE47779776AF1BEC83C2886B77F120E30C610F5065414399483F1F5D5B12BE9940C5FBDE773CF6A95A362DA094BEBC27FB65FED10AE2E70D5787D356A2641A8A5BB7D05371A8F0E77CCEB4FA52C91261B26A02DFFA8456582CF25EAAB05CED7778D1150BF56CB82A69BCB10966EE8D3CE59177F54B7143D6C207B10B06299C89A161A3561766436216E8110D1AE7654B583A871390CB3EA5F9D8C1BB4650DAF14556DC8F6F5B6A41F2D376F06DA5A3DAB09E00A03D50898B640303C8E10909D18F9400C5292E99F7977AB88632837F69BE9AD3A01B233FC4C11019566D17AF724640318137B59A3132C5F50A8AA18B724131C55F869BEDAD67A6BC4153A9D942528EF401FE0F6A9D925CFF9A7169BE72A6577569697ABA1CE3B0B0E8CBB7DCCD85D907FCC1AF8D38848F9DBD088FA62C2234318C017966666DDC5578B095F9F0623BC099B9ECAD9EECE9ADD28512997C8E90F80D0B89C414078B3B8C09FE68B01F1523C5D970D9AADCA197D3F7D3AF6155874A6A343B34357B3EE6E41EB42ABEF7F903527E09D4DEBD7CE735F785E6E2B04DB5EA8AAEB00A9E8C0EF88A184396B0B062E47A7D0F852D5C99CD57EAAF23ADB6B4B585E08782F8302AD69EDDCC306017CF347D5AF3B4A0E7DFF33317924C8B921B466B823D5E89B7E58942A04E02EE53A2A0C58ECEDABA307CC5A7398F4E82DCE3687C06A9963DDCFFB3966720638201A083AF15571312BB523B52C8BEB784F0FAF13AAC20009462521E6B10059D103B579F270825B2E33766D1FD4F BE2FC4A11AF06E44EBA73E7580976742EEE45429E30708F537BD4612C773E7C420B4061B6DA31FC2DFD5B743378BB6D7139A824875A0123E46B5E2A60A092F35E9455DC08078BABC7F01E6C4B521053B55B7B7EFC5B91E22996E7F4F6FA7627C148BDB313FCC235999299941BD80F2593DCBF48442B2E675F397E23AB37896A24346EF27A0C90F5BBD80E937458C2416323E50482E434D5C61B9434E13105CABC67ED98E5C59B8ED7DA37D6807E7EA78A0B9DE07F82F0B9B7D7F22B6B6BC48C5E2A9B21A2913932DB44A5F191C9ADC1F7CBB6DDF72889F00653EEB49C9FAA9BB37D36F4BB921E1352B4AB5B34AFF3D196EF68C4D996687D5E1DD7D037112725AD35966F9FEE7D674C4EFDFC8925461A8BF354737FCB89ECBAA240BC4CDBFF0A471722F5EACB133EDB40FF99002E4D9B6EBC49BC6D5796E448F19DA0875EDC2A81759966A31D740A4143A1056DD1C09E489AEEC064035140848E589455C9752D2C02C16B57C1DF027C4B25F0E0674BD0353B5229180B73D5FE0843E600951B5BA6297C8095BFCAEC7CF7A7C0F0EE91C4AF2C3A71B9002B1629285765E8B106AE00829DF06B0B13683A61947A5638C72C3D98AB4732BC01BDDFA86A387358903882ECF2C7426C512B847F8FEC65BE711CFED78004D467775A43451F8F25734DD629120CB27309ECB47727D1E9C2BF34BD132EEF496E05FBD4458EE1838F8B347ECEC79ABF381AE088CC4F57BA79546A104040D99B1AE3CD0A6DDFE5C425BA01CD70DAC8DF6901B851DA634C106BC9068801A0557B109ADA125B75A0E68C7990F4B06CB97E3ECD019B87D3F79FAEC20B2BE1F4EE1C2195632D39EEA38D132281504D7908179DBAC482018DFFF9FD7A74F435C4003A68EBB3CB86184462CF472CA26D41CB53357864C7E6617EF89C37B47E0ACE0412F684335021EB17443892C68326834B739633409A57B046E501BE61823091692BD1B1CDBE363AE681D128C98C0365D7B2C366D2A2A5982C4E3AE80FC170E68101E0933B6BAED973BFCEC84B3D8C2B1C70394025A5089B57FD6025927319FEA6AAC3C622E48FE4A5EEE70EF084934543E19B6F162FD08DDF19347434AFE650553DB80900BB83E07C6452FC465BCAD2950927399039188DE5ED789E3833318F7B6801149F4F25845E3D3BAE33123328EC2B2ADA99EADAA0F1CFBAABB84F5CDA4DC02C9185B0C8BDBAD086D700D45B2CE9EF3FF7D59156ABBCD29997BD79FC3DF0B3871B76804370FC425B547C6B828EB13F31C0CA52228FD4B4531836BF428A8C06B512914D8CB9A38ADC3B1324D9B05F73C1CA5BF6F43C2DE891ABE22BD419702A57341B23689EC8C8B002C7D198AC497C23B93FA81DD6C1B65D91843396C4869761CE73A1482C9FC5FE0B36AE42A6E421EE4E6A94CC72F4655C58EE35E 16C6609938C7C38FA1FC6D14DE7C82097554C4C5605CF3348927BB521440B73755C0C8E43F75ED1C69A1E4C457865B5C5752E604FB33B99FFEE5DA1AC198C775D2465EB2E2BBA7D141A58B8D1CDE6BF3E7636AE188352E409F652249D5E4855EFD48F8EB6E58CBBAE1944E50C97CCF055B0975ED59B87E75F5458B0255BDB72BD75EDAB4EF3DF41374A1D347D71054524C1983B505F5B189995D7C16B1FD07B576EE941E194E0F26572D9C0B9AA479C9B356122D457A14506D236F18D7EC5AFABEEDF8206CC16DC5A4AB81671F1605AF9DED49817FCFA85A45145837DAEC60C9D71BB8F51C6523F942BBE8CC5C8C5FF17B132CB01EC7380FEAECC498E2851E966DEF168204D5E07F83C027542BCCE1ADE15B4D51E02B5BA6E5F3395F0AF05A6207CCF989217C4521EFC17E2D4A751DD11B0A10D89BAA009B8AD1846C401357E531E63564B95818EC5BBF39A4FC22CB49B3DDA872F1C23D03AE0BD321DCF0432D83A6067EF4BD553954BC9696796171FAEE25FAF29DC1441D5E6A7B9523A3015EC45429BF1B5DC2D0E77FF89889A8C20E0F04B226A34EFBB72A048735BC225CC272D3FA04B00456844DE8B9790587193BBCB52AD8EB8D778A5283E673B3B4F6901573EF62A2A5C2D571D176F98357E3A0DC4AE59373B2D2952888D0E29DC35726484C8B9EA96B962ECE77E3AEBC492EE19B89FDF27D38752E1AC746DE4BDFB21A07F7FE69479CA4995524075AC7D5F61DA5A686C0DF13D899DE2CDD67186EE3AA38AB16264688F9E578DA7F41BD72A7EE4060C69B0B9B8BCB5D566D073EC69C182F5FEEDF5FF1B570282BE2AE20392E5568D3F577351F656E7670DEB84A008181B8015D9C88DC59C2BA70D33DD1AABB9A9C0DAB09D1A2811BC35A2C9888C5A5286C5DE7D93195CECE893F71BA0358CE1F89CD033822DFB0056B9C42F6BF4D36D6AF3BF040A6CE7E9BEE67C9E54EEE915B1F74DAE2C988E161A1DB7BE53B56D92B8F8A630F5EA6E8481A34EB0D9F13A7EF3484875175530C8C64241DEE379C5EFA7EBB97C1E5015475BB593A22B109662BC7B4B9A6EE3E3DB5D67078DA22927640243AF1048B80B6F452ABAE0A1BB0E671924AFAE38B19960F61A96AD7D0E67DD307C244F6AD853551763A1A38E2BE7B697B53D274D590E397CD256279F77A4FB3FB0676A29C1971C7E6FDC40101928CDFC6ED52A5FED6BAF0C8220532CB46ED0250503CD8EED5D8D1ED13B26A97FB1C2576D696C2C0F41B900340DC9C6636A51163054001D9DE542321DAFBF638F457EEFAEB7EB8953F82186F3D7C712D895FD524360272CCD9A07C4B58CF85F2CE345CD72A8AB114700E32538B1A0E058146B573869E883853A6F7B945B07378F76AE2C01562D7451B8D5759F00C5F6262D21925732FF5969CBB9B38DEE785511295B8 B749D37076286B65F31A5BC45095447100915C25178C20FC562F124ADE587C36C7A53B164F4E515A90C264328262D3BCBEB3AB906F49AE7993238C9D6287569CAF71389BA8805F378DF8569E0C6EA4EFBDB4ED454AA31E6752642F0DE6D6D0F686F546610931D3DD08BAEF9D73E0143FC9EEFD727DA23FAD15B3B7D0EFA6FBA9B2A09CA98EC2FF3C7E6C752DCBDF8039959706F750598D29A80792FEFCE50777670585EA610706C77A1754EE53D547DE2F72521F201B1370BDB93DE3CE228482C26765BD16888CC27AA3D5770A7EEC71CC37DE41587C650FD74ABE1516764B399DED8D91BD893EC9BFB68DABB23E3C10E91C96DE083AB317979040BF09A51976E6F741CDD24059AF0F1752A1176AB0572569FF4F601C16C45D97E5AB8EED441DA0BC68DAB9BCACA763347F784C27141DD21A8BBE2FE4BD8C1020313688440DE1AFF1E6CEACE97EAF41247D155CB83D217415D16101984981A31DFB10DC0F850779303B70BED9D7F8529120A1815D96C02BECAD36B7888CE3BAA61AC70DE421264B482035956CCA89BE5459FB4FA4533A452A4B13F9F1AB655E417EA68878B1B58D562C12D7C22A9BC9CA1E543633CC68CDF7C2A88B769DF8E01C194B264F4E541859DB9250CB5A9C1079A452D4315A8468A2BAB08E8F9E9BC00AD2CB66A61CEE851ED58CF0DB9B2B3C3B384A271AD717F0CF8C27E956177036E5FDB315D8F72B8C3532584CB396AD08E7423D4386FD1DDE3457623C1F1B20178A811895FCEC93A58203DA3206CAA9658D71DFA73E2A819F1B73E54BDCC39484392B529E490CA1891C7C213F23BC9AC58F92CDBEC852D3FE2061936B25339EF64FDD550C199761A87277421FE659ED8CA0346ABDC7DF25EDB48B08019ABE39168A4112DF019534315E8A7C272D19B8D5013B7E89E0D1119637B11C274EC2B06D3CFFD61EB5D1544C0B1D6F2798B00F1D76DCB82B0C8F9FC16E1355571C0C4BB46F6B94213C32277D4625984495331D77B38652791D698DF4C86C9A1DB43C2DF73D54D05061C3FE85E7400B871CFF3DEF40DBB6096ED2EEEA1E33CC924419540DCBA69A81DA930DFE0677EAEC8638FC70DA768745CBC76AE0C52080C96C80160CD01620FAB7EF3B6636BCA936F2A289BAB897FF1D15090699E2784CBAF103EB1FC495BB76E669F76ACF9636B02719C655C88C0CFD2250DF5AA4E8105BA25F0C4C2D8EB0D767FBAB1F1778F1092DD82AC5584E196440FF0988046465E73582778BF54CC21316346EC20FF1D6DF458FC95C41D73CDDCFF910CFF0649ABBFAAA228709A85A4BC9AB63C359B1AD1D6346656BAA0494E125797C55685ED10CD5F452269586B61BF16CD231FDDEAEDC4B8D2B918AAC82992624B9E41E65B9312921B96EF212DDEC733F35AB8183E89ABE6F7F515EDB4285AA812E B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC450954471B749D37076286B65F31A5BC4509544714961DD81E307363FFA211483C09D90EB 095CB6A4F17DED9F67F25F190B04F28F4E34B7100D4199D0D3FA1773A544ADD17B9EE051EBD2EA00D29833A7F6E06D67BD2A006E348459F1B41EB1D5DE52E4B3B4931B48B87B162EC0CAC2E6FA007CA5FB92B5E916071EB8822A19966D13F9E06A92652EC08224C91258A7C382B39AFC7E56BC2D08B6489955F1694C7E5C8CD979E3FD3F6DC4DAD116EDC580A3067B2237BAF1DEC9A97F16C6DA4C3F077D8E434F67DC0FE7071A9D545665CB7F4338C05D7D981435E9BA71264D97FD5D5C81200989913B3D0012E98B279ACD7617401CF5F24E4A26251F3DAB00D10D3F091C388471F9A51775745CFD9A6189CA24EA05614356AF70EA5D3CDD06BE0AB5AAADD1E5454A94D96DB99C8DA95D792BE868FBF387CFE56E440CEBD5E1E5FA7E57782C804DEB3FC766D6F3474C27A29E8EA7D98A38DDBE92610EA422D70C439894ECF25528FDA1A0248465F830B5F8AAF2BFBA167AE9BCB7ECCDBCFDD91E5DF4407E9D8FFC813841064CBD7AC66248A7BF9638A31D1EEDC76CABFC0825928C4B6F9EE40DCEF36B44CFE48C3120BB91778AEC979C8D7AC4FCA2D2E8AA9A957968FF5ED41EF8749D50803BDE611FAC9DB93F1B0BD9CCD1413366D7DF4A270159B283AC21E801EE47779776AF1BEC83C2886B77F120E30C610F5065414399483F1F5D5B12BE9940C5FBDE773CF6A95A362DA094BEBC27FB65FED10AE2E70D5787D356A2641A8A5BB7D05371A8F0E77CCEB4FA52C91261B26A02DFFA8456582CF25EAAB05CED7778D1150BF56CB82A69BCB10966EE8D3CE59177F54B7143D6C207B10B06299C89A161A3561766436216E8110D1AE7654B583A871390CB3EA5F9D8C1BB4650DAF14556DC8F6F5B6A41F2D376F06DA5A3DAB09E00A03D50898B640303C8E10909D18F9400C5292E99F7977AB88632837F69BE9AD3A01B233FC4C11019566D17AF724640318137B59A3132C5F50A8AA18B724131C55F869BEDAD67A6BC4153A9D942528EF401FE0F6A9D925CFF9A7169BE72A6577569697ABA1CE3B0B0E8CBB7DCCD85D907FCC1AF8D38848F9DBD088FA62C2234318C017966666DDC5578B095F9F0623BC099B9ECAD9EECE9ADD28512997C8E90F80D0B89C414078B3B8C09FE68B01F1523C5D970D9AADCA197D3F7D3AF6155874A6A343B34357B3EE6E41EB42ABEF7F903527E09D4DEBD7CE735F785E6E2B04DB5EA8AAEB00A9E8C0EF88A184396B0B062E47A7D0F852D5C99CD57EAAF23ADB6B4B585E08782F8302AD69EDDCC306017CF347D5AF3B4A0E7DFF33317924C8B921B466B823D5E89B7E58942A04E02EE53A2A0C58ECEDABA307CC5A7398F4E82DCE3687C06A9963DDCFFB3966720638201A083AF15571312BB523B52C8BEB784F0FAF13AAC20009462521E6B10059D103B579F270825B2E33766D1FD4F 45BDF4D9D1C4DD3B10B5BF8A28DAA45633F053B0BAFFBE6DE34BD3B33CF1E1A1C73E837E67DA0A1FD7B9E135EC0444D312BF117500241902A787853FB1872B7E2AD561C0DBE59DAB1682B47346CC3310554458BF1CF8745ECCC3EC9CC4E5E899E730897B7577C9A805D84344979BBE1E07DB53391A8FA1BC7C307F4AB141DFF5A605A6E9BE3B3CE3114444348C86B5302BE9BB966F899895981499EA63AD9EC5AD687F517136C4445910C9BA871E299E69DC0FFA87775A27BA738139130BF02729F736517DF4DF7A7B0B79CE00C90CA87542A00CDB8D14B8268A06B349689519833868C1C73EA3188FB5596BE818C853C9D0C6E281C627906E89C25F55C5CD9F2C8C3DFE1A4B5E3491BB0D5A958289D3BF75A96D9ADCA5AB476F7387319E7A6B157E3808ABF31FBE7AAA681137825AA8AC757AB3EC9718A795DD352485B1634492E603BA08EF812E72AA226733BE0E0F043B63CBC5B90C56CF2632EF99A23104DD32BA3341927C6A426AE86F358E593F0D0D5DB30447C57823DEFF2B9B7CC44F33A7B13751C13F8D9708307FC8AE5DB918E275C7C3DD9734D34F9FEABF3ACE02C2F9DD23897431819FCB708F3043E67F0F356437DF0BD109C9C741006720B6DF145FA32F75D382433EFC8BE80795D1E414CECFC8D378BDBD3EBD01909C6F4603DC661DDF6DAE92FCB8395F7C51C14F5F47A784DA2805D9E6EF149BF1B6031584DE4F5BF09CA7AA01A553F034C26E90A1E50FE2CDEFF6D92DFE3494BB0486363E446263DAA7CFC3F0D137BC2DB1C91D495B32129F8E4A6B81383B107E92DC06D1544F051B149FA25FC56E248C3E9B614DB897B34BD111D5057E8BA2B72B313A29516FF45E501B69A907770D5BCCD8FE26C12C70A4A27B893711FD94C5BC7A96DB594970A8AD0E41E5E3966C89886F684D1026D1DF94BE9EA301E1D3087E606B4D87B57589C6359044639C8E0FCF0021ACE824AA214338BFA31F0921193DD293BFF8FD2528F94456AF5DCCF73DB31227DD0BFFEAF2967ABBF6FC1F028D836C904D3C749ADC6320F40F0874242D7785138E0556AF98A5179F95A13A9B2160F94F31FFCE656AF1131BD332B82D8D339842C379F9781C6B2DA96BBEA2B50AB65878F6D49651461CB6A893682AA50624C5E082331D84E7B78871450E7150342780B109B17FDDCBEFB27E2608A9E597FC23C392454C3123FA7242784DC86938C83D1EB1579EB448D131FCC655ADE7CFC9033B4110667ED01784E0000A2A3A37D3F67523AB27731555A45E06C0F9E0272C97A67BB548EFFA1FEAAC13EB08C0BF2538EB8723FCE511A68D94075859E42D4B2F316E78300051965E517C9AED2E2B65D17329EEBA190A9CE2BA707088DA1EC710CFBF3AB365E593DC394FD742C5BBB1A1B030A4860CBB76374C396FB6C0B98442970F +0 AC02CCCECCCC9E1F540EDA07BE095217 AC02CCCECCCC9E1F540EDA07BE095217 57924E4142C320AD1E0B3835F9626006 3A8086CB311B81C76525A566FAFD3F25 23F46422E9666FF481A8CAD401B3787D +1 662075ECD004D9F69A9650F7032037E1 662075ECD004D9F69A9650F7032037E1 F2EBB09C54C47833430D3589DF2370F8 B326754767BC739592FCCADD32A57626 A40F6D79D453F0B3007CE22F2CB63E15 +2 1E4F3A37C68F3F4D26C066B5BA775BCD EECE72984CF629576F5E6F3FD8B79AB4 E911C2F6E2E1B298F93DACA191E87FD0 EE039E0A49B7AB7C04E3735CE3A5F701 95CE1BF7AD9DD9023C6353E03004BE79 +3 43952CF4334D196F7C5E8D91E9542D0928FBB2898AC8D70E1B030AC6D1B31961 ACAACC387C36EDBBA31688D4620D8871F72DF636AC75725C700C0299FD7C6AD4 891363716D929B4D9B8582D25496682F6177B3E4749320DA17551650315C1A49 8B323CF23C09A949BD7CD2F4EC3A1944BDEEB2F59BE8C273A52529DDC4976E9E 64BA36626C0390717BCD0F3AA1F7F3D39E38D395FB83EA33BD525D4ED888A4DC +4 987FB4C84E64BECF4238564AA0A3747421C14C984109E39B04C1AB38C6483797 CC1931B545C3DBE0EB01080573213C5B83FA6EBE07C95FC01B9396948A99E3B4 C5E0BF49B04E05B172401CB365D38A5A4B9A67FC67CE8A68879CE5AA71ACA216 5125D7801A5A4E9E63480CA91BE15EB39BCEBD475481D2521E8F175F6FC76739 87AA446EEAE8D19F35A70ADAF6052C5C2654E1957021A25DDDB57B8779EC41E0 +5 096151F483A0D2AD4716EAA98A5987F517382CD13F07130E79E2795E67192033CA4B91B00532F3CB644725D78AB6CCB3 FEAC5466C0945A2C9B891CB4059B797C4DF8E30062499E22696E365380662D6E32DEBE6058DBF4E04A0A58EC7D9BBC15 5A8E10CE1A5901DBFDB107580B3DC5DC5A8E10CE1A5901DBFDB107580B3DC5DC27404BE8450F60E9E33530EDFBC0E707 A719CB4950AD91DE74CA53C6800178F4EAD3211F878A99320EB33272219436E84AE3D2D09F7676C8EF9D9857CB170474 F38B5879376AFA624CE60710AF2E9BB5D60CC5E4AD4D8C444DB7DF2575B8E68574B2EE38A8EA4E6C8056A68A8DC57ED2 +6 0D2D5AEA32C024A39596F95371F03434 918C159C7F05A1904B48ABD8C1F11983 17D7AC312FF0F9B45573CA6D78C73DD4 41668F2EE19DFA570D917F0AA54E5331 687027B3BA1F4B85E8759A2850050567 +7 205794F50017E50B204E8D05F4A778B1 08887942418F1AE83865321CCE5279FE 3CFCF9B15AD4048F0A5B129839DF4AAF FA617BED345709B218FA7AE2F8FBB7FF 80878ED89FF3653D2CEBFF6774C44EF4 +8 C1555137956300DA812445A7F16FF59FB147EBDBCB4ED64B08F0EFACE772220A 614209F4B6F4E4B767C6FFE135BC260A2A358C4A2A487EB9244C6A312C5B4605 4EAD39A6BA40C12A61E1202ACA1C911C6B7523DBA6DC122ACBD853348F7ACA02 543706900527420D606C5D6A887975A8576BCD90B19BE44EFC166EF340EDF958 E8E505209D75181A120D25CFBE126B499651AD20785FC35CC8B1C4773F034667 +9 8337FAB9D877FF09D6EC513B550C32A0AED772736354B9F5239FDC4B0AE37FE2 2746C3E90F55553518ECE38AB0F7E519D92086B50E306A2623E9B4CDBF0DE0A5 EC94E3A71B57EFB63A044D9EFCFBBC808CCD39A69ECFDF168C532F8597C7D212 31C930B267900250B72A28B0E83A9F3F00CAB4483CEF62BB8AC626153467D8B1 BE7B261A86F002097FEC74D4863A4C5226AE99B87B7484D9CCE1350D42516D12 +10 12043B89E7FC56511064EB7AFFB3E2E39C873AD06F760D8ACB81682A6BB09D20 3C59799B921A67515712768C1A6C76F8D4B09E507961B79A37E6BC515726D1EB 1B8C182091347D3EAB8AB1A31810ED9B1E1710889EF819DE1A3FE565D3D851E5 23D6BFBB8AFC3843BEC4E8030A5A1D0AD13F7F3637CC6DB340DDFD933A090FFA 538A1FB589B7C31B1CABECA1DBAB2AD72E6F300E03FF45D723E4CF206D4E33C9 +11 68FFB4FAB966E24E4FBAB91423C050F958C98C203DEC03DF5D544001513A507B39CF0D2557D80A44012FE693ACE06501 4C053061361A6EB89C630AB42E839BDFB7AABBDC22B797FEBA7805D4642547C2CB233A542E42D76EFBA5D1E05A42796E 896112083E1780EDC798275FE632BAB4896112083E1780EDC798275FE632BAB41878C5B22CB07D912FDB146E396F75E3 0D1382C389837604251EDE08A457FE7797DDBDBBEF2BC0D7B9A1939C16EB03C6960C011F959F84B79852DDAFD07C62E6 81BF5CA41D92CC5F31CBAD86743C53465B2A63D111412BB6CDDBA2B251C061CB38DFCEDDDFE88AE40905970832ABAAE6 +12 5FAF83B886F65B6FF1C75B0011BFB046 7709CA5BCAAE8623484CB91C86DD36CD 50DF2EFE5495E38DF314CD662ABD9C7B 88737113EA251501AD1A7D723BC866C1 543B144249E064AC59D15124DEFD2066 +13 8B9DD5884E5CF66EEED7207CA4FD90AB 86A0D61C1C35E1B340D7C65B38E2217D BEAB15E9E15D89914AF7248C4334E1E5 A8645FCDAB7D653C491F8C1E422369BB 9F7CE99161F645DABEA64919FE6CB98E +14 59919BDFF40B6334B072E13550FEB2485AA7880B9FF77DF5F5716EF497F648EC 0AEC0FA2DD1D38EC16EFB6FEA18D63B3D87E368AE9F113DB8D3A9EE74F8967DF 4ED90E71455D069FEE44EC05846C1A67E0EA9142C264EDA152088F0FE4A1B2BA 7C015D7095E5FA810CFAF558B7D70A10C14861343870AF4630F49B6A439F6B6F F8787D46198A36C38DBED3CBD2906DFE1A26858995EDD60C48CA20C5DC9826EE +15 75F515C8ACBAFD70C5F814AEF11614DF5A61F3E0D4CF025A30C4A71C38C5F7BA 257EF4DD72305B67519CA7AE962F2A8E7F3831C5560A46AAA47FECDA53D74588 02824D51850D140E7898D0B60F7DF1D7611F753F04A301797DF837CAC7AEA44F 566F75CFED7BCB60A46F2B78130C4E1D16868AB838431BFB15DD3EC7B81B0E35 11DA018BBCB1CF335AAF736D02F9C0216E040F040CE056B3803BA1A1070F73A3 +16 F12432368ECC4A9A159EEFFBA4D53D13B4C1DDDDFB7380F1C8525065BDACEFE23BB485C33D0F8B3E677E7102723C59A0 9F33E6222655E964C7D9504252A602F6B94C13627D28E0D15D66784675ECA4F8A338D44032B59E37FC8AFD5C07EE0E28 996981B40459A0DE7C3328A37E2B2AEB996981B40459A0DE7C3328A37E2B2AEB2346EAE7474EABF15778DBA4C7A4B9C5 9C0B1DD9CA128A071B8E717DE5DBCE0721FDF6B82552873C1B178519C664244CCF660D1A5A2F5B533FCA7D59BC05E541 EF0F84E576CED10A723AC65AD1058EB0A4A0E4FC82F835178FBF7D371A50B9D528C33F7B4C63EC18473B9DE90E123226 +17 2D183BA857AF37A0758C08908A920D85E533A8D8C0825F2A8065248CB6F3F8F5538931D436FEF0FEB2655240A9BCAB83 8001867DE4015B976C346E06BEB229265FC82F56041C93E4000A003CBF3CD691F61DA30C99ACB401FCC530A10DDB6357 7C8B1CEAF3E6DB6413CEBA0E03AD8EE57C8B1CEAF3E6DB6413CEBA0E03AD8EE5C64BB7D3E20A28255C5F51F7CE77CA8B D0D7D8D68EB62C845ABB21FCD542C0567F53E7FC90FBC053A6A42E4FFC5F0CC19B58AAA66F08CDC23ACCB0488F5F424E D81956AC4C6A30B9E2BBF89DA6D7CA2B7629853A8BEB0C12180B42A6F01A27A0003F2EF6811CE648B1E3106041596E20 +18 A636FADA4C98A2C3D7FC84C963289B5A 1A3D7E107B310D2E8FA827DA107C3379 87BA3D049392AB119BF5636E4277FD92 CDA4356F267ED20F180067D83F53E435 8649001A86B432A083B0FBFC2F9B8486 +19 FD3E2551EFBF3975FD0B9DD59894713A 7F8B41466AE68485C2D93118ECA2BB4D EF5A1F8B515FD420B220A5C0D7F4624E 2EF3C79A58A69C0DEEB64EB439326723 21C855797305C82B631256AB6EB1DE9A +0 +1 45FAD5D1A2C829 45FAD5D1A2C829 C6F9CC5FE665DB 5324D28540487F 45FAD5D1A2C829 45FAD5D1A2C829 C6F9CC5FE665DB 5324D28540487F +2 DB42B15F9274DD94642CFD412249 D97E202AECFE902D373096696432 B057EEABB0335F5DB12C70D93435 1BDD3B60F7ACB1F907B7A2D20926 DB42B15F9274DD94642CFD412249 D97E202AECFE902D373096696432 B057EEABB0335F5DB12C70D93435 1BDD3B60F7ACB1F907B7A2D20926 +3 9AB211A903CDCAC881E93DC14B12328F2C4DB2E56E 4B3D63D9322C177239CEB9EB535814F46FC9CE61B7 506490AECA03D222A07F18FCD4853EB2744E2F880A 5E7B7124C836973C75F943A0E6E1A7F83F38E50B09 9AB211A903CDCAC881E93DC14B12328FB02B19A1EF 4B3D63D9322C177239CEB9EB535814F4EE0B185DE4 506490AECA03D222A07F18FCD4853EB2AE0C512239 5E7B7124C836973C75F943A0E6E1A7F818E0280A8B +4 5DFC33E59355A445AA9ADB29E8084EC471724574F99310623431022D 78E90B652F3820F8EF2B232B519AC6CDA5FBF2EEF71C2A9CA95A464D 744E2F880AC262F58EB52A12860027F330C02A06368C28BC1397B305 4A0079FADC1543C29D832264840BEDE81379D10872F5D9487A3207EF 5DFC33E59355A445AA9ADB29E8084EC4767C17E4742FE454E4F57823 78E90B652F3820F8EF2B232B519AC6CD14B1E64327B7FE231A32C806 744E2F880AC262F58EB52A12860027F35273673B47575806297E6BED 4A0079FADC1543C29D832264840BEDE8BD824803D21BB463B3FAAD71 +5 87A9EA999311C5AD0E474847690A410D1C3293178B21C8726CB6738BDE0713831E93F0 858EAE23E6B692D6C8D4DCD855E365CC45D13E1633FE585EE5ADD81FDE00A1BE5D0D0C 30C02A06368C28BC1397B305014AE06B35E63024A4F5F7307456FEF0DE60B9EBC656D1 59932BF7233CFFE8FF3E8B1757A9BD072CB526E85240DF1C2E01F0CE0E9C463EF0CE13 87A9EA999311C5AD0E474847690A410DEBDD76F6320B7022BF86BBE75ADB815B7DAB94 858EAE23E6B692D6C8D4DCD855E365CC4C317B64CD8C7C1A5A23BAF6F2317AB01BCBD8 30C02A06368C28BC1397B305014AE06B71672CAB6A5CD3B9BEE901B3D1E072A591767E 59932BF7233CFFE8FF3E8B1757A9BD07588F5F145A836062763B0CD4901E0D8AAEBD64 +6 9694 F452 35E6 5A7A 9694 F452 35E6 5A7A +7 250786E644BEC3019A 5D0D0C01E32A519F23 C656D1D77BC26BDAF4 5B4520BB6C7AD902A7 250786E644BEC3019A 5D0D0C01E32A519F23 C656D1D77BC26BDAF4 5B4520BB6C7AD902A7 +8 6A7B0E01C9882D51E3A3D46116E47FE8 C7C6BB9FF16A81ADB94550AAACE5C887 EC6AD7B7D03051D50F43DC97825EF513 B8896E12B037C140CBEDDB0328B65D7E 6A7B0E01C9882D51E3A3D46116E47FE8 C7C6BB9FF16A81ADB94550AAACE5C887 EC6AD7B7D03051D50F43DC97825EF513 B8896E12B037C140CBEDDB0328B65D7E +9 22C395160541151751111176A1E798CC3B3C6FC3FBA437 D93BEFBA461A78B989956D119687A53E2349701F00D80A 85651E17AC004DCB142BFB51D101CC5440EFB682C5DAEF 65EA44A7D47C8B824BEDAC73E01F8919B0C1F135CE38D9 22C395160541151751111176A1E798CCCDC01D0F1ED16A D93BEFBA461A78B989956D119687A53E839E08E2E9CDFF 85651E17AC004DCB142BFB51D101CC54680C78C2880792 65EA44A7D47C8B824BEDAC73E01F8919CD3BDFD083619A +10 7097D32DD6D892AE6BA3ECD799DD5C208D76D2AE8140A6710C308AF800DF EBC8720D27F279698679864A4F54A9B4DD53AAE4E4166F0AC6641799C254 DAAA9705F6DDD280163E07B5530DA9D9A28614F5F8041C87BB3EA80E4EF0 FEB5F3EAEA58DD1AE984F04B6F34F6C65047D9B14E042F388FADB7C80535 7097D32DD6D892AE6BA3ECD799DD5C2076320F1C6862742B2F56E6E4C665 EBC8720D27F279698679864A4F54A9B43599D05467D91E1BA6E111CFAC69 DAAA9705F6DDD280163E07B5530DA9D967E511342B1315DC7C868D7DC81F FEB5F3EAEA58DD1AE984F04B6F34F6C67B602EFCDC811519AF55F489A8BC +11 1898A5116CD6B273658CE027A4005E59D9DB2FA354A9A5938E02876717CAFE1A1AB1F0CEF4 9DD92F3A3CB44F2FFD397A7A6FBE248C9C0F325CC10CF16106CEA4AD4460A6C6945B99194D A28614F5F8041C87BB3EA80E4EF0D14201E10FCAE80517732A3F59EDC3518347E7D119B50F D54C955A132D4A2E43A78AA1C698EE8A5D8BBD16A18FC94CC534C7DDCDCFD51D6C3F5085BB 1898A5116CD6B273658CE027A4005E59DF62EB46DF96499AE2DE338319DC21577483CFC3AC 9DD92F3A3CB44F2FFD397A7A6FBE248C232F25CDD17F97A4C9E8BD4E66B66F2E715FD9E978 A28614F5F8041C87BB3EA80E4EF0D142CF1791F5C2C2DB3488DF09A82599A33563AF96E460 D54C955A132D4A2E43A78AA1C698EE8AA50782B4FE0D69C93DF849338AA112D6FBBA6DF3AF +12 878B27FF 5603D0E5 01E10FCA 080D5AF4 878B27FF 5603D0E5 01E10FCA 080D5AF4 +13 31C61775E0938979E9F588 945B99194D79751DF8E4A0 E7D119B50F6F1F857727F3 4F83A94CA41E3E29F8CA80 31C61775E0938979E9F588 945B99194D79751DF8E4A0 E7D119B50F6F1F857727F3 4F83A94CA41E3E29F8CA80 +14 9C805944AFD65E68241A45C862F0EB52AE35 83A2AE0AE036216BB6E77479BC2A38964832 2A50E384A49B4D1A0CD2FD4513B37E5A1BBD 3C06F6F5CFCFE6E6064084CF9CBEDCAE1475 9C805944AFD65E68241A45C862F0EB52FAB6 83A2AE0AE036216BB6E77479BC2A3896D9DC 2A50E384A49B4D1A0CD2FD4513B37E5ADCFE 3C06F6F5CFCFE6E6064084CF9CBEDCAEAAFF +15 DA9F1E25817B8DFE817AF9510377956D1FCA265455467676EF 71646C8051B4EA906A0E5D6B7F43800656DB32C2308A43A7B1 1BBDDC4FC97CD3F4F7A706FE1D6B26AA62081C0DE7014ADD82 A77BA33ADCC7C6A17C30430EA760377DAC4FF889812A64AD55 DA9F1E25817B8DFE817AF9510377956DD8F009861E7BD579B9 71646C8051B4EA906A0E5D6B7F438006E975BAD9A235454C42 1BBDDC4FC97CD3F4F7A706FE1D6B26AAF08860CC4D0A3E08A3 A77BA33ADCC7C6A17C30430EA760377D72BD221F4613B19316 +16 2ED4D19420A694D2165347996FA23B794B6020AC2FA725A8C56DEB8B150707F7 D940BCCF013B9D85977A29D479DF19BBFB0CCCF80355AA66CB281F721964D78A 62081C0DE7014ADD82C6852D280DAA2C9C97E9D5EB10AD5B10B2EA7539A3EF9D 33E2BD7AC2051B48266ECD699610ECCF65FCCDD57941ED19CA4674C45A1E8C0E 2ED4D19420A694D2165347996FA23B7997DBC8923C7FD5FF157FE1A54718A02D D940BCCF013B9D85977A29D479DF19BBFFD0B90212928067425A899B380ACC0D 62081C0DE7014ADD82C6852D280DAA2C255FE9CD2889C8EB89ED5F9112C82D78 33E2BD7AC2051B48266ECD699610ECCF5CA69559A8FA71DC570B8284C2A12F97 +17 788EAC539B4317378388F77B776466E285190F7F6D53C68B1AA5F9379F0498AF9C2383E5D91320 F9ED4AF81863057D17D4922753CC7D9E0A634088B8B23F19A97B1FE989949FFCFDFE6A50599F54 9C97E9D5EB10AD5B10B2EA7539A3EF9DE4DC938C999E8634F2D7CA3EA22FBD70D90898E7150F79 3C689DD24B8BFEB16BF5C5DC060F202BF5FBD9F485623DF37A1DCFFDE058D020D480B97A3DB08C 788EAC539B4317378388F77B776466E2AEE678F4BF8634710F8092E9B33BCB441231FDAE74610E F9ED4AF81863057D17D4922753CC7D9EE194657077E55C285936AEECF73DEBABC05DA084B2EF3F 9C97E9D5EB10AD5B10B2EA7539A3EF9DBB8C53A237A5271F226B9364B022E40BE92D332DA760F5 3C689DD24B8BFEB16BF5C5DC060F202B770F48E654C90D9797636F9D41F7284FE6280C472C1A49 +18 C3BA041FB92B B25FD0E73312 E4DC938C999E 2777DFCB3989 C3BA041FB92B B25FD0E73312 E4DC938C999E 2777DFCB3989 +19 E8D246399D5590775CF66D8167 FDFE6A50599F54A80E02397527 D90898E7150F7989CE17DD1282 37AA3390A80CBDDBB20D0260E0 E8D246399D5590775CF66D8167 FDFE6A50599F54A80E02397527 D90898E7150F7989CE17DD1282 37AA3390A80CBDDBB20D0260E0 +0 1 1 1 1 +1 1 1 1 1 +2 1 1 1 1 +3 1 1 1 1 +4 1 1 1 1 +5 1 1 1 1 +6 1 1 1 1 +7 1 1 1 1 +8 1 1 1 1 +9 1 1 1 1 +10 1 1 1 1 +11 1 1 1 1 +12 1 1 1 1 +13 1 1 1 1 +14 1 1 1 1 +15 1 1 1 1 +16 1 1 1 1 +17 1 1 1 1 +18 1 1 1 1 +19 1 1 1 1 +0 1 1 1 1 1 +1 1 1 1 1 1 +15 1 1 1 1 1 +16 1 1 1 1 1 +17 1 1 1 1 1 +63 1 1 1 1 1 +64 1 1 1 1 1 +65 1 1 1 1 1 +256 1 1 1 1 1 +0 0 1 1 1 1 +1 0 1 1 1 1 +2 0 1 1 1 1 +3 0 1 1 1 1 +4 0 1 1 1 1 +5 0 1 1 1 1 +6 0 1 1 1 1 +7 0 1 1 1 1 +8 0 1 1 1 1 +9 0 1 1 1 1 +10 0 1 1 1 1 +11 0 1 1 1 1 +0 0 +1 1 +2 0 +3 1 +4 0 +5 1 +6 0 +7 1 +659725796A48898D387A0479E312DFED +6E5BEFF939B1C2881C0C0E5584DCF7FA +659725796A48898D387A0479E312DFED +EF22FCCF9F7009B96E4BBEED42A90EAC6049F4F70ACD37AE63DE9645025689CB225B248DB3775820D21F7E9C492EDE6F6A9F2961F760E7DA7CF72A181455E5C958298D04554837E851FC3AFF0ED01865DC39BD297E9A0A6D100449A3D9187BCA692EF510C9AA533042A3998847DE07AA +299ABAC49709D2403395797ABBBFEA14 +659725796A48898D387A0479E312DFED diff --git a/tests/queries/0_stateless/04337_encrypt_decrypt_block_modes.sql b/tests/queries/0_stateless/04337_encrypt_decrypt_block_modes.sql new file mode 100644 index 000000000000..3602b9b14353 --- /dev/null +++ b/tests/queries/0_stateless/04337_encrypt_decrypt_block_modes.sql @@ -0,0 +1,138 @@ +-- Tags: no-fasttest +-- Tag no-fasttest: Depends on OpenSSL + +-- Known-answer and round-trip tests for the block-composed fast path of plain AES +-- ECB/CBC modes in `encrypt`/`decrypt`/`tryDecrypt`/`aes_encrypt_mysql`/`aes_decrypt_mysql` +-- (a streaming ECB context with manual PKCS#7 padding and CBC chaining XOR). +-- The reference output was produced by the generic per-row EVP implementation, +-- so any divergence of the fast path from OpenSSL semantics fails this test. +-- Input lengths cross block boundaries and the threshold at which CBC encryption +-- switches to the stitched implementation. +-- https://github.com/ClickHouse/ClickHouse/issues/65116 + +WITH + arrayJoin([0, 1, 15, 16, 17, 31, 32, 33, 63, 64, 65, 100, 256, 1000]) AS len, + repeat('x', len) AS plain, + 'key-key-key-key+' AS key16, + 'key-key-key-key+key-key+' AS key24, + 'key-key-key-key+key-key-key-key+' AS key32, + 'iviviviviviviviv' AS iv16 +SELECT + len, + hex(encrypt('aes-128-ecb', plain, key16)) AS e128ecb, + hex(encrypt('aes-256-ecb', plain, key32)) AS e256ecb, + hex(encrypt('aes-128-cbc', plain, key16)) AS e128cbc_noiv, + hex(encrypt('aes-128-cbc', plain, key16, iv16)) AS e128cbc_iv, + hex(encrypt('aes-192-cbc', plain, key24, iv16)) AS e192cbc_iv, + hex(encrypt('aes-256-cbc', plain, key32, iv16)) AS e256cbc_iv, + hex(encrypt('aes-128-cbc', plain, key16, '')) AS e128cbc_emptyiv, + hex(aes_encrypt_mysql('aes-128-ecb', plain, key16)) AS m128ecb, + hex(aes_encrypt_mysql('aes-128-cbc', plain, key16, iv16)) AS m128cbc, + hex(aes_encrypt_mysql('aes-256-cbc', plain, repeat(key32, 2), repeat(iv16, 2))) AS m256cbc_longkey +ORDER BY len; + +-- Keys and IVs varying per row and alternating keys exercise the key schedule cache +-- transitions; results must not depend on the previous row's key or IV state. +WITH + arrayJoin(range(20)) AS n, + leftPad(toString(n), 3, '0') AS n3, + concat('key-key-key-k', n3) AS vkey, + concat('iviviviviviiv', n3) AS viv, + concat('key-key-key-k', leftPad(toString(n % 2), 3, '0')) AS akey, + repeat('p', 1 + (n * 7) % 40) AS plain +SELECT + n, + hex(encrypt('aes-128-cbc', plain, vkey, viv)) AS vkey_viv, + hex(encrypt('aes-128-cbc', plain, akey, viv)) AS akey_viv, + hex(encrypt('aes-128-ecb', plain, vkey)) AS vkey_ecb, + hex(encrypt('aes-128-cbc', plain, 'key-key-key-key+', viv)) AS ckey_viv, + hex(encrypt('aes-128-cbc', plain, vkey, 'iviviviviviviviv')) AS vkey_civ +ORDER BY n; + +-- CTR and OFB are streaming modes that do not use the ECB/CBC fast path, but they +-- share the same per-row key-schedule cache: a key match resets only the IV, a key +-- change re-expands the schedule. Alternating keys force a cache miss every row, a +-- constant key with a varying IV exercises the IV-only reset, and zero-length rows +-- exercise the empty-input skip path that must not corrupt the cipher state. Output +-- must depend only on the current row's key and IV, never on the previous row's state. +WITH + arrayJoin(range(20)) AS n, + leftPad(toString(n), 3, '0') AS n3, + concat('key-key-key-k', n3) AS vkey, + concat('iviviviviviiv', n3) AS viv, + concat('key-key-key-k', leftPad(toString(n % 2), 3, '0')) AS akey, + repeat('s', (n * 7) % 40) AS plain +SELECT + n, + hex(encrypt('aes-128-ctr', plain, vkey, viv)) AS ctr_vkey_viv, + hex(encrypt('aes-128-ctr', plain, akey, viv)) AS ctr_akey_viv, + hex(encrypt('aes-128-ctr', plain, 'key-key-key-key+', viv)) AS ctr_ckey_viv, + hex(encrypt('aes-128-ctr', plain, vkey, 'iviviviviviviviv')) AS ctr_vkey_civ, + hex(encrypt('aes-128-ofb', plain, vkey, viv)) AS ofb_vkey_viv, + hex(encrypt('aes-128-ofb', plain, akey, viv)) AS ofb_akey_viv, + hex(encrypt('aes-128-ofb', plain, 'key-key-key-key+', viv)) AS ofb_ckey_viv, + hex(encrypt('aes-128-ofb', plain, vkey, 'iviviviviviviviv')) AS ofb_vkey_civ +ORDER BY n; + +-- Round trips through the same cache transitions must recover the plaintext for the +-- streaming modes regardless of how the key and IV change between rows, both with an +-- explicit IV and with no IV. +WITH + arrayJoin(range(20)) AS n, + leftPad(toString(n), 3, '0') AS n3, + concat('key-key-key-k', leftPad(toString(n % 2), 3, '0')) AS akey, + concat('iviviviviviiv', n3) AS viv, + repeat('w', (n * 11) % 50) AS plain +SELECT + n, + decrypt('aes-128-ctr', encrypt('aes-128-ctr', plain, akey, viv), akey, viv) = plain AS ctr_rt, + decrypt('aes-128-ofb', encrypt('aes-128-ofb', plain, akey, viv), akey, viv) = plain AS ofb_rt, + decrypt('aes-128-ctr', encrypt('aes-128-ctr', plain, akey), akey) = plain AS ctr_noiv_rt, + decrypt('aes-128-ofb', encrypt('aes-128-ofb', plain, akey), akey) = plain AS ofb_noiv_rt +ORDER BY n; + +-- Round trips must recover the plaintext. +WITH + arrayJoin([0, 1, 15, 16, 17, 63, 64, 65, 256]) AS len, + repeat('y', len) AS plain, + 'key-key-key-key+' AS key16, + 'key-key-key-key+key-key-key-key+' AS key32, + 'iviviviviviviviv' AS iv16 +SELECT + len, + decrypt('aes-128-ecb', encrypt('aes-128-ecb', plain, key16), key16) = plain, + decrypt('aes-128-cbc', encrypt('aes-128-cbc', plain, key16), key16) = plain, + decrypt('aes-256-cbc', encrypt('aes-256-cbc', plain, key32, iv16), key32, iv16) = plain, + decrypt('aes-128-cbc', encrypt('aes-128-cbc', plain, key16, ''), key16, '') = plain, + aes_decrypt_mysql('aes-128-cbc', aes_encrypt_mysql('aes-128-cbc', plain, repeat(key16, 3), repeat(iv16, 2)), repeat(key16, 3), repeat(iv16, 2)) = plain +ORDER BY len; + +-- Corrupted, truncated, extended, and wrong-key ciphertexts must decrypt to NULL via +-- `tryDecrypt`, and PKCS#7 padding must be validated in full. +WITH + arrayJoin(range(12)) AS n, + 'key-key-key-key+' AS key16, + encrypt('aes-128-cbc', repeat('z', n * 3), key16) AS good +SELECT + n, + tryDecrypt('aes-128-cbc', good, key16) IS NULL AS good_is_null, + tryDecrypt('aes-128-cbc', substring(good, 1, length(good) - 1), key16) IS NULL AS truncated_is_null, + tryDecrypt('aes-128-cbc', concat(good, 'q'), key16) IS NULL AS extended_is_null, + tryDecrypt('aes-128-cbc', good, 'wrong-key-wrong-') IS NULL AS wrongkey_is_null, + tryDecrypt('aes-128-ecb', unhex('00000000000000000000000000000000'), key16) IS NULL AS zeros_is_null, + tryDecrypt('aes-128-cbc', '', key16) AS empty_result +ORDER BY n; + +-- Failed rows must not corrupt the cipher state of subsequent rows. +SELECT n, isNull(tryDecrypt('aes-128-cbc', + if(n % 2 = 0, encrypt('aes-128-cbc', toString(n), 'key-key-key-key+'), 'badc'), + 'key-key-key-key+')) AS is_null +FROM (SELECT arrayJoin(range(8)) AS n) +ORDER BY n; + +-- Mixed-length rows, including empty strings between non-empty ones. +SELECT hex(encrypt('aes-128-cbc', arrayJoin(['', 'a', '', repeat('b', 100), 'c', '']), 'key-key-key-key+')) AS mixed; + +-- Decryption errors keep the OPENSSL_ERROR code. +SELECT decrypt('aes-128-cbc', 'hello there', 'key-key-key-key+'); -- {serverError OPENSSL_ERROR} not a multiple of block size +SELECT decrypt('aes-128-cbc', 'sixteen bytes ab', 'key-key-key-key+'); -- {serverError OPENSSL_ERROR} invalid padding