Merge pull request #11844 from Enmk/AES_encrypt_decrypt

encrypt and decrypt functions
This commit is contained in:
alexey-milovidov 2020-10-20 22:57:35 +03:00 committed by GitHub
commit 2b00b5391a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 17875 additions and 8 deletions

View File

@ -57,8 +57,8 @@ if (SANITIZE)
endif ()
elseif (SANITIZE STREQUAL "undefined")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SAN_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fno-sanitize=float-divide-by-zero")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SAN_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fno-sanitize=float-divide-by-zero")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SAN_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fno-sanitize=float-divide-by-zero -fsanitize-blacklist=${CMAKE_SOURCE_DIR}/tests/ubsan_suppressions.txt")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SAN_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fno-sanitize=float-divide-by-zero -fsanitize-blacklist=${CMAKE_SOURCE_DIR}/tests/ubsan_suppressions.txt")
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
endif()

View File

@ -219,6 +219,8 @@ TESTS_TO_SKIP=(
01268_dictionary_direct_layout
01280_ssd_complex_key_dictionary
01281_group_by_limit_memory_tracking # max_memory_usage_for_user can interfere another queries running concurrently
01318_encrypt # Depends on OpenSSL
01318_decrypt # Depends on OpenSSL
01281_unsucceeded_insert_select_queries_counter
01292_create_user
01294_lazy_database_concurrent

View File

@ -722,18 +722,22 @@
-->
<format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
<!-- Uncomment to use query masking rules.
<!-- Default query masking rules, matching lines would be replaced with something else in the logs
(both text logs and system.query_log).
name - name for the rule (optional)
regexp - RE2 compatible regular expression (mandatory)
replace - substitution string for sensitive data (optional, by default - six asterisks)
-->
<query_masking_rules>
<rule>
<name>hide SSN</name>
<regexp>\b\d{3}-\d{2}-\d{4}\b</regexp>
<replace>000-00-0000</replace>
<name>hide encrypt/decrypt arguments</name>
<regexp>((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\)</regexp>
<!-- or more secure, but also more invasive:
(aes_\w+)\s*\(.*\)
-->
<replace>\1(???)</replace>
</rule>
</query_masking_rules>
-->
<!-- Uncomment to use custom http handlers.
rules are checked from top to bottom, first match runs the handler

View File

@ -634,4 +634,10 @@ void ColumnString::protect()
getOffsets().protect();
}
void ColumnString::validate() const
{
if (!offsets.empty() && offsets.back() != chars.size())
throw Exception(ErrorCodes::LOGICAL_ERROR, "ColumnString validation failed: size mismatch (internal logical error) {} != {}", offsets.back(), chars.size());
}
}

View File

@ -267,6 +267,9 @@ public:
Offsets & getOffsets() { return offsets; }
const Offsets & getOffsets() const { return offsets; }
// Throws an exception if offsets/chars are messed up
void validate() const;
};

View File

@ -137,7 +137,7 @@ void validateArgumentsImpl(const IFunction & func,
const auto & arg = arguments[i + argument_offset];
const auto descriptor = descriptors[i];
if (int error_code = descriptor.isValid(arg.type, arg.column); error_code != 0)
throw Exception("Illegal type of argument #" + std::to_string(i)
throw Exception("Illegal type of argument #" + std::to_string(argument_offset + i + 1) // +1 is for human-friendly 1-based indexing
+ (descriptor.argument_name ? " '" + std::string(descriptor.argument_name) + "'" : String{})
+ " of function " + func.getName()
+ (descriptor.expected_type_description ? String(", expected ") + descriptor.expected_type_description : String{})

View File

@ -0,0 +1,63 @@
#include <Functions/FunctionsAES.h>
#if USE_SSL
#include <openssl/evp.h>
#include <openssl/err.h>
#include <string>
#include <cassert>
namespace DB
{
namespace ErrorCodes
{
extern const int OPENSSL_ERROR;
}
}
namespace OpenSSLDetails
{
void onError(std::string error_message)
{
error_message += ". OpenSSL error code: " + std::to_string(ERR_get_error());
throw DB::Exception(error_message, DB::ErrorCodes::OPENSSL_ERROR);
}
StringRef foldEncryptionKeyInMySQLCompatitableMode(size_t cipher_key_size, const StringRef & key, std::array<char, EVP_MAX_KEY_LENGTH> & folded_key)
{
assert(cipher_key_size <= EVP_MAX_KEY_LENGTH);
memcpy(folded_key.data(), key.data, cipher_key_size);
for (size_t i = cipher_key_size; i < key.size; ++i)
{
folded_key[i % cipher_key_size] ^= key.data[i];
}
return StringRef(folded_key.data(), cipher_key_size);
}
const EVP_CIPHER * getCipherByName(const StringRef & cipher_name)
{
const auto * evp_cipher = EVP_get_cipherbyname(cipher_name.data);
if (evp_cipher == nullptr)
{
// For some reasons following ciphers can't be found by name.
if (cipher_name == "aes-128-cfb128")
evp_cipher = EVP_aes_128_cfb128();
else if (cipher_name == "aes-192-cfb128")
evp_cipher = EVP_aes_192_cfb128();
else if (cipher_name == "aes-256-cfb128")
evp_cipher = EVP_aes_256_cfb128();
}
// 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.
return evp_cipher;
}
}
#endif

View File

@ -0,0 +1,693 @@
#pragma once
#if !defined(ARCADIA_BUILD)
# include <Common/config.h>
#endif
#if USE_SSL
#include <DataTypes/DataTypeString.h>
#include <Columns/ColumnString.h>
#include <Functions/IFunction.h>
#include <Functions/FunctionFactory.h>
#include <Functions/FunctionHelpers.h>
#include <fmt/format.h>
#include <openssl/evp.h>
#include <openssl/engine.h>
#include <string_view>
#include <functional>
#include <initializer_list>
#include <string.h>
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
}
}
namespace OpenSSLDetails
{
[[noreturn]] void onError(std::string error_message);
StringRef foldEncryptionKeyInMySQLCompatitableMode(size_t cipher_key_size, const StringRef & key, std::array<char, EVP_MAX_KEY_LENGTH> & folded_key);
const EVP_CIPHER * getCipherByName(const StringRef & name);
enum class CompatibilityMode
{
MySQL,
OpenSSL
};
enum class CipherMode
{
MySQLCompatibility, // with key folding
OpenSSLCompatibility, // just as regular openssl's enc application does (AEAD modes, like GCM and CCM are not supported)
RFC5116_AEAD_AES_GCM // AEAD GCM with custom IV length and tag (HMAC) appended to the ciphertext, see https://tools.ietf.org/html/rfc5116#section-5.1
};
template <CipherMode mode>
struct KeyHolder
{
inline StringRef setKey(size_t cipher_key_size, const StringRef & key) const
{
if (key.size != cipher_key_size)
throw DB::Exception(fmt::format("Invalid key size: {} expected {}", key.size, cipher_key_size),
DB::ErrorCodes::BAD_ARGUMENTS);
return key;
}
};
template <>
struct KeyHolder<CipherMode::MySQLCompatibility>
{
inline StringRef setKey(size_t cipher_key_size, const StringRef & key)
{
if (key.size < cipher_key_size)
throw DB::Exception(fmt::format("Invalid key size: {} expected {}", key.size, cipher_key_size),
DB::ErrorCodes::BAD_ARGUMENTS);
// MySQL does something fancy with the keys that are too long,
// ruining compatibility with OpenSSL and not improving security.
// But we have to do the same to be compatitable with MySQL.
// see https://github.com/mysql/mysql-server/blob/8.0/router/src/harness/src/my_aes_openssl.cc#L71
// (my_aes_create_key function)
return foldEncryptionKeyInMySQLCompatitableMode(cipher_key_size, key, folded_key);
}
~KeyHolder()
{
OPENSSL_cleanse(folded_key.data(), folded_key.size());
}
private:
std::array<char, EVP_MAX_KEY_LENGTH> folded_key;
};
template <CompatibilityMode compatibility_mode>
inline void validateCipherMode(const EVP_CIPHER * evp_cipher)
{
if constexpr (compatibility_mode == CompatibilityMode::MySQL)
{
switch (EVP_CIPHER_mode(evp_cipher))
{
case EVP_CIPH_ECB_MODE: [[fallthrough]];
case EVP_CIPH_CBC_MODE: [[fallthrough]];
case EVP_CIPH_CFB_MODE: [[fallthrough]];
case EVP_CIPH_OFB_MODE:
return;
}
}
else if constexpr (compatibility_mode == CompatibilityMode::OpenSSL)
{
switch (EVP_CIPHER_mode(evp_cipher))
{
case EVP_CIPH_ECB_MODE: [[fallthrough]];
case EVP_CIPH_CBC_MODE: [[fallthrough]];
case EVP_CIPH_CFB_MODE: [[fallthrough]];
case EVP_CIPH_OFB_MODE: [[fallthrough]];
case EVP_CIPH_CTR_MODE: [[fallthrough]];
case EVP_CIPH_GCM_MODE:
return;
}
}
throw DB::Exception("Unsupported cipher mode " + std::string(EVP_CIPHER_name(evp_cipher)), DB::ErrorCodes::BAD_ARGUMENTS);
}
template <CipherMode mode>
inline void validateIV(const StringRef & iv_value, const size_t cipher_iv_size)
{
// In MySQL mode we don't care if IV is longer than expected, only if shorter.
if ((mode == CipherMode::MySQLCompatibility && iv_value.size != 0 && iv_value.size < cipher_iv_size)
|| (mode == CipherMode::OpenSSLCompatibility && iv_value.size != 0 && iv_value.size != cipher_iv_size))
throw DB::Exception(fmt::format("Invalid IV size: {} expected {}", iv_value.size, cipher_iv_size),
DB::ErrorCodes::BAD_ARGUMENTS);
}
}
namespace DB
{
template <typename Impl>
class FunctionEncrypt : public IFunction
{
public:
static constexpr OpenSSLDetails::CompatibilityMode compatibility_mode = Impl::compatibility_mode;
static constexpr auto name = Impl::name;
static FunctionPtr create(const Context &) { return std::make_shared<FunctionEncrypt>(); }
private:
using CipherMode = OpenSSLDetails::CipherMode;
String getName() const override { return name; }
bool isVariadic() const override { return true; }
size_t getNumberOfArguments() const override { return 0; }
ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {0}; }
bool useDefaultImplementationForConstants() const override { return true; }
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
{
auto optional_args = FunctionArgumentDescriptors{
{"IV", isStringOrFixedString, nullptr, "Initialization vector binary string"},
};
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::OpenSSL)
{
optional_args.emplace_back(FunctionArgumentDescriptor{
"AAD", isStringOrFixedString, nullptr, "Additional authenticated data binary string for GCM mode"
});
}
validateFunctionArgumentTypes(*this, arguments,
FunctionArgumentDescriptors{
{"mode", isStringOrFixedString, isColumnConst, "encryption mode string"},
{"input", nullptr, nullptr, "plaintext"},
{"key", isStringOrFixedString, nullptr, "encryption key binary string"},
},
optional_args
);
return std::make_shared<DataTypeString>();
}
void executeImpl(DB::ColumnsWithTypeAndName & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) const override
{
using namespace OpenSSLDetails;
const auto mode = block[arguments[0]].column->getDataAt(0);
if (mode.size == 0 || !std::string_view(mode).starts_with("aes-"))
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
auto evp_cipher = getCipherByName(mode);
if (evp_cipher == nullptr)
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
const auto cipher_mode = EVP_CIPHER_mode(evp_cipher);
const auto input_column = block[arguments[1]].column;
const auto key_column = block[arguments[2]].column;
OpenSSLDetails::validateCipherMode<compatibility_mode>(evp_cipher);
ColumnPtr result_column;
if (arguments.size() <= 3)
result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, nullptr, nullptr);
else
{
const auto iv_column = block[arguments[3]].column;
if (compatibility_mode != OpenSSLDetails::CompatibilityMode::MySQL && EVP_CIPHER_iv_length(evp_cipher) == 0)
throw Exception(mode.toString() + " does not support IV", ErrorCodes::BAD_ARGUMENTS);
if (arguments.size() <= 4)
{
result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, nullptr);
}
else
{
if (cipher_mode != EVP_CIPH_GCM_MODE)
throw Exception("AAD can be only set for GCM-mode", ErrorCodes::BAD_ARGUMENTS);
const auto aad_column = block[arguments[4]].column;
result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
}
block[result].column = std::move(result_column);
}
template <typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
static ColumnPtr doEncrypt(const EVP_CIPHER * evp_cipher,
size_t input_rows_count,
const InputColumnType & input_column,
const KeyColumnType & key_column,
const IvColumnType & iv_column,
const AadColumnType & aad_column)
{
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::MySQL)
{
return doEncryptImpl<CipherMode::MySQLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
else
{
if (EVP_CIPHER_mode(evp_cipher) == EVP_CIPH_GCM_MODE)
{
return doEncryptImpl<CipherMode::RFC5116_AEAD_AES_GCM>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
else
{
return doEncryptImpl<CipherMode::OpenSSLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
}
return nullptr;
}
template <CipherMode mode, typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
static ColumnPtr doEncryptImpl(const EVP_CIPHER * evp_cipher,
size_t input_rows_count,
const InputColumnType & input_column,
const KeyColumnType & key_column,
[[maybe_unused]] const IvColumnType & iv_column,
[[maybe_unused]] const AadColumnType & aad_column)
{
using namespace OpenSSLDetails;
auto evp_ctx_ptr = std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free);
auto evp_ctx = evp_ctx_ptr.get();
const auto block_size = static_cast<size_t>(EVP_CIPHER_block_size(evp_cipher));
const auto key_size = static_cast<size_t>(EVP_CIPHER_key_length(evp_cipher));
[[maybe_unused]] const auto iv_size = static_cast<size_t>(EVP_CIPHER_iv_length(evp_cipher));
const auto tag_size = 16; // https://tools.ietf.org/html/rfc5116#section-5.1
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 modes with block_size > 1, plaintext is padded up to a block_size,
// which may result in allocating to much for block_size = 1.
// That may lead later to reading unallocated data from underlying PaddedPODArray
// due to assumption that it is safe to read up to 15 bytes past end.
const auto pad_to_next_block = block_size == 1 ? 0 : 1;
for (size_t r = 0; r < input_rows_count; ++r)
{
resulting_size += (input_column->getDataAt(r).size / block_size + pad_to_next_block) * block_size + 1;
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
resulting_size += tag_size;
}
#if defined(MEMORY_SANITIZER)
encrypted_result_column_data.resize_fill(resulting_size, 0xFF);
#else
encrypted_result_column_data.resize(resulting_size);
#endif
}
auto encrypted = encrypted_result_column_data.data();
KeyHolder<mode> key_holder;
for (size_t r = 0; r < input_rows_count; ++r)
{
const auto key_value = key_holder.setKey(key_size, key_column->getDataAt(r));
auto iv_value = StringRef{};
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<IvColumnType>>)
{
iv_value = iv_column->getDataAt(r);
}
const auto input_value = input_column->getDataAt(r);
auto aad_value = StringRef{};
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM && !std::is_same_v<nullptr_t, std::decay_t<AadColumnType>>)
{
aad_value = aad_column->getDataAt(r);
}
if constexpr (mode != CipherMode::MySQLCompatibility)
{
// in GCM mode IV can be of arbitrary size (>0), IV is optional for other modes.
if (mode == CipherMode::RFC5116_AEAD_AES_GCM && iv_value.size == 0)
{
throw Exception("Invalid IV size " + std::to_string(iv_value.size) + " != expected size " + std::to_string(iv_size),
DB::ErrorCodes::BAD_ARGUMENTS);
}
if (mode != CipherMode::RFC5116_AEAD_AES_GCM && key_value.size != key_size)
{
throw Exception("Invalid key size " + std::to_string(key_value.size) + " != expected size " + std::to_string(key_size),
DB::ErrorCodes::BAD_ARGUMENTS);
}
}
// Avoid extra work on empty ciphertext/plaintext for some ciphers
if (!(input_value.size == 0 && block_size == 1 && mode != CipherMode::RFC5116_AEAD_AES_GCM))
{
// 1: Init CTX
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
// 1.a.1: Init CTX with custom IV length and optionally with AAD
if (EVP_EncryptInit_ex(evp_ctx, evp_cipher, nullptr, nullptr, nullptr) != 1)
onError("Failed to initialize encryption context with cipher");
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_SET_IVLEN, iv_value.size, nullptr) != 1)
onError("Failed to set custom IV length to " + std::to_string(iv_value.size));
if (EVP_EncryptInit_ex(evp_ctx, nullptr, nullptr,
reinterpret_cast<const unsigned char*>(key_value.data),
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
onError("Failed to set key and IV");
// 1.a.2 Set AAD
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<AadColumnType>>)
{
const auto aad_data = aad_column->getDataAt(r);
int tmp_len = 0;
if (aad_data.size != 0 && EVP_EncryptUpdate(evp_ctx, nullptr, &tmp_len,
reinterpret_cast<const unsigned char *>(aad_data.data), aad_data.size) != 1)
onError("Failed to set AAD data");
}
}
else
{
// 1.b: Init CTX
validateIV<mode>(iv_value, iv_size);
if (EVP_EncryptInit_ex(evp_ctx, evp_cipher, nullptr,
reinterpret_cast<const unsigned char*>(key_value.data),
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
onError("Failed to initialize cipher context");
}
int output_len = 0;
// 2: Feed the data to the cipher
if (EVP_EncryptUpdate(evp_ctx,
reinterpret_cast<unsigned char*>(encrypted), &output_len,
reinterpret_cast<const unsigned char*>(input_value.data), static_cast<int>(input_value.size)) != 1)
onError("Failed to encrypt");
encrypted += output_len;
// 3: retrieve encrypted data (ciphertext)
if (EVP_EncryptFinal_ex(evp_ctx,
reinterpret_cast<unsigned char*>(encrypted), &output_len) != 1)
onError("Failed to fetch ciphertext");
encrypted += output_len;
// 4: optionally retrieve a tag and append it to the ciphertext (RFC5116):
// https://tools.ietf.org/html/rfc5116#section-5.1
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_GET_TAG, tag_size, encrypted) != 1)
onError("Failed to retrieve GCM tag");
encrypted += tag_size;
}
}
*encrypted = '\0';
++encrypted;
encrypted_result_column_offsets.push_back(encrypted - encrypted_result_column_data.data());
}
// in case of block size of 1, we overestimate buffer required for encrypted data, fix it up.
if (!encrypted_result_column_offsets.empty() && encrypted_result_column_data.size() > encrypted_result_column_offsets.back())
{
encrypted_result_column_data.resize(encrypted_result_column_offsets.back());
}
encrypted_result_column->validate();
return encrypted_result_column;
}
};
/// AES_decrypt(string, key, block_mode[, init_vector])
template <typename Impl>
class FunctionDecrypt : public IFunction
{
public:
static constexpr OpenSSLDetails::CompatibilityMode compatibility_mode = Impl::compatibility_mode;
static constexpr auto name = Impl::name;
static FunctionPtr create(const Context &) { return std::make_shared<FunctionDecrypt>(); }
private:
using CipherMode = OpenSSLDetails::CipherMode;
String getName() const override { return name; }
bool isVariadic() const override { return true; }
size_t getNumberOfArguments() const override { return 0; }
ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {0}; }
bool useDefaultImplementationForConstants() const override { return true; }
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
{
auto optional_args = FunctionArgumentDescriptors{
{"IV", isStringOrFixedString, nullptr, "Initialization vector binary string"},
};
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::OpenSSL)
{
optional_args.emplace_back(FunctionArgumentDescriptor{
"AAD", isStringOrFixedString, nullptr, "Additional authenticated data binary string for GCM mode"
});
}
validateFunctionArgumentTypes(*this, arguments,
FunctionArgumentDescriptors{
{"mode", isStringOrFixedString, isColumnConst, "decryption mode string"},
{"input", nullptr, nullptr, "ciphertext"},
{"key", isStringOrFixedString, nullptr, "decryption key binary string"},
},
optional_args
);
return std::make_shared<DataTypeString>();
}
void executeImpl(DB::ColumnsWithTypeAndName & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) const override
{
using namespace OpenSSLDetails;
const auto mode = block[arguments[0]].column->getDataAt(0);
if (mode.size == 0 || !std::string_view(mode).starts_with("aes-"))
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
auto evp_cipher = getCipherByName(mode);
if (evp_cipher == nullptr)
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
OpenSSLDetails::validateCipherMode<compatibility_mode>(evp_cipher);
const auto input_column = block[arguments[1]].column;
const auto key_column = block[arguments[2]].column;
ColumnPtr result_column;
if (arguments.size() <= 3)
result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, nullptr, nullptr);
else
{
const auto iv_column = block[arguments[3]].column;
if (compatibility_mode != OpenSSLDetails::CompatibilityMode::MySQL && EVP_CIPHER_iv_length(evp_cipher) == 0)
throw Exception(mode.toString() + " does not support IV", ErrorCodes::BAD_ARGUMENTS);
if (arguments.size() <= 4)
{
result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, nullptr);
}
else
{
if (EVP_CIPHER_mode(evp_cipher) != EVP_CIPH_GCM_MODE)
throw Exception("AAD can be only set for GCM-mode", ErrorCodes::BAD_ARGUMENTS);
const auto aad_column = block[arguments[4]].column;
result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
}
block[result].column = std::move(result_column);
}
template <typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
static ColumnPtr doDecrypt(const EVP_CIPHER * evp_cipher,
size_t input_rows_count,
const InputColumnType & input_column,
const KeyColumnType & key_column,
const IvColumnType & iv_column,
const AadColumnType & aad_column)
{
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::MySQL)
{
return doDecryptImpl<CipherMode::MySQLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
else
{
const auto cipher_mode = EVP_CIPHER_mode(evp_cipher);
if (cipher_mode == EVP_CIPH_GCM_MODE)
{
return doDecryptImpl<CipherMode::RFC5116_AEAD_AES_GCM>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
else
{
return doDecryptImpl<CipherMode::OpenSSLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
}
return nullptr;
}
template <CipherMode mode, typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
static ColumnPtr doDecryptImpl(const EVP_CIPHER * evp_cipher,
size_t input_rows_count,
const InputColumnType & input_column,
const KeyColumnType & key_column,
[[maybe_unused]] const IvColumnType & iv_column,
[[maybe_unused]] const AadColumnType & aad_column)
{
using namespace OpenSSLDetails;
auto evp_ctx_ptr = std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free);
auto evp_ctx = evp_ctx_ptr.get();
[[maybe_unused]] const auto block_size = static_cast<size_t>(EVP_CIPHER_block_size(evp_cipher));
[[maybe_unused]] const auto iv_size = static_cast<size_t>(EVP_CIPHER_iv_length(evp_cipher));
const auto key_size = static_cast<size_t>(EVP_CIPHER_key_length(evp_cipher));
const auto tag_size = 16; // https://tools.ietf.org/html/rfc5116#section-5.1
auto decrypted_result_column = ColumnString::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 r = 0; r < input_rows_count; ++r)
{
resulting_size += input_column->getDataAt(r).size + 1;
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
resulting_size -= tag_size;
}
#if defined(MEMORY_SANITIZER)
// Pre-fill result column with values to prevent MSAN from dropping dead on
// aes-X-ecb mode with "WARNING: MemorySanitizer: use-of-uninitialized-value".
// This is most likely to be caused by the underlying assembler implementation:
// see crypto/aes/aesni-x86_64.s, function aesni_ecb_encrypt
// which msan seems to fail instrument correctly.
decrypted_result_column_data.resize_fill(resulting_size, 0xFF);
#else
decrypted_result_column_data.resize(resulting_size);
#endif
}
auto decrypted = decrypted_result_column_data.data();
KeyHolder<mode> key_holder;
for (size_t r = 0; r < input_rows_count; ++r)
{
// 0: prepare key if required
auto key_value = key_holder.setKey(key_size, key_column->getDataAt(r));
auto iv_value = StringRef{};
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<IvColumnType>>)
{
iv_value = iv_column->getDataAt(r);
}
auto input_value = input_column->getDataAt(r);
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
// empty plaintext results in empty ciphertext + tag, means there should be atleast tag_size bytes.
if (input_value.size < tag_size)
throw Exception(fmt::format("Encrypted data is too short: only {} bytes, "
"should contain at least {} bytes of a tag.",
input_value.size, block_size, tag_size), ErrorCodes::BAD_ARGUMENTS);
input_value.size -= tag_size;
}
if constexpr (mode != CipherMode::MySQLCompatibility)
{
// in GCM mode IV can be of arbitrary size (>0), for other modes IV is optional.
if (mode == CipherMode::RFC5116_AEAD_AES_GCM && iv_value.size == 0)
{
throw Exception("Invalid IV size " + std::to_string(iv_value.size) + " != expected size " + std::to_string(iv_size),
DB::ErrorCodes::BAD_ARGUMENTS);
}
if (key_value.size != key_size)
{
throw Exception("Invalid key size " + std::to_string(key_value.size) + " != expected size " + std::to_string(key_size),
DB::ErrorCodes::BAD_ARGUMENTS);
}
}
// Avoid extra work on empty ciphertext/plaintext for some ciphers
if (!(input_value.size == 0 && block_size == 1 && mode != CipherMode::RFC5116_AEAD_AES_GCM))
{
// 1: Init CTX
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
if (EVP_DecryptInit_ex(evp_ctx, evp_cipher, nullptr, nullptr, nullptr) != 1)
onError("Failed to initialize cipher context 1");
// 1.a.1 : Set custom IV length
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_SET_IVLEN, iv_value.size, nullptr) != 1)
onError("Failed to set custom IV length to " + std::to_string(iv_value.size));
// 1.a.1 : Init CTX with key and IV
if (EVP_DecryptInit_ex(evp_ctx, nullptr, nullptr,
reinterpret_cast<const unsigned char*>(key_value.data),
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
onError("Failed to set key and IV");
// 1.a.2: Set AAD if present
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<AadColumnType>>)
{
const auto aad_data = aad_column->getDataAt(r);
int tmp_len = 0;
if (aad_data.size != 0 && EVP_DecryptUpdate(evp_ctx, nullptr, &tmp_len,
reinterpret_cast<const unsigned char *>(aad_data.data), aad_data.size) != 1)
onError("Failed to sed AAD data");
}
}
else
{
// 1.b: Init CTX
validateIV<mode>(iv_value, iv_size);
if (EVP_DecryptInit_ex(evp_ctx, evp_cipher, nullptr,
reinterpret_cast<const unsigned char*>(key_value.data),
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
onError("Failed to initialize cipher context");
}
// 2: Feed the data to the cipher
int output_len = 0;
if (EVP_DecryptUpdate(evp_ctx,
reinterpret_cast<unsigned char*>(decrypted), &output_len,
reinterpret_cast<const unsigned char*>(input_value.data), static_cast<int>(input_value.size)) != 1)
onError("Failed to decrypt");
decrypted += output_len;
// 3: optionally get tag from the ciphertext (RFC5116) and feed it to the context
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
void * tag = const_cast<void *>(reinterpret_cast<const void *>(input_value.data + input_value.size));
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_SET_TAG, tag_size, tag) != 1)
onError("Failed to set tag");
}
// 4: retrieve encrypted data (ciphertext)
if (EVP_DecryptFinal_ex(evp_ctx,
reinterpret_cast<unsigned char*>(decrypted), &output_len) != 1)
onError("Failed to decrypt");
decrypted += output_len;
}
*decrypted = '\0';
++decrypted;
decrypted_result_column_offsets.push_back(decrypted - decrypted_result_column_data.data());
}
// in case we overestimate buffer required for decrypted data, fix it up.
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();
return decrypted_result_column;
}
};
}
#endif

View File

@ -0,0 +1,31 @@
#if !defined(ARCADIA_BUILD)
# include <Common/config.h>
#endif
#if USE_SSL
#include <Functions/FunctionFactory.h>
#include <Functions/FunctionsAES.h>
namespace
{
struct DecryptMySQLModeImpl
{
static constexpr auto name = "aes_decrypt_mysql";
static constexpr auto compatibility_mode = OpenSSLDetails::CompatibilityMode::MySQL;
};
}
namespace DB
{
void registerFunctionAESDecryptMysql(FunctionFactory & factory)
{
factory.registerFunction<FunctionDecrypt<DecryptMySQLModeImpl>>();
}
}
#endif

View File

@ -0,0 +1,31 @@
#if !defined(ARCADIA_BUILD)
# include <Common/config.h>
#endif
#if USE_SSL
#include <Functions/FunctionFactory.h>
#include <Functions/FunctionsAES.h>
namespace
{
struct EncryptMySQLModeImpl
{
static constexpr auto name = "aes_encrypt_mysql";
static constexpr auto compatibility_mode = OpenSSLDetails::CompatibilityMode::MySQL;
};
}
namespace DB
{
void registerFunctionAESEncryptMysql(FunctionFactory & factory)
{
factory.registerFunction<FunctionEncrypt<EncryptMySQLModeImpl>>();
}
}
#endif

31
src/Functions/decrypt.cpp Normal file
View File

@ -0,0 +1,31 @@
#if !defined(ARCADIA_BUILD)
# include <Common/config.h>
#endif
#if USE_SSL
#include <Functions/FunctionFactory.h>
#include <Functions/FunctionsAES.h>
namespace
{
struct DecryptImpl
{
static constexpr auto name = "decrypt";
static constexpr auto compatibility_mode = OpenSSLDetails::CompatibilityMode::OpenSSL;
};
}
namespace DB
{
void registerFunctionDecrypt(FunctionFactory & factory)
{
factory.registerFunction<FunctionDecrypt<DecryptImpl>>();
}
}
#endif

31
src/Functions/encrypt.cpp Normal file
View File

@ -0,0 +1,31 @@
#if !defined(ARCADIA_BUILD)
# include <Common/config.h>
#endif
#if USE_SSL
#include <Functions/FunctionFactory.h>
#include <Functions/FunctionsAES.h>
namespace
{
struct EncryptImpl
{
static constexpr auto name = "encrypt";
static constexpr auto compatibility_mode = OpenSSLDetails::CompatibilityMode::OpenSSL;
};
}
namespace DB
{
void registerFunctionEncrypt(FunctionFactory & factory)
{
factory.registerFunction<FunctionEncrypt<EncryptImpl>>();
}
}
#endif

View File

@ -1,3 +1,7 @@
#if !defined(ARCADIA_BUILD)
# include <Common/config.h>
#endif
#include <Functions/FunctionFactory.h>
@ -37,12 +41,21 @@ void registerFunctionsNull(FunctionFactory &);
void registerFunctionsJSON(FunctionFactory &);
void registerFunctionsConsistentHashing(FunctionFactory & factory);
void registerFunctionsUnixTimestamp64(FunctionFactory & factory);
#if !defined(ARCADIA_BUILD)
void registerFunctionBayesAB(FunctionFactory &);
#endif
void registerFunctionTid(FunctionFactory & factory);
void registerFunctionLogTrace(FunctionFactory & factory);
#if USE_SSL
void registerFunctionEncrypt(FunctionFactory & factory);
void registerFunctionDecrypt(FunctionFactory & factory);
void registerFunctionAESEncryptMysql(FunctionFactory & factory);
void registerFunctionAESDecryptMysql(FunctionFactory & factory);
#endif
void registerFunctions()
{
@ -84,9 +97,17 @@ void registerFunctions()
registerFunctionsIntrospection(factory);
registerFunctionsConsistentHashing(factory);
registerFunctionsUnixTimestamp64(factory);
#if !defined(ARCADIA_BUILD)
registerFunctionBayesAB(factory);
#endif
#if USE_SSL
registerFunctionEncrypt(factory);
registerFunctionDecrypt(factory);
registerFunctionAESEncryptMysql(factory);
registerFunctionAESDecryptMysql(factory);
#endif
registerFunctionTid(factory);
registerFunctionLogTrace(factory);
}

View File

@ -47,6 +47,8 @@ SRCS(
addSeconds.cpp
addWeeks.cpp
addYears.cpp
aes_decrypt_mysql.cpp
aes_encrypt_mysql.cpp
appendTrailingCharIfAbsent.cpp
array/arrayAll.cpp
array/arrayAUC.cpp
@ -141,6 +143,7 @@ SRCS(
currentUser.cpp
dateDiff.cpp
date_trunc.cpp
decrypt.cpp
defaultValueOfArgumentType.cpp
defaultValueOfTypeName.cpp
demange.cpp
@ -148,6 +151,7 @@ SRCS(
dumpColumnStructure.cpp
e.cpp
empty.cpp
encrypt.cpp
endsWith.cpp
equals.cpp
erfc.cpp
@ -176,6 +180,7 @@ SRCS(
FunctionFQDN.cpp
FunctionHelpers.cpp
FunctionJoinGet.cpp
FunctionsAES.cpp
FunctionsCoding.cpp
FunctionsConversion.cpp
FunctionsEmbeddedDictionaries.cpp

View File

@ -0,0 +1,84 @@
<test>
<!-- "Strict" mode (key and iv length checks), non-empty plaintext.
See also other encrypt_decrypt_*.xml for more cases. -->
<substitutions>
<substitution>
<name>func</name>
<values>
<!-- materialize(plaitext) is to avoid all-args-are-const optimization, resulting in executing function exactly once. -->
<value>encrypt('aes-128-cbc', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-cfb1', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-cfb8', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-cfb128', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-ctr', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-ecb', materialize(plaintext), key16)</value>
<value>encrypt('aes-128-ofb', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-gcm', materialize(plaintext), key16, iv12, 'aadaadaadaad')</value>
<value>encrypt('aes-192-cbc', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-cfb1', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-cfb8', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-cfb128', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-ctr', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-ecb', materialize(plaintext), key24)</value>
<value>encrypt('aes-192-ofb', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-gcm', materialize(plaintext), key24, iv12, 'aadaadaadaad')</value>
<value>encrypt('aes-256-cbc', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-cfb1', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-cfb8', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-cfb128', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-ctr', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-ecb', materialize(plaintext), key32)</value>
<value>encrypt('aes-256-ofb', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-gcm', materialize(plaintext), key32, iv12, 'aadaadaadaad')</value>
<!-- decrypt + encrypt since it is really hard to compose decrypt-only case -->
<value>decrypt('aes-128-cbc', encrypt('aes-128-cbc', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-cfb8', encrypt('aes-128-cfb8', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-cfb128', encrypt('aes-128-cfb128', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-ctr', encrypt('aes-128-ctr', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-ecb', encrypt('aes-128-ecb', materialize(plaintext), key16), key16)</value>
<value>decrypt('aes-128-ofb', encrypt('aes-128-ofb', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-gcm', encrypt('aes-128-gcm', materialize(plaintext), key16, iv12, 'aadaadaadaad'), key16, iv12, 'aadaadaadaad')</value>
<value>decrypt('aes-192-cbc', encrypt('aes-192-cbc', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-cfb8', encrypt('aes-192-cfb8', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-cfb128', encrypt('aes-192-cfb128', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-ctr', encrypt('aes-192-ctr', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-ecb', encrypt('aes-192-ecb', materialize(plaintext), key24), key24)</value>
<value>decrypt('aes-192-ofb', encrypt('aes-192-ofb', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-gcm', encrypt('aes-192-gcm', materialize(plaintext), key24, iv12, 'aadaadaadaad'), key24, iv12, 'aadaadaadaad')</value>
<value>decrypt('aes-256-cbc', encrypt('aes-256-cbc', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-cfb8', encrypt('aes-256-cfb8', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-cfb128', encrypt('aes-256-cfb128', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-ctr', encrypt('aes-256-ctr', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-ecb', encrypt('aes-256-ecb', materialize(plaintext), key32), key32)</value>
<value>decrypt('aes-256-ofb', encrypt('aes-256-ofb', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-gcm', encrypt('aes-256-gcm', materialize(plaintext), key32, iv12, 'aadaadaadaad'), key32, iv12, 'aadaadaadaad')</value>
</values>
</substitution>
<substitution>
<name>table</name>
<values>
<value>numbers(100000)</value>
</values>
</substitution>
<substitution>
<name>plaintext</name>
<values>
<value>number</value>
<value>'paintext'</value>
<value>'\x12\x2B\xF9\x16\x93\xA4\xD6\x74\x22\xD9\x17\x5E\x38\xCD\x1D\x7B\xB0\x12\xEC\x43\x6B\xC7\x76\xFD\xA1\xA2\x4E\xFC\xBC\x19\x92\x3A\x12\x8B\xD4\xB3\x62\xA8\x9D\xBB\x3E\x0C\x08\x12\x67\x20\x7D\x02\x58\xCF\xE7\xD6\x06\xB8\xB0\x14\x0A\x70\xA1\x81\x94\x14\x24\x74'</value>
</values>
</substitution>
</substitutions>
<!-- allow OpenSSL-related code load ciphers and warm-up -->
<fill_query>WITH {plaintext} as plaintext, repeat('k', 32) as key32, substring(key32, 1, 24) as key24, substring(key32, 1, 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12 SELECT count() FROM {table} WHERE NOT ignore({func}) LIMIT 1</fill_query>
<query>WITH {plaintext} as plaintext, repeat('k', 32) as key32, substring(key32, 1, 24) as key24, substring(key32, 1, 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12 SELECT count() FROM {table} WHERE NOT ignore({func})</query>
</test>

View File

@ -0,0 +1,67 @@
<test>
<!-- "Strict" mode (key and iv length checks), empty plaintext.
Ciphers that produce empty ciphertext on empty plaintext, and hence can be optimized to almost NO-OP. -->
<substitutions>
<substitution>
<name>func</name>
<values>
<!-- materialize(plaitext) is to avoid all-args-are-const optimization, resulting in executing function exactly once. -->
<value>encrypt('aes-128-cfb1', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-cfb8', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-cfb128', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-ctr', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-ofb', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-192-cfb1', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-cfb8', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-cfb128', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-ctr', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-ofb', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-256-cfb1', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-cfb8', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-cfb128', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-ctr', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-ofb', materialize(plaintext), key32, iv16)</value>
<!-- decrypt + encrypt since it is really hard to compose decrypt-only case -->
<value>decrypt('aes-128-cfb1', encrypt('aes-128-cfb1', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-cfb8', encrypt('aes-128-cfb8', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-cfb128', encrypt('aes-128-cfb128', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-ctr', encrypt('aes-128-ctr', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-ofb', encrypt('aes-128-ofb', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-192-cfb1', encrypt('aes-192-cfb1', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-cfb8', encrypt('aes-192-cfb8', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-cfb128', encrypt('aes-192-cfb128', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-ctr', encrypt('aes-192-ctr', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-ofb', encrypt('aes-192-ofb', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-256-cfb1', encrypt('aes-256-cfb1', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-cfb8', encrypt('aes-256-cfb8', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-cfb128', encrypt('aes-256-cfb128', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-ctr', encrypt('aes-256-ctr', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-ofb', encrypt('aes-256-ofb', materialize(plaintext), key32, iv16), key32, iv16)</value>
</values>
</substitution>
<substitution>
<name>table</name>
<values>
<value>numbers(10000000)</value>
</values>
</substitution>
<substitution>
<name>plaintext</name>
<values>
<value>''</value>
</values>
</substitution>
</substitutions>
<!-- allow OpenSSL-related code load ciphers and warm-up -->
<fill_query>WITH {plaintext} as plaintext, repeat('k', 32) as key32, substring(key32, 1, 24) as key24, substring(key32, 1, 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12 SELECT count() FROM {table} WHERE NOT ignore({func}) LIMIT 1</fill_query>
<query>WITH {plaintext} as plaintext, repeat('k', 32) as key32, substring(key32, 1, 24) as key24, substring(key32, 1, 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12 SELECT count() FROM {table} WHERE NOT ignore({func})</query>
</test>

View File

@ -0,0 +1,55 @@
<test>
<!-- "Strict" mode (key and iv length checks), empty plaintext.
Ciphers that produce non-empty ciphertext on empty plaintext, and hence can't be optimized.-->
<substitutions>
<substitution>
<name>func</name>
<values>
<!-- materialize(plaitext) is to avoid all-args-are-const optimization, resulting in executing function exactly once. -->
<value>encrypt('aes-128-cbc', materialize(plaintext), key16, iv16)</value>
<value>encrypt('aes-128-ecb', materialize(plaintext), key16)</value>
<value>encrypt('aes-128-gcm', materialize(plaintext), key16, iv12, 'aadaadaadaad')</value>
<value>encrypt('aes-192-cbc', materialize(plaintext), key24, iv16)</value>
<value>encrypt('aes-192-ecb', materialize(plaintext), key24)</value>
<value>encrypt('aes-192-gcm', materialize(plaintext), key24, iv12, 'aadaadaadaad')</value>
<value>encrypt('aes-256-cbc', materialize(plaintext), key32, iv16)</value>
<value>encrypt('aes-256-ecb', materialize(plaintext), key32)</value>
<value>encrypt('aes-256-gcm', materialize(plaintext), key32, iv12, 'aadaadaadaad')</value>
<!-- decrypt + encrypt since it is really hard to compose decrypt-only case -->
<value>decrypt('aes-128-cbc', encrypt('aes-128-cbc', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-128-ecb', encrypt('aes-128-ecb', materialize(plaintext), key16), key16)</value>
<value>decrypt('aes-128-gcm', encrypt('aes-128-gcm', materialize(plaintext), key16, iv12, 'aadaadaadaad'), key16, iv12, 'aadaadaadaad')</value>
<value>decrypt('aes-192-cbc', encrypt('aes-192-cbc', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-192-ecb', encrypt('aes-192-ecb', materialize(plaintext), key24), key24)</value>
<value>decrypt('aes-192-gcm', encrypt('aes-192-gcm', materialize(plaintext), key24, iv12, 'aadaadaadaad'), key24, iv12, 'aadaadaadaad')</value>
<value>decrypt('aes-256-cbc', encrypt('aes-256-cbc', materialize(plaintext), key32, iv16), key32, iv16)</value>
<value>decrypt('aes-256-ecb', encrypt('aes-256-ecb', materialize(plaintext), key32), key32)</value>
<value>decrypt('aes-256-gcm', encrypt('aes-256-gcm', materialize(plaintext), key32, iv12, 'aadaadaadaad'), key32, iv12, 'aadaadaadaad')</value>
</values>
</substitution>
<substitution>
<name>table</name>
<values>
<value>numbers(100000)</value>
</values>
</substitution>
<substitution>
<name>plaintext</name>
<values>
<value>''</value>
</values>
</substitution>
</substitutions>
<!-- allow OpenSSL-related code load ciphers and warm-up -->
<fill_query>WITH {plaintext} as plaintext, repeat('k', 32) as key32, substring(key32, 1, 24) as key24, substring(key32, 1, 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12 SELECT count() FROM {table} WHERE NOT ignore({func}) LIMIT 1</fill_query>
<query>WITH {plaintext} as plaintext, repeat('k', 32) as key32, substring(key32, 1, 24) as key24, substring(key32, 1, 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12 SELECT count() FROM {table} WHERE NOT ignore({func})</query>
</test>

View File

@ -0,0 +1,36 @@
<test>
<!-- "Strict" mode (key and iv length checks), non-empty plaintext.
Ciphers that are slow on every input are put here. -->
<substitutions>
<substitution>
<name>func</name>
<values>
<!-- decrypt + encrypt since it is really hard to compose decrypt-only case -->
<value>decrypt('aes-128-cfb1', encrypt('aes-128-cfb1', materialize(plaintext), key16, iv16), key16, iv16)</value>
<value>decrypt('aes-192-cfb1', encrypt('aes-192-cfb1', materialize(plaintext), key24, iv16), key24, iv16)</value>
<value>decrypt('aes-256-cfb1', encrypt('aes-256-cfb1', materialize(plaintext), key32, iv16), key32, iv16)</value>
</values>
</substitution>
<substitution>
<name>table</name>
<values>
<value>numbers(50000)</value>
</values>
</substitution>
<substitution>
<name>plaintext</name>
<values>
<value>number</value>
<value>'paintext'</value>
<value>'\x12\x2B\xF9\x16\x93\xA4\xD6\x74\x22\xD9\x17\x5E\x38\xCD\x1D\x7B\xB0\x12\xEC\x43\x6B\xC7\x76\xFD\xA1\xA2\x4E\xFC\xBC\x19\x92\x3A\x12\x8B\xD4\xB3\x62\xA8\x9D\xBB\x3E\x0C\x08\x12\x67\x20\x7D\x02\x58\xCF\xE7\xD6\x06\xB8\xB0\x14\x0A\x70\xA1\x81\x94\x14\x24\x74'</value>
</values>
</substitution>
</substitutions>
<!-- allow OpenSSL-related code load ciphers and warm-up -->
<fill_query>WITH {plaintext} as plaintext, repeat('k', 32) as key32, substring(key32, 1, 24) as key24, substring(key32, 1, 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12 SELECT count() FROM {table} WHERE NOT ignore({func}) LIMIT 1</fill_query>
<query>WITH {plaintext} as plaintext, repeat('k', 32) as key32, substring(key32, 1, 24) as key24, substring(key32, 1, 16) as key16, repeat('iv', 8) as iv16, substring(iv16, 1, 12) as iv12 SELECT count() FROM {table} WHERE NOT ignore({func})</query>
</test>

View File

@ -0,0 +1,144 @@
0
0
0
1
MySQL-compatitable mode, with key folding, no length checks, etc.
aes-128-cbc 1
aes-128-cbc 1
aes-128-cbc 1
aes-192-cbc 1
aes-192-cbc 1
aes-192-cbc 1
aes-256-cbc 1
aes-256-cbc 1
aes-256-cbc 1
aes-128-cfb1 1
aes-128-cfb1 1
aes-128-cfb1 1
aes-192-cfb1 1
aes-192-cfb1 1
aes-192-cfb1 1
aes-256-cfb1 1
aes-256-cfb1 1
aes-256-cfb1 1
aes-128-cfb8 1
aes-128-cfb8 1
aes-128-cfb8 1
aes-192-cfb8 1
aes-192-cfb8 1
aes-192-cfb8 1
aes-256-cfb8 1
aes-256-cfb8 1
aes-256-cfb8 1
aes-128-cfb128 1
aes-128-cfb128 1
aes-128-cfb128 1
aes-192-cfb128 1
aes-192-cfb128 1
aes-192-cfb128 1
aes-256-cfb128 1
aes-256-cfb128 1
aes-256-cfb128 1
aes-128-ecb 1
aes-128-ecb 1
aes-128-ecb 1
aes-192-ecb 1
aes-192-ecb 1
aes-192-ecb 1
aes-256-ecb 1
aes-256-ecb 1
aes-256-ecb 1
aes-128-ofb 1
aes-128-ofb 1
aes-128-ofb 1
aes-192-ofb 1
aes-192-ofb 1
aes-192-ofb 1
aes-256-ofb 1
aes-256-ofb 1
aes-256-ofb 1
Strict mode without key folding and proper key and iv lengths checks.
aes-128-cbc 1
aes-128-cbc 1
aes-128-cbc 1
aes-192-cbc 1
aes-192-cbc 1
aes-192-cbc 1
aes-256-cbc 1
aes-256-cbc 1
aes-256-cbc 1
aes-128-cfb1 1
aes-128-cfb1 1
aes-128-cfb1 1
aes-192-cfb1 1
aes-192-cfb1 1
aes-192-cfb1 1
aes-256-cfb1 1
aes-256-cfb1 1
aes-256-cfb1 1
aes-128-cfb8 1
aes-128-cfb8 1
aes-128-cfb8 1
aes-192-cfb8 1
aes-192-cfb8 1
aes-192-cfb8 1
aes-256-cfb8 1
aes-256-cfb8 1
aes-256-cfb8 1
aes-128-cfb128 1
aes-128-cfb128 1
aes-128-cfb128 1
aes-192-cfb128 1
aes-192-cfb128 1
aes-192-cfb128 1
aes-256-cfb128 1
aes-256-cfb128 1
aes-256-cfb128 1
aes-128-ctr 1
aes-128-ctr 1
aes-128-ctr 1
aes-192-ctr 1
aes-192-ctr 1
aes-192-ctr 1
aes-256-ctr 1
aes-256-ctr 1
aes-256-ctr 1
aes-128-ecb 1
aes-128-ecb 1
aes-128-ecb 1
aes-192-ecb 1
aes-192-ecb 1
aes-192-ecb 1
aes-256-ecb 1
aes-256-ecb 1
aes-256-ecb 1
aes-128-ofb 1
aes-128-ofb 1
aes-128-ofb 1
aes-192-ofb 1
aes-192-ofb 1
aes-192-ofb 1
aes-256-ofb 1
aes-256-ofb 1
aes-256-ofb 1
GCM mode with IV
aes-128-gcm 1
aes-128-gcm 1
aes-128-gcm 1
aes-192-gcm 1
aes-192-gcm 1
aes-192-gcm 1
aes-256-gcm 1
aes-256-gcm 1
aes-256-gcm 1
GCM mode with IV and AAD
aes-128-gcm 1
aes-128-gcm 1
aes-128-gcm 1
aes-192-gcm 1
aes-192-gcm 1
aes-192-gcm 1
aes-256-gcm 1
aes-256-gcm 1
aes-256-gcm 1
F56E87055BC32D0EEB31B2EACC2BF2A5 1

View File

@ -0,0 +1,152 @@
--- aes_decrypt_mysql(string, key, block_mode[, init_vector, AAD])
-- The MySQL-compatitable encryption, only ecb, cbc, cfb1, cfb8, cfb128 and ofb modes are supported,
-- just like for MySQL
-- https://dev.mysql.com/doc/refman/8.0/en/encryption-functions.html#function_aes-encrypt
-- https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_block_encryption_mode
-- Please note that for keys that exceed mode-specific length, keys are folded in a MySQL-specific way,
-- meaning that whole key is used, but effective key length is still determined by mode.
-- when key doesn't exceed the default mode length, ecryption result equals with AES_encypt()
-----------------------------------------------------------------------------------------
-- error cases
-----------------------------------------------------------------------------------------
SELECT aes_decrypt_mysql(); --{serverError 42} not enough arguments
SELECT aes_decrypt_mysql('aes-128-ecb'); --{serverError 42} not enough arguments
SELECT aes_decrypt_mysql('aes-128-ecb', 'text'); --{serverError 42} not enough arguments
-- Mode
SELECT aes_decrypt_mysql(789, 'text', 'key'); --{serverError 43} bad mode type
SELECT aes_decrypt_mysql('blah blah blah', 'text', 'key'); -- {serverError 36} garbage mode value
SELECT aes_decrypt_mysql('des-ede3-ecb', 'text', 'key'); -- {serverError 36} bad mode value of valid cipher name
SELECT aes_decrypt_mysql('aes-128-gcm', 'text', 'key'); -- {serverError 36} mode is not supported by _mysql-functions
SELECT decrypt(789, 'text', 'key'); --{serverError 43} bad mode type
SELECT decrypt('blah blah blah', 'text', 'key'); -- {serverError 36} garbage mode value
SELECT decrypt('des-ede3-ecb', 'text', 'key'); -- {serverError 36} bad mode value of valid cipher name
-- Key
SELECT aes_decrypt_mysql('aes-128-ecb', 'text', 456); --{serverError 43} bad key type
SELECT aes_decrypt_mysql('aes-128-ecb', 'text', 'key'); -- {serverError 36} key is too short
SELECT decrypt('aes-128-ecb', 'text'); --{serverError 42} key is missing
SELECT decrypt('aes-128-ecb', 'text', 456); --{serverError 43} bad key type
SELECT decrypt('aes-128-ecb', 'text', 'key'); -- {serverError 36} key is too short
SELECT decrypt('aes-128-ecb', 'text', 'keykeykeykeykeykeykeykeykeykeykeykey'); -- {serverError 36} key is to long
-- IV
SELECT aes_decrypt_mysql('aes-128-ecb', 'text', 'key', 1011); --{serverError 43} bad IV type 6
SELECT aes_decrypt_mysql('aes-128-ecb', 'text', 'key', 'iv'); --{serverError 36} IV is too short 4
SELECT decrypt('aes-128-cbc', 'text', 'keykeykeykeykeyk', 1011); --{serverError 43} bad IV type 1
SELECT decrypt('aes-128-cbc', 'text', 'keykeykeykeykeyk', 'iviviviviviviviviviviviviviviviviviviviviv'); --{serverError 36} IV is too long 3
SELECT decrypt('aes-128-cbc', 'text', 'keykeykeykeykeyk', 'iv'); --{serverError 36} IV is too short 2
--AAD
SELECT aes_decrypt_mysql('aes-128-ecb', 'text', 'key', 'IV', 1213); --{serverError 42} too many arguments
SELECT decrypt('aes-128-ecb', 'text', 'key', 'IV', 1213); --{serverError 43} bad AAD type
SELECT decrypt('aes-128-gcm', 'text', 'key', 'IV', 1213); --{serverError 43} bad AAD type
-- Invalid ciphertext should cause an error or produce garbage
SELECT ignore(decrypt('aes-128-ecb', 'hello there', '1111111111111111')); -- {serverError 454} 1
SELECT ignore(decrypt('aes-128-cbc', 'hello there', '1111111111111111')); -- {serverError 454} 2
SELECT ignore(decrypt('aes-128-cfb1', 'hello there', '1111111111111111')); -- GIGO
SELECT ignore(decrypt('aes-128-ofb', 'hello there', '1111111111111111')); -- GIGO
SELECT ignore(decrypt('aes-128-ctr', 'hello there', '1111111111111111')); -- GIGO
SELECT decrypt('aes-128-ctr', '', '1111111111111111') == '';
-----------------------------------------------------------------------------------------
-- Validate against predefined ciphertext,plaintext,key and IV for MySQL compatibility mode
-----------------------------------------------------------------------------------------
CREATE TABLE encryption_test
(
input String,
key String DEFAULT unhex('fb9958e2e897ef3fdb49067b51a24af645b3626eed2f9ea1dc7fd4dd71b7e38f9a68db2a3184f952382c783785f9d77bf923577108a88adaacae5c141b1576b0'),
iv String DEFAULT unhex('8CA3554377DFF8A369BC50A89780DD85'),
key32 String DEFAULT substring(key, 1, 32),
key24 String DEFAULT substring(key, 1, 24),
key16 String DEFAULT substring(key, 1, 16)
) Engine = Memory;
INSERT INTO encryption_test (input)
VALUES (''), ('text'), ('What Is ClickHouse? ClickHouse is a column-oriented database management system (DBMS) for online analytical processing of queries (OLAP).');
SELECT 'MySQL-compatitable mode, with key folding, no length checks, etc.';
SELECT 'aes-128-cbc' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-192-cbc' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-256-cbc' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-128-cfb1' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-192-cfb1' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-256-cfb1' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-128-cfb8' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-192-cfb8' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-256-cfb8' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-128-cfb128' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-192-cfb128' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-256-cfb128' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-128-ecb' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-192-ecb' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-256-ecb' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-128-ofb' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-192-ofb' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'aes-256-ofb' as mode, aes_decrypt_mysql(mode, aes_encrypt_mysql(mode, input, key, iv), key, iv) == input FROM encryption_test;
SELECT 'Strict mode without key folding and proper key and iv lengths checks.';
SELECT 'aes-128-cbc' as mode, decrypt(mode, encrypt(mode, input, key16, iv), key16, iv) == input FROM encryption_test;
SELECT 'aes-192-cbc' as mode, decrypt(mode, encrypt(mode, input, key24, iv), key24, iv) == input FROM encryption_test;
SELECT 'aes-256-cbc' as mode, decrypt(mode, encrypt(mode, input, key32, iv), key32, iv) == input FROM encryption_test;
SELECT 'aes-128-cfb1' as mode, decrypt(mode, encrypt(mode, input, key16, iv), key16, iv) == input FROM encryption_test;
SELECT 'aes-192-cfb1' as mode, decrypt(mode, encrypt(mode, input, key24, iv), key24, iv) == input FROM encryption_test;
SELECT 'aes-256-cfb1' as mode, decrypt(mode, encrypt(mode, input, key32, iv), key32, iv) == input FROM encryption_test;
SELECT 'aes-128-cfb8' as mode, decrypt(mode, encrypt(mode, input, key16, iv), key16, iv) == input FROM encryption_test;
SELECT 'aes-192-cfb8' as mode, decrypt(mode, encrypt(mode, input, key24, iv), key24, iv) == input FROM encryption_test;
SELECT 'aes-256-cfb8' as mode, decrypt(mode, encrypt(mode, input, key32, iv), key32, iv) == input FROM encryption_test;
SELECT 'aes-128-cfb128' as mode, decrypt(mode, encrypt(mode, input, key16, iv), key16, iv) == input FROM encryption_test;
SELECT 'aes-192-cfb128' as mode, decrypt(mode, encrypt(mode, input, key24, iv), key24, iv) == input FROM encryption_test;
SELECT 'aes-256-cfb128' as mode, decrypt(mode, encrypt(mode, input, key32, iv), key32, iv) == input FROM encryption_test;
SELECT 'aes-128-ctr' as mode, decrypt(mode, encrypt(mode, input, key16, iv), key16, iv) == input FROM encryption_test;
SELECT 'aes-192-ctr' as mode, decrypt(mode, encrypt(mode, input, key24, iv), key24, iv) == input FROM encryption_test;
SELECT 'aes-256-ctr' as mode, decrypt(mode, encrypt(mode, input, key32, iv), key32, iv) == input FROM encryption_test;
SELECT 'aes-128-ecb' as mode, decrypt(mode, encrypt(mode, input, key16), key16) == input FROM encryption_test;
SELECT 'aes-192-ecb' as mode, decrypt(mode, encrypt(mode, input, key24), key24) == input FROM encryption_test;
SELECT 'aes-256-ecb' as mode, decrypt(mode, encrypt(mode, input, key32), key32) == input FROM encryption_test;
SELECT 'aes-128-ofb' as mode, decrypt(mode, encrypt(mode, input, key16, iv), key16, iv) == input FROM encryption_test;
SELECT 'aes-192-ofb' as mode, decrypt(mode, encrypt(mode, input, key24, iv), key24, iv) == input FROM encryption_test;
SELECT 'aes-256-ofb' as mode, decrypt(mode, encrypt(mode, input, key32, iv), key32, iv) == input FROM encryption_test;
SELECT 'GCM mode with IV';
SELECT 'aes-128-gcm' as mode, decrypt(mode, encrypt(mode, input, key16, iv), key16, iv) == input FROM encryption_test;
SELECT 'aes-192-gcm' as mode, decrypt(mode, encrypt(mode, input, key24, iv), key24, iv) == input FROM encryption_test;
SELECT 'aes-256-gcm' as mode, decrypt(mode, encrypt(mode, input, key32, iv), key32, iv) == input FROM encryption_test;
SELECT 'GCM mode with IV and AAD';
SELECT 'aes-128-gcm' as mode, decrypt(mode, encrypt(mode, input, key16, iv, 'AAD'), key16, iv, 'AAD') == input FROM encryption_test;
SELECT 'aes-192-gcm' as mode, decrypt(mode, encrypt(mode, input, key24, iv, 'AAD'), key24, iv, 'AAD') == input FROM encryption_test;
SELECT 'aes-256-gcm' as mode, decrypt(mode, encrypt(mode, input, key32, iv, 'AAD'), key32, iv, 'AAD') == input FROM encryption_test;
-- based on https://github.com/openssl/openssl/blob/master/demos/evp/aesgcm.c#L20
WITH
unhex('eebc1f57487f51921c0465665f8ae6d1658bb26de6f8a069a3520293a572078f') as key,
unhex('67ba0510262ae487d737ee6298f77e0c') as tag,
unhex('99aa3e68ed8173a0eed06684') as iv,
unhex('f56e87055bc32d0eeb31b2eacc2bf2a5') as plaintext,
unhex('4d23c3cec334b49bdb370c437fec78de') as aad,
unhex('f7264413a84c0e7cd536867eb9f21736') as ciphertext
SELECT
hex(decrypt('aes-256-gcm', concat(ciphertext, tag), key, iv, aad)) as plaintext_actual,
plaintext_actual = hex(plaintext);

View File

@ -0,0 +1,147 @@
UInt64 5417DEA8D67A1A03FD561809C62402FF
Float64 9B66D0AA685DC0F1EFFA2E385F7EA2F2
Decimal64 5417DEA8D67A1A03FD561809C62402FF
MySQL-compatitable mode, with key folding, no length checks, etc.
aes-128-cbc B2EB028BC2236566A946085E65A5632B
aes-128-cbc 25026C55954363AEF90325822218C370
aes-128-cbc 683051259880871EA8EBFBBF5360D1DA96D967450DFEFA45C89C8B2D59121602C1C5A54AAB8C95EC53F46E5A021BEDE7B5B2B9E83A416726F0DD750F6ACE9C654C986C3B3C9BEB497F54BFA2EF1B107EF204E7508C4E2D37797641404E51D496DFE477A49DCECB2EB47FC6BB6A13CF72AD19E99CEE7656D3EA29BDBC673879D7814D158FB8CB0760DFE89943BE3234C1
aes-192-cbc 829FA9DAF77594921A16494EC005AD29
aes-192-cbc 20727A7264B5DD601ECFE40FB9CF50B0
aes-192-cbc BBFE507F31FF227F74EFA831CCE338443947492DD8141840B0C3D006404836574040AC80DD243311E1A791BB0C5C02AB4DF85BA39B102056DA75CF1E32BAA8836E616D27542F84EA4792F41CD1180E0FF66ACACEDAC4AFD0D2031771C370D413A077DC755F5AF38A441950958216B1130BBF6265E1CE824A6E9B0EE76993495535654B10344A201171D9F1A788FBB45A
aes-256-cbc 8C0F6D5F2B0E751EC2033C5CA08E99F7
aes-256-cbc D907F72499D48DB36850AF2C038CEF62
aes-256-cbc A398D86A880BA4DE1FBA1ABBD38B9DA3B038B198134E798A4CF134029DB18B0A53F9A7177DAA5270EAD7448C56EB96351745A779A56B42A848FE265BFAB85BF349580A9E1751E627AEE80A8B0FC9046F18B8CF575358E21F5E353F115B2BF87984DB199744D4A83E58AD4764B6DFA92F933E0E1AA1F2AA95E4A9662C9BD8F1AC8D79BF531E77EDDB1A25CCD52D4E994D
aes-128-cfb1
aes-128-cfb1 78BF3E26
aes-128-cfb1 4A9D5DC0463F6C4E353E20ED62EFE9B9470882BEFE403CDCEF73019133EAA6B38E92C8C8D0BA46DFEE332A4D1003481EF5E05AB30244ECBFB46E1FD878377D9A8209630C2304D42B2B8F545841535DE3C3D7FC6DD839EB8C35D9CB7172D0F5B9AE7EB3E1BE2F1E42007BA76FBBFE9B38225071468717E9C8EFBA73FDA016A533F709B1B4B18AFD4D85
aes-192-cfb1
aes-192-cfb1 6B44342A
aes-192-cfb1 5D93C18B6821DA795910E27BA92A0F6C1BB74F924C5D369D4DB4697AC9F2F2F9F7159EC34C66260DB6BEE4BE13F51344EDC640F10B6ED64D1DD891FF8542ECA6B9CA7BB64DCA160C5460CE1F1BF602C16B571E35FBFFD4F26EC34FBBCE14D9C56ABE18779C9DC5266601573B4D25B188E0778EE77C98B0B16F65623BBB834F2B254B84D1B891ED4105
aes-256-cfb1
aes-256-cfb1 51860DF1
aes-256-cfb1 687FB4B773E5C87F8B42E6A9B2538EC3D1B302B11BCECC0F846B2D5BB3050C41BAF43B29271F663035A27863C913C7149B5E1CF08E803616B9B649EB33C63F66EF608876A5BB43ABDD310E40597DDC93E88E4465663D7E967A0E1EA68C98CD5E039B08843EDE8E11A66DBBA67F3D4844EB0270732BE69ADFEF6DC6E801E100479AB86AFE3447454841
aes-128-cfb8
aes-128-cfb8 0EC82D99
aes-128-cfb8 2DDE927A331C8482A453901E6EA1119746A5E6E7452DDC1349973A04433AD56C3473D10EFC5030B9BDC2549D607D174469134D73AC325C2B6E2BDF8F4D323B82F37222FC05C199EDA9693490EFA52427B00E872F9D89FC2262147296B5957BE8EA8FF2A6BF5BB3A6537C0A81D8BBC671E716C3B52504F2D567031AAC33B4434677BAF0944E883961DA
aes-192-cfb8
aes-192-cfb8 054CD2E8
aes-192-cfb8 26AC354F7232BD5A0B3CDC241EFF3ED9258E118FC0301E1CA1A745FC20F029066D1D3DA5368A2FE7B589CD6242F68546999DF68A0E1DE018B5B3DCD5CA911506FC6EFADC769CB6CFE2A91749C2DBA06D4614E351A4AAC58C381344DB44E3A83F31A299823B2158C4E65B457072CFBAD4D14FE9960876245E840117E8B39018D6D34C4832510A1992BD
aes-256-cfb8
aes-256-cfb8 7FA03B1B
aes-256-cfb8 5C67ABAE9944F8BE6C35F1B348CF2E112ECF45349EA2BCFC1789EA89B7298998E8886E9147FA9AEBC3DFBEFB3320C1661251A9129DBC14649D88983371D10185E6C6D0C935438344B161999191C05CA805E7C5A7410C50370FE3347CDE4A21F5089831116701B324A5CBB24EE604F043259B8898976B807DEB3544951C0AB2C2CE55DE964B4BBD285E
aes-128-cfb128
aes-128-cfb128 0EAAFAF5
aes-128-cfb128 2DA7E3F5CD13148BED988533A2560F52959044EC2FF38A1D1A66DB2B20635FC8800060DA0062E0399CFE059E5E687F4BBA5E7182A4D79F18317B970708F079A59771C231EBA359741565B903BA820EE3EA07249777E745387B9774EE495940A50121A617B20768AA3A1A78AC9D49983E7BD43CD7BD21504640EAB23F57AB9E5B6260D875B665A63359
aes-192-cfb128
aes-192-cfb128 053E029A
aes-192-cfb128 26331B9AEF235360655730F3D8905479AEACC18B2FFCC7FF355DBA918A2B09C5FEEE817C929400A3E412A7528EB6D79846B687858E250AD54A9913CB81009AC55E391163ECCEF6DA0095C4C57B2C913D70B82C0B14ADD59DD559A039B48A47C86142D15922E86FE2586707F689DFD962D2B96E3571151D642A8E8CC2F2CC09D17F009592B0963AD2AB
aes-256-cfb128
aes-256-cfb128 7FB039F7
aes-256-cfb128 5CBD20F7ABD3AC41FCAA1A5C0E119E2BB5174FDACA4353FFA5D1BC2928CE015E08C7D813C51A9493902FD4BF6A864FA6F26B220C8FD21B846C90453241F369B170B6DAAF5B7E10AF025EA6EBF3852BCDA6AA92DA086B13162E3CCCC859FE3A6A8782395247785951D5095305BE97196C4A8C7607CFC0191A4DEB72F085ECF759F5AA5CBD3BE60A7FF5
aes-128-ecb FEA8CFDE6EE2C6E7A2CC6ADDC9F62C83
aes-128-ecb 78B16CD4BE107660156124C5FEE6454A
aes-128-ecb 67C0B119D96F18E2823968D42871B3D126D5DDD35074303974946BE81A246757C3ACAEBFE0590EC98C4F51469E9FE27A8F8A98749E4DCAEF02F2076AC4CEB317062C0531F5FD2A505FE62413D8B0900ECAB5B8E1909A4A38FF922E3302857A16CE8E6804ACBA36C5E00EF5054288922517E59A47D0A26451905DE9E391D683ABB5852B5611886A2EF662AC8A1E156D85
aes-192-ecb 99BA10452392CF90CC4D24489213BE78
aes-192-ecb EB9D63FB9A457DB400EDE00878E828B1
aes-192-ecb 4ADC9AA9BDD0A70C9FAEEA565C0C3329E2D0D5A9BB5F48ADB440F2676173CBB099898BBDF3DE98BCE4C0D663916E8CF401B063AD51BF3110C2C318DECB62F3C87B564C61794F6B393761745626A58DC3485E3930E4145E35C343DB56FB51D831C9EDB07987939009EB4241A0E3BE9CF64E235081AB5EFBBE585FE547AC49F65E5D1E772DE16A0BC85D7C60CAC34094A8
aes-256-ecb 42575C26B6D9838CF5BB0214CFA7CA31
aes-256-ecb 08B5C9159FA1E2C986FE57CFFE4A5CD7
aes-256-ecb 72FC92DD17DD5E0BA4B621C2F20B9B04C3F81A42BA8E34F1138EAC99C1FD43B85AD238B61B8B68389F432E734345CC26C21D1DCCA80EF4B267BAAEEFCB5A6A00A323693758C8E31DC84BF8E017C81825C51A2D30607174403B61B7D71A3FFBFC6905A977B496DDF09E1C2BDC49AF1AAA0FD3B130404A27F7915007B2E00646C8573137E8AE1DF231C0235F53C1E7A832
aes-128-ofb
aes-128-ofb 0EAAFAF5
aes-128-ofb 2DA7E3F5CD13148BED988533A2560F523B04048D918E339B531EBE886FA60448A32056AE6599984C4FB6F41381A09E822470951A7B154A157C661BEF5116633B8CF39574CB5754368011C249A9A936AA7A2D75812B42E28259D219CE5A69E3B0CF8FEE19427B607E2D02F2A3ED184B4D1387CFCEEA2BD48FF9AB7091F5A7548B8C3601DF0CCBEEBDBC
aes-192-ofb
aes-192-ofb 053E029A
aes-192-ofb 26331B9AEF235360655730F3D890547987BD7D29A17C0B076546820084C2F973C28E93589C68BFBFAC8D212F36A5809F0532ABEE022C1DEC985487DF146BCAAA1A82310DE8EF397A5121873D2335FAC47D05CA27A49048F55366D7AA6BBD4E64740CB36EC538B225D7667D796665E3EFD0BDBE0226F716388A39063A85CCD0969CFA52BE4B2F523603
aes-256-ofb
aes-256-ofb 7FB039F7
aes-256-ofb 5CBD20F7ABD3AC41FCAA1A5C0E119E2BCD544279C69E49904DCC791C2D5A8542FE255641D9F79B6900744A4310F0965F1CC84147CE952A32837B9F0853EC7DDB3FCBF49EC5E7C3674AA38ED3A1FB212C56FBB1A0AEFBF8E8E3AE5C0B08E86E317E3A5A998A9EF062FF95977571804F40C1120E54AFDC495EF95D532BB76F6F5351285AAF302ACCA066
Strict mode without key folding and proper key and iv lengths checks.
aes-128-cbc C09B247E927C81D643CDCA58B2AD3F0D
aes-128-cbc 676ED1EA792A8E2E4B0D3CF45A945D73
aes-128-cbc 7FDC3DAECBD2C89E41561A04ED586244BE3266643877D721F80C78E6E5F0F195A450DC2548A8DB3253D9612DB116B4B50C3B1C2EEB93704942449C7A606DE2035813B83B533FF561A6781F306A8720AE6344F30B8AE4A81920C3A8A777310FF6246B914127983C8D2E951675E929F939F05E50AA0ED635A2564EB276DD428DCB0D6B7CD655E065210955BD373C555D2E
aes-192-cbc 0735013389B1241D9316202CD7A618A2
aes-192-cbc 5DC3B5ACD2CF676F968E12068BA8C675
aes-192-cbc C6390AAB7AB3B7E6A15E8CA4907AE2B5B0D767B30B0BFFF87D76FF025C384669E1DB6769234B89E5CB365B6721D118534D4CDB33977D87FE22CE9D4CF546AF96ED35F558839AFC6748759F3A36B8C44B5232038F0528254EC5FFE58A68C5306C4C4F982FEE2F9955C6833747B2E093AE1F0BF19A4AB16F4429E5FFB2C17F70588A79B67301FDD6D2A731229FF25853B1
aes-256-cbc 8C0F6D5F2B0E751EC2033C5CA08E99F7
aes-256-cbc D907F72499D48DB36850AF2C038CEF62
aes-256-cbc A398D86A880BA4DE1FBA1ABBD38B9DA3B038B198134E798A4CF134029DB18B0A53F9A7177DAA5270EAD7448C56EB96351745A779A56B42A848FE265BFAB85BF349580A9E1751E627AEE80A8B0FC9046F18B8CF575358E21F5E353F115B2BF87984DB199744D4A83E58AD4764B6DFA92F933E0E1AA1F2AA95E4A9662C9BD8F1AC8D79BF531E77EDDB1A25CCD52D4E994D
aes-128-cfb1
aes-128-cfb1 79A4880E
aes-128-cfb1 5A83873C33073FB2AA84F0344C5828D833DE87B85BA3B7A5F27521C072C99359F1E95ABD2C98E02712DAA23F27BDFB28089152BFD4074E1AE3BEF472EE7518FCD824C67FA767142E5BEF00D089F2BB1A31F555CE6DFBAA7D0698C9016AEA1BCF2296DB5820B36E397DD8546874C4A2135C02877828478785F536345EBAD3541D484DED181587D043B1
aes-192-cfb1
aes-192-cfb1 AECB3AEE
aes-192-cfb1 8014FFC665907F3FAB5AA3C7BFEE808BFB744F7EF2AC7243D099ED3D188E6C457F497E875B023F070B7FBA2BDDB091D71CEBB4CD39B19FB61737EB06927A6406B53F6513B07ADE609FEA4D358E9396EA2BE2C3CF873C52B03BA1FAC1540E3491AABAE769C3DFF081224A1A5B8ECFBA098793D3E7FFFD5C810342E780577FF11B0A77E751F8940C1288
aes-256-cfb1
aes-256-cfb1 51860DF1
aes-256-cfb1 687FB4B773E5C87F8B42E6A9B2538EC3D1B302B11BCECC0F846B2D5BB3050C41BAF43B29271F663035A27863C913C7149B5E1CF08E803616B9B649EB33C63F66EF608876A5BB43ABDD310E40597DDC93E88E4465663D7E967A0E1EA68C98CD5E039B08843EDE8E11A66DBBA67F3D4844EB0270732BE69ADFEF6DC6E801E100479AB86AFE3447454841
aes-128-cfb8
aes-128-cfb8 513D0801
aes-128-cfb8 72B632E6010A526E5F7EFEC4ABFF87E2087FB91159399FCF81639B104B5CFD92D7DC4A6FDD1946FCD7883D88A65B3DAB050467886CFF35B33035C7671F85EBEDB7D934A93CE9EECEE251C95E33CC1E7EAB7F38FC37B1BE08F675CBD446B8B4856363DE1BD6976546DAB4A1125BE5A0516C9BCEEF99BC1EE20539160A973771C01EF45D7A8A78F5D3AE
aes-192-cfb8
aes-192-cfb8 F9B3F3EE
aes-192-cfb8 DAB2433E165A0CD4261DCD2B77B9A2D6128D8054F02166B76CC45B681BC7556E48A06A1838C0F5F0BD6C766DBFEFC07769FF986E58F5B5DA9AE8AF1AFC64A038F8939DD51B585A3FFFD13948D6D716D574BAD875258E3E8D2D2CC589982B625E375B31C34B1F50E82125AB91F14ABCD984FA24057D1BB15395214DC830F125A6EDB3C43023F3F403DA
aes-256-cfb8
aes-256-cfb8 7FA03B1B
aes-256-cfb8 5C67ABAE9944F8BE6C35F1B348CF2E112ECF45349EA2BCFC1789EA89B7298998E8886E9147FA9AEBC3DFBEFB3320C1661251A9129DBC14649D88983371D10185E6C6D0C935438344B161999191C05CA805E7C5A7410C50370FE3347CDE4A21F5089831116701B324A5CBB24EE604F043259B8898976B807DEB3544951C0AB2C2CE55DE964B4BBD285E
aes-128-cfb128
aes-128-cfb128 519F7556
aes-128-cfb128 72926C569BC409EA1646E840082C18F28531DE0AEFA2F980ADCEA64A8BC57798CD549092928F115E702F325DA709A7DB445B6BE9C510452ABB78966B4D8EB622303113EB1BB955FB507A11B1092FEA78C5A05F71D8A9E553591AC6E72B833F1BECE8A5E1816742270C12495BD436C93C5DD1EC017A2EEFE5C5966A01D2BA0EED477D46234DFF333F02
aes-192-cfb128
aes-192-cfb128 F978FB28
aes-192-cfb128 DA75E22875FB05DDE0145038A775E5BD6397D4DC5839CCF84C1F2D0983E87E06A7B1DB1E25FF9A3C0C7BE9FAF61AAC2DE08AAD9C08694F7E35F8E144967C0C798365AB4BA5DF2308014862C80617AF0BC6857B15806412A0E5CAB5B017196A3AFFB73DB33E3D3954FA1F8839501CD117003ED139231E15B28B5E73FBF84E3CC047A2DA0ADA74C25DE8
aes-256-cfb128
aes-256-cfb128 7FB039F7
aes-256-cfb128 5CBD20F7ABD3AC41FCAA1A5C0E119E2BB5174FDACA4353FFA5D1BC2928CE015E08C7D813C51A9493902FD4BF6A864FA6F26B220C8FD21B846C90453241F369B170B6DAAF5B7E10AF025EA6EBF3852BCDA6AA92DA086B13162E3CCCC859FE3A6A8782395247785951D5095305BE97196C4A8C7607CFC0191A4DEB72F085ECF759F5AA5CBD3BE60A7FF5
aes-128-ctr
aes-128-ctr 519F7556
aes-128-ctr 72926C569BC409EA1646E840082C18F24A5A4A7A178EBCBAC0F170479253ACD2A18968DEAB5148C9C2E878B8F4B7C82B6601A0CD5470AA74EA7A2AAD15107950FC50786C3DC483B8BCA6DF1E5CDD64332C2289641EF66271DFEF9D07B392D4762AEE8FD65E0E8E8CB4FBE26D9D94A207352046380BB44AF32EDB900F6796EA87FC4C52A092CEB229C7
aes-192-ctr
aes-192-ctr F978FB28
aes-192-ctr DA75E22875FB05DDE0145038A775E5BDD158C02B77DD5840038E297E275500B3B8CA25422979B29D57F07B94359EF6F84552018BEC0D8CD444A852E31BCAD95811D396DA223589983AE09C80D27E690B3CCFEE1AD0E6F30493A8221698F12286F86F2202A7BABFC0F710B234994CDA7001E3CD237B171D663EB425D08D330557812F6D8897F1B30E93
aes-256-ctr
aes-256-ctr 7FB039F7
aes-256-ctr 5CBD20F7ABD3AC41FCAA1A5C0E119E2B3259493C5A24845535AF1E97FACD790FB5C06D94F7292D617D38EC3319718C29F9CC533560228E892CC9C80867167AC8F26B09D34E5917A59B35D7DF30604B66F2E5891E22539F1B8581037933B623132FE4249191908457BB27E08CA8E2FE066B1119FD9DE6C7A604F4D2DDC4C64A6D37CDD7C1BA883EF759
aes-128-ecb 4603E6862B0D94BBEC68E0B0DF51D60F
aes-128-ecb 3004851B86D3F3950672DE7085D27C03
aes-128-ecb E807F8C8D40A11F65076361AFC7D8B6844054F47B421F0AA0C0D693388A8779A08D71389C06C509D73FA533392DBD24F1600A9650F7F8D1D55F65E50312D48A6CFA69BDCB8D096AB47E8BDA65DC5DA6A5245536312D04882DC94ACF050F3E53A22CAC2D6C1962697DA311B595A086A8DA3EFDE5E1AE0A7009455F3CB6621EADB1E74727BF0F4AF0C4191FE504EA1BBB4
aes-192-ecb 046D3CD33E7B61B75D1BE371CA44DD76
aes-192-ecb 37CE413D3B953BCEB7FAD79837DB5F1C
aes-192-ecb 60CCA1B9A0E5F2E88561E960309229385DB05D62A012FF35AF39D0577C3E31C1D55BB51C9DD3DA07F87E565031A40900745844A5CC79B143662BD392581DAFD17E829EB15C0D5D853B49FD5536F0E3F2E8B3337BBA63C06AAD32C282C98F42D45442CE8971CACE0BAC9852E656A6A7F6A8203EA1BC77AC3965CA192CC817D52A628217933A2B5C2264A71B6E60354997
aes-256-ecb 42575C26B6D9838CF5BB0214CFA7CA31
aes-256-ecb 08B5C9159FA1E2C986FE57CFFE4A5CD7
aes-256-ecb 72FC92DD17DD5E0BA4B621C2F20B9B04C3F81A42BA8E34F1138EAC99C1FD43B85AD238B61B8B68389F432E734345CC26C21D1DCCA80EF4B267BAAEEFCB5A6A00A323693758C8E31DC84BF8E017C81825C51A2D30607174403B61B7D71A3FFBFC6905A977B496DDF09E1C2BDC49AF1AAA0FD3B130404A27F7915007B2E00646C8573137E8AE1DF231C0235F53C1E7A832
aes-128-ofb
aes-128-ofb 519F7556
aes-128-ofb 72926C569BC409EA1646E840082C18F273A5DC2A93E6F58F6191847385035377DECB7C20E0E35B04724FA5B4473999A192B9C6125A441DA50AE071E7A0924B4770278CD219870320F9654177936CEBB5DBAC5E065596D56ED010E57FCC66B9A1FA541B96FCBEAEB4F8D177FEEAAFB9A78C0F1A55B15C1B1009F0EBBB4AEBF4D2DC537EA3012A99F7E4
aes-192-ofb
aes-192-ofb F978FB28
aes-192-ofb DA75E22875FB05DDE0145038A775E5BD26133E8DFB8FC939B564D224E623B825FB59E66D34DA6499850F9A390CB7531D31AB0567D77BF2DD4EE7AA9FD39ACA53B589A12627292B4A707D2F3069DB542D797213C379EFBF700F6F638FB0A98307F2CBC7F73E1DC857885B8DF4F5BC190E65B77ED27BA283027262D953FDA346F8FD2435996BFC919171
aes-256-ofb
aes-256-ofb 7FB039F7
aes-256-ofb 5CBD20F7ABD3AC41FCAA1A5C0E119E2BCD544279C69E49904DCC791C2D5A8542FE255641D9F79B6900744A4310F0965F1CC84147CE952A32837B9F0853EC7DDB3FCBF49EC5E7C3674AA38ED3A1FB212C56FBB1A0AEFBF8E8E3AE5C0B08E86E317E3A5A998A9EF062FF95977571804F40C1120E54AFDC495EF95D532BB76F6F5351285AAF302ACCA066
GCM mode with IV
aes-128-gcm 3D67D2B8D8F49A24C482085FEC494231
aes-128-gcm C08B1CF60C5A2C92C55DAC62223CBA22C736446C
aes-128-gcm E38605F61AE032292F25A35A6CDF6E827980625B9A50BB3C77CD2AD54DB9BE5578322CC55569D1B0C5B82577060C0053388F511DB7BF9C898BF4B05FB6C8C0D0F50449C06A2E89E086610CB2BAEF25B206312836884DCBCC6CD8329B2A43E2BA751183B1696AB3F070BE94FA823F1E1A5E2372A06E1AD2719AF37983D23FCD199820B7769E72EDC20AF48466DAEB6550DC7FDBA041F77D5231
aes-192-gcm FC2C8C63A570E584AB71F19BA6E79A8F
aes-192-gcm 9A6CF0FDA93F1614F982B5570CC1216D84E356BD
aes-192-gcm B961E9FD9B940EBAD7ADDA75C9F198A40797A598AC7FA183AC58705EF6E4E295504D71B85D81978B4CE196AFFFA019B941F44B14DF06375688FCA986F2F3088C24E955392F0DB378F033052822D560CD8DDFF5472C66029E997AE2D63935DAAA10D6703E5AB627E8168F16CF5CDB1112DD2D49F10E087BA20831DCCE592465C95AAA5AF8F766BAEDC3FD3949EDD2E667333C83E58786504137
aes-256-gcm E99DBEBC01F021758352D7FBD9039EFA
aes-256-gcm 8742CE3A7B0595B281C712600D274CA881F47414
aes-256-gcm A44FD73ACEB1A64BDE2D03808A2576EDBB6076F61614CC84A960CCBE55FBABF365671B7017BC89C8A2E0A633E0A05E40B2681B33AD3E7A0AC4925DBD9735C4D1C1E33726B1D6A83CBD337A65C50D7CC33CC4E64369D54C1B6AF3A82D206DF0698BEB61EF9AB2DF81B03DF3829A2EC42D667D87376B8A1351C69BB7A11CCBE50DA88ABA991E98D3BD71129682F35422AD73B05EC624357E77FC
GCM mode with IV and AAD
aes-128-gcm 5AB059BB98F087E8134B19E7EB5CD9C7
aes-128-gcm C08B1CF67AD5D38FE0F3508689794961B8D1FAB8
aes-128-gcm E38605F61AE032292F25A35A6CDF6E827980625B9A50BB3C77CD2AD54DB9BE5578322CC55569D1B0C5B82577060C0053388F511DB7BF9C898BF4B05FB6C8C0D0F50449C06A2E89E086610CB2BAEF25B206312836884DCBCC6CD8329B2A43E2BA751183B1696AB3F070BE94FA823F1E1A5E2372A06E1AD2719AF37983D23FCD199820B7769E72EDC20A0826DB2A479DB59F7216A9BDCBD0C989
aes-192-gcm 04C13E4B1D62481ED22B3644595CB5DB
aes-192-gcm 9A6CF0FD2B329B04EAD18301818F016DF8F77447
aes-192-gcm B961E9FD9B940EBAD7ADDA75C9F198A40797A598AC7FA183AC58705EF6E4E295504D71B85D81978B4CE196AFFFA019B941F44B14DF06375688FCA986F2F3088C24E955392F0DB378F033052822D560CD8DDFF5472C66029E997AE2D63935DAAA10D6703E5AB627E8168F16CF5CDB1112DD2D49F10E087BA20831DCCE592465C95AAA5AF8F766BAEDC3668E035498D8C46FB662833CCC12C9D6
aes-256-gcm E94F5F6ED4A99B741D492D7EA10B7147
aes-256-gcm 8742CE3A3EA5153952DB4C0D94B501FE878FF9A7
aes-256-gcm A44FD73ACEB1A64BDE2D03808A2576EDBB6076F61614CC84A960CCBE55FBABF365671B7017BC89C8A2E0A633E0A05E40B2681B33AD3E7A0AC4925DBD9735C4D1C1E33726B1D6A83CBD337A65C50D7CC33CC4E64369D54C1B6AF3A82D206DF0698BEB61EF9AB2DF81B03DF3829A2EC42D667D87376B8A1351C69BB7A11CCBE50DA88ABA991E98D3BD712F56268961DDAB59FA4D2B50578602C4
Nullable and LowCardinality
Nullable(String) \N
Nullable(String) 7FB039F7
LowCardinality(String) 7FB039F7
F7264413A84C0E7CD536867EB9F2173667BA0510262AE487D737EE6298F77E0C 1

View File

@ -0,0 +1,156 @@
--- aes_encrypt_mysql(string, key, block_mode[, init_vector, AAD])
-- The MySQL-compatitable encryption, only ecb, cbc, cfb1, cfb8, cfb128 and ofb modes are supported,
-- just like for MySQL
-- https://dev.mysql.com/doc/refman/8.0/en/encryption-functions.html#function_aes-encrypt
-- https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_block_encryption_mode
-- Please note that for keys that exceed mode-specific length, keys are folded in a MySQL-specific way,
-- meaning that whole key is used, but effective key length is still determined by mode.
-- when key doesn't exceed the default mode length, ecryption result equals with AES_encypt()
-----------------------------------------------------------------------------------------
-- error cases
-----------------------------------------------------------------------------------------
SELECT aes_encrypt_mysql(); --{serverError 42} not enough arguments
SELECT aes_encrypt_mysql('aes-128-ecb'); --{serverError 42} not enough arguments
SELECT aes_encrypt_mysql('aes-128-ecb', 'text'); --{serverError 42} not enough arguments
-- Mode
SELECT aes_encrypt_mysql(789, 'text', 'key'); --{serverError 43} bad mode type
SELECT aes_encrypt_mysql('blah blah blah', 'text', 'key'); -- {serverError 36} garbage mode value
SELECT aes_encrypt_mysql('des-ede3-ecb', 'text', 'key'); -- {serverError 36} bad mode value of valid cipher name
SELECT aes_encrypt_mysql('aes-128-gcm', 'text', 'key'); -- {serverError 36} mode is not supported by _mysql-functions
SELECT encrypt(789, 'text', 'key'); --{serverError 43} bad mode type
SELECT encrypt('blah blah blah', 'text', 'key'); -- {serverError 36} garbage mode value
SELECT encrypt('des-ede3-ecb', 'text', 'key'); -- {serverError 36} bad mode value of valid cipher name
-- Key
SELECT aes_encrypt_mysql('aes-128-ecb', 'text', 456); --{serverError 43} bad key type
SELECT aes_encrypt_mysql('aes-128-ecb', 'text', 'key'); -- {serverError 36} key is too short
SELECT encrypt('aes-128-ecb', 'text'); --{serverError 42} key is missing
SELECT encrypt('aes-128-ecb', 'text', 456); --{serverError 43} bad key type
SELECT encrypt('aes-128-ecb', 'text', 'key'); -- {serverError 36} key is too short
SELECT encrypt('aes-128-ecb', 'text', 'keykeykeykeykeykeykeykeykeykeykeykey'); -- {serverError 36} key is to long
-- IV
SELECT aes_encrypt_mysql('aes-128-ecb', 'text', 'key', 1011); --{serverError 43} bad IV type 6
SELECT aes_encrypt_mysql('aes-128-ecb', 'text', 'key', 'iv'); --{serverError 36} IV is too short 4
SELECT encrypt('aes-128-cbc', 'text', 'keykeykeykeykeyk', 1011); --{serverError 43} bad IV type 1
SELECT encrypt('aes-128-cbc', 'text', 'keykeykeykeykeyk', 'iviviviviviviviviviviviviviviviviviviviviv'); --{serverError 36} IV is too long 3
SELECT encrypt('aes-128-cbc', 'text', 'keykeykeykeykeyk', 'iv'); --{serverError 36} IV is too short 2
--AAD
SELECT aes_encrypt_mysql('aes-128-ecb', 'text', 'key', 'IV', 1213); --{serverError 42} too many arguments
SELECT encrypt('aes-128-ecb', 'text', 'key', 'IV', 1213); --{serverError 43} bad AAD type
SELECT encrypt('aes-128-gcm', 'text', 'key', 'IV', 1213); --{serverError 43} bad AAD type
-----------------------------------------------------------------------------------------
-- Valid cases
-----------------------------------------------------------------------------------------
SELECT 'UInt64', hex(aes_encrypt_mysql('aes-128-ecb', 123456789101112, 'keykeykeykeykeykeykeykeykeykeyke'));
SELECT 'Float64', hex(aes_encrypt_mysql('aes-128-ecb', 1234567891011.12, 'keykeykeykeykeykeykeykeykeykeyke'));
SELECT 'Decimal64', hex(aes_encrypt_mysql('aes-128-ecb', toDecimal64(1234567891011.12, 2), 'keykeykeykeykeykeykeykeykeykeyke'));
-----------------------------------------------------------------------------------------
-- Validate against predefined ciphertext,plaintext,key and IV for MySQL compatibility mode
-----------------------------------------------------------------------------------------
CREATE TABLE encryption_test
(
input String,
key String DEFAULT unhex('fb9958e2e897ef3fdb49067b51a24af645b3626eed2f9ea1dc7fd4dd71b7e38f9a68db2a3184f952382c783785f9d77bf923577108a88adaacae5c141b1576b0'),
iv String DEFAULT unhex('8CA3554377DFF8A369BC50A89780DD85'),
key32 String DEFAULT substring(key, 1, 32),
key24 String DEFAULT substring(key, 1, 24),
key16 String DEFAULT substring(key, 1, 16)
) Engine = Memory;
INSERT INTO encryption_test (input)
VALUES (''), ('text'), ('What Is ClickHouse? ClickHouse is a column-oriented database management system (DBMS) for online analytical processing of queries (OLAP).');
SELECT 'MySQL-compatitable mode, with key folding, no length checks, etc.';
SELECT 'aes-128-cbc' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-192-cbc' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-256-cbc' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-cfb1' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-192-cfb1' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-256-cfb1' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-cfb8' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-192-cfb8' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-256-cfb8' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-cfb128' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-192-cfb128' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-256-cfb128' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-ecb' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-192-ecb' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-256-ecb' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-ofb' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-192-ofb' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-256-ofb' as mode, hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test;
SELECT 'Strict mode without key folding and proper key and iv lengths checks.';
SELECT 'aes-128-cbc' as mode, hex(encrypt(mode, input, key16, iv)) FROM encryption_test;
SELECT 'aes-192-cbc' as mode, hex(encrypt(mode, input, key24, iv)) FROM encryption_test;
SELECT 'aes-256-cbc' as mode, hex(encrypt(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-cfb1' as mode, hex(encrypt(mode, input, key16, iv)) FROM encryption_test;
SELECT 'aes-192-cfb1' as mode, hex(encrypt(mode, input, key24, iv)) FROM encryption_test;
SELECT 'aes-256-cfb1' as mode, hex(encrypt(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-cfb8' as mode, hex(encrypt(mode, input, key16, iv)) FROM encryption_test;
SELECT 'aes-192-cfb8' as mode, hex(encrypt(mode, input, key24, iv)) FROM encryption_test;
SELECT 'aes-256-cfb8' as mode, hex(encrypt(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-cfb128' as mode, hex(encrypt(mode, input, key16, iv)) FROM encryption_test;
SELECT 'aes-192-cfb128' as mode, hex(encrypt(mode, input, key24, iv)) FROM encryption_test;
SELECT 'aes-256-cfb128' as mode, hex(encrypt(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-ctr' as mode, hex(encrypt(mode, input, key16, iv)) FROM encryption_test;
SELECT 'aes-192-ctr' as mode, hex(encrypt(mode, input, key24, iv)) FROM encryption_test;
SELECT 'aes-256-ctr' as mode, hex(encrypt(mode, input, key32, iv)) FROM encryption_test;
SELECT 'aes-128-ecb' as mode, hex(encrypt(mode, input, key16)) FROM encryption_test;
SELECT 'aes-192-ecb' as mode, hex(encrypt(mode, input, key24)) FROM encryption_test;
SELECT 'aes-256-ecb' as mode, hex(encrypt(mode, input, key32)) FROM encryption_test;
SELECT 'aes-128-ofb' as mode, hex(encrypt(mode, input, key16, iv)) FROM encryption_test;
SELECT 'aes-192-ofb' as mode, hex(encrypt(mode, input, key24, iv)) FROM encryption_test;
SELECT 'aes-256-ofb' as mode, hex(encrypt(mode, input, key32, iv)) FROM encryption_test;
SELECT 'GCM mode with IV';
SELECT 'aes-128-gcm' as mode, hex(encrypt(mode, input, key16, iv)) FROM encryption_test;
SELECT 'aes-192-gcm' as mode, hex(encrypt(mode, input, key24, iv)) FROM encryption_test;
SELECT 'aes-256-gcm' as mode, hex(encrypt(mode, input, key32, iv)) FROM encryption_test;
SELECT 'GCM mode with IV and AAD';
SELECT 'aes-128-gcm' as mode, hex(encrypt(mode, input, key16, iv, 'AAD')) FROM encryption_test;
SELECT 'aes-192-gcm' as mode, hex(encrypt(mode, input, key24, iv, 'AAD')) FROM encryption_test;
SELECT 'aes-256-gcm' as mode, hex(encrypt(mode, input, key32, iv, 'AAD')) FROM encryption_test;
SELECT 'Nullable and LowCardinality';
WITH CAST(NULL as Nullable(String)) as input, 'aes-256-ofb' as mode SELECT toTypeName(input), hex(aes_encrypt_mysql(mode, input, key32,iv)) FROM encryption_test LIMIT 1;
WITH CAST('text' as Nullable(String)) as input, 'aes-256-ofb' as mode SELECT toTypeName(input), hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test LIMIT 1;
WITH CAST('text' as LowCardinality(String)) as input, 'aes-256-ofb' as mode SELECT toTypeName(input), hex(aes_encrypt_mysql(mode, input, key32, iv)) FROM encryption_test LIMIT 1;
-- based on https://github.com/openssl/openssl/blob/master/demos/evp/aesgcm.c#L20
WITH
unhex('eebc1f57487f51921c0465665f8ae6d1658bb26de6f8a069a3520293a572078f') as key,
unhex('67ba0510262ae487d737ee6298f77e0c') as tag,
unhex('99aa3e68ed8173a0eed06684') as iv,
unhex('f56e87055bc32d0eeb31b2eacc2bf2a5') as plaintext,
unhex('4d23c3cec334b49bdb370c437fec78de') as aad,
unhex('f7264413a84c0e7cd536867eb9f21736') as ciphertext
SELECT
hex(encrypt('aes-256-gcm', plaintext, key, iv, aad)) as ciphertext_actual,
ciphertext_actual = concat(hex(ciphertext), hex(tag));

View File

@ -0,0 +1,6 @@
<yandex>
<timezone>Europe/Moscow</timezone>
<listen_host replace="replace">0.0.0.0</listen_host>
<path>/var/lib/clickhouse/</path>
<tmp_path>/var/lib/clickhouse/tmp/</tmp_path>
</yandex>

View File

@ -0,0 +1,17 @@
<yandex>
<shutdown_wait_unfinished>3</shutdown_wait_unfinished>
<logger>
<level>trace</level>
<log>/var/log/clickhouse-server/log.log</log>
<errorlog>/var/log/clickhouse-server/log.err.log</errorlog>
<size>1000M</size>
<count>10</count>
<stderr>/var/log/clickhouse-server/stderr.log</stderr>
<stdout>/var/log/clickhouse-server/stdout.log</stdout>
</logger>
<part_log>
<database>system</database>
<table>part_log</table>
<flush_interval_milliseconds>500</flush_interval_milliseconds>
</part_log>
</yandex>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<yandex>
<https_port>8443</https_port>
<tcp_port_secure>9440</tcp_port_secure>
</yandex>

View File

@ -0,0 +1,107 @@
<?xml version="1.0"?>
<yandex>
<remote_servers>
<replicated_cluster>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>clickhouse1</host>
<port>9000</port>
</replica>
<replica>
<host>clickhouse2</host>
<port>9000</port>
</replica>
<replica>
<host>clickhouse3</host>
<port>9000</port>
</replica>
</shard>
</replicated_cluster>
<!--
<replicated_cluster_readonly>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>clickhouse1</host>
<port>9000</port>
<user>readonly</user>
</replica>
<replica>
<host>clickhouse2</host>
<port>9000</port>
<user>readonly</user>
</replica>
<replica>
<host>clickhouse3</host>
<port>9000</port>
<user>readonly</user>
</replica>
</shard>
</replicated_cluster_readonly>
-->
<replicated_cluster_secure>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>clickhouse1</host>
<port>9440</port>
<secure>1</secure>
</replica>
<replica>
<host>clickhouse2</host>
<port>9440</port>
<secure>1</secure>
</replica>
<replica>
<host>clickhouse3</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
</replicated_cluster_secure>
<sharded_cluster>
<shard>
<replica>
<host>clickhouse1</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse2</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse3</host>
<port>9000</port>
</replica>
</shard>
</sharded_cluster>
<sharded_cluster_secure>
<shard>
<replica>
<host>clickhouse1</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse2</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse3</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
</sharded_cluster_secure>
</remote_servers>
</yandex>

View File

@ -0,0 +1,17 @@
<yandex>
<openSSL>
<server>
<certificateFile>/etc/clickhouse-server/ssl/server.crt</certificateFile>
<privateKeyFile>/etc/clickhouse-server/ssl/server.key</privateKeyFile>
<verificationMode>none</verificationMode>
<cacheSessions>true</cacheSessions>
</server>
<client>
<cacheSessions>true</cacheSessions>
<verificationMode>none</verificationMode>
<invalidCertificateHandler>
<name>AcceptCertificateHandler</name>
</invalidCertificateHandler>
</client>
</openSSL>
</yandex>

View File

@ -0,0 +1,20 @@
<yandex>
<storage_configuration>
<disks>
<default>
<keep_free_space_bytes>1024</keep_free_space_bytes>
</default>
</disks>
<policies>
<default>
<volumes>
<default>
<disk>default</disk>
</default>
</volumes>
</default>
</policies>
</storage_configuration>
</yandex>

View File

@ -0,0 +1,10 @@
<?xml version="1.0"?>
<yandex>
<zookeeper>
<node index="1">
<host>zookeeper</host>
<port>2181</port>
</node>
<session_timeout_ms>15000</session_timeout_ms>
</zookeeper>
</yandex>

View File

@ -0,0 +1,436 @@
<?xml version="1.0"?>
<!--
NOTE: User and query level settings are set up in "users.xml" file.
-->
<yandex>
<logger>
<!-- Possible levels: https://github.com/pocoproject/poco/blob/develop/Foundation/include/Poco/Logger.h#L105 -->
<level>trace</level>
<log>/var/log/clickhouse-server/clickhouse-server.log</log>
<errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
<size>1000M</size>
<count>10</count>
<!-- <console>1</console> --> <!-- Default behavior is autodetection (log to console if not daemon mode and is tty) -->
</logger>
<!--display_name>production</display_name--> <!-- It is the name that will be shown in the client -->
<http_port>8123</http_port>
<tcp_port>9000</tcp_port>
<!-- For HTTPS and SSL over native protocol. -->
<!--
<https_port>8443</https_port>
<tcp_port_secure>9440</tcp_port_secure>
-->
<!-- Used with https_port and tcp_port_secure. Full ssl options list: https://github.com/ClickHouse-Extras/poco/blob/master/NetSSL_OpenSSL/include/Poco/Net/SSLManager.h#L71 -->
<openSSL>
<server> <!-- Used for https server AND secure tcp port -->
<!-- openssl req -subj "/CN=localhost" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /etc/clickhouse-server/server.key -out /etc/clickhouse-server/server.crt -->
<certificateFile>/etc/clickhouse-server/server.crt</certificateFile>
<privateKeyFile>/etc/clickhouse-server/server.key</privateKeyFile>
<!-- openssl dhparam -out /etc/clickhouse-server/dhparam.pem 4096 -->
<dhParamsFile>/etc/clickhouse-server/dhparam.pem</dhParamsFile>
<verificationMode>none</verificationMode>
<loadDefaultCAFile>true</loadDefaultCAFile>
<cacheSessions>true</cacheSessions>
<disableProtocols>sslv2,sslv3</disableProtocols>
<preferServerCiphers>true</preferServerCiphers>
</server>
<client> <!-- Used for connecting to https dictionary source -->
<loadDefaultCAFile>true</loadDefaultCAFile>
<cacheSessions>true</cacheSessions>
<disableProtocols>sslv2,sslv3</disableProtocols>
<preferServerCiphers>true</preferServerCiphers>
<!-- Use for self-signed: <verificationMode>none</verificationMode> -->
<invalidCertificateHandler>
<!-- Use for self-signed: <name>AcceptCertificateHandler</name> -->
<name>RejectCertificateHandler</name>
</invalidCertificateHandler>
</client>
</openSSL>
<!-- Default root page on http[s] server. For example load UI from https://tabix.io/ when opening http://localhost:8123 -->
<!--
<http_server_default_response><![CDATA[<html ng-app="SMI2"><head><base href="http://ui.tabix.io/"></head><body><div ui-view="" class="content-ui"></div><script src="http://loader.tabix.io/master.js"></script></body></html>]]></http_server_default_response>
-->
<!-- Port for communication between replicas. Used for data exchange. -->
<interserver_http_port>9009</interserver_http_port>
<!-- Hostname that is used by other replicas to request this server.
If not specified, than it is determined analoguous to 'hostname -f' command.
This setting could be used to switch replication to another network interface.
-->
<!--
<interserver_http_host>example.yandex.ru</interserver_http_host>
-->
<!-- Listen specified host. use :: (wildcard IPv6 address), if you want to accept connections both with IPv4 and IPv6 from everywhere. -->
<!-- <listen_host>::</listen_host> -->
<!-- Same for hosts with disabled ipv6: -->
<!-- <listen_host>0.0.0.0</listen_host> -->
<!-- Default values - try listen localhost on ipv4 and ipv6: -->
<!--
<listen_host>::1</listen_host>
<listen_host>127.0.0.1</listen_host>
-->
<!-- Don't exit if ipv6 or ipv4 unavailable, but listen_host with this protocol specified -->
<!-- <listen_try>0</listen_try> -->
<!-- Allow listen on same address:port -->
<!-- <listen_reuse_port>0</listen_reuse_port> -->
<!-- <listen_backlog>64</listen_backlog> -->
<max_connections>4096</max_connections>
<keep_alive_timeout>3</keep_alive_timeout>
<!-- Maximum number of concurrent queries. -->
<max_concurrent_queries>100</max_concurrent_queries>
<!-- Set limit on number of open files (default: maximum). This setting makes sense on Mac OS X because getrlimit() fails to retrieve
correct maximum value. -->
<!-- <max_open_files>262144</max_open_files> -->
<!-- Size of cache of uncompressed blocks of data, used in tables of MergeTree family.
In bytes. Cache is single for server. Memory is allocated only on demand.
Cache is used when 'use_uncompressed_cache' user setting turned on (off by default).
Uncompressed cache is advantageous only for very short queries and in rare cases.
-->
<uncompressed_cache_size>8589934592</uncompressed_cache_size>
<!-- Approximate size of mark cache, used in tables of MergeTree family.
In bytes. Cache is single for server. Memory is allocated only on demand.
You should not lower this value.
-->
<mark_cache_size>5368709120</mark_cache_size>
<!-- Path to data directory, with trailing slash. -->
<path>/var/lib/clickhouse/</path>
<!-- Path to temporary data for processing hard queries. -->
<tmp_path>/var/lib/clickhouse/tmp/</tmp_path>
<!-- Directory with user provided files that are accessible by 'file' table function. -->
<user_files_path>/var/lib/clickhouse/user_files/</user_files_path>
<!-- Path to folder where users and roles created by SQL commands are stored. -->
<access_control_path>/var/lib/clickhouse/access/</access_control_path>
<!-- Path to configuration file with users, access rights, profiles of settings, quotas. -->
<users_config>users.xml</users_config>
<!-- Default profile of settings. -->
<default_profile>default</default_profile>
<!-- System profile of settings. This settings are used by internal processes (Buffer storage, Distibuted DDL worker and so on). -->
<!-- <system_profile>default</system_profile> -->
<!-- Default database. -->
<default_database>default</default_database>
<!-- Server time zone could be set here.
Time zone is used when converting between String and DateTime types,
when printing DateTime in text formats and parsing DateTime from text,
it is used in date and time related functions, if specific time zone was not passed as an argument.
Time zone is specified as identifier from IANA time zone database, like UTC or Africa/Abidjan.
If not specified, system time zone at server startup is used.
Please note, that server could display time zone alias instead of specified name.
Example: W-SU is an alias for Europe/Moscow and Zulu is an alias for UTC.
-->
<!-- <timezone>Europe/Moscow</timezone> -->
<!-- You can specify umask here (see "man umask"). Server will apply it on startup.
Number is always parsed as octal. Default umask is 027 (other users cannot read logs, data files, etc; group can only read).
-->
<!-- <umask>022</umask> -->
<!-- Perform mlockall after startup to lower first queries latency
and to prevent clickhouse executable from being paged out under high IO load.
Enabling this option is recommended but will lead to increased startup time for up to a few seconds.
-->
<mlock_executable>false</mlock_executable>
<!-- Configuration of clusters that could be used in Distributed tables.
https://clickhouse.yandex/docs/en/table_engines/distributed/
-->
<remote_servers incl="remote" >
<!-- Test only shard config for testing distributed storage -->
<test_shard_localhost>
<shard>
<replica>
<host>localhost</host>
<port>9000</port>
</replica>
</shard>
</test_shard_localhost>
<test_cluster_two_shards_localhost>
<shard>
<replica>
<host>localhost</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>localhost</host>
<port>9000</port>
</replica>
</shard>
</test_cluster_two_shards_localhost>
<test_shard_localhost_secure>
<shard>
<replica>
<host>localhost</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
</test_shard_localhost_secure>
<test_unavailable_shard>
<shard>
<replica>
<host>localhost</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>localhost</host>
<port>1</port>
</replica>
</shard>
</test_unavailable_shard>
</remote_servers>
<!-- If element has 'incl' attribute, then for it's value will be used corresponding substitution from another file.
By default, path to file with substitutions is /etc/metrika.xml. It could be changed in config in 'include_from' element.
Values for substitutions are specified in /yandex/name_of_substitution elements in that file.
-->
<!-- ZooKeeper is used to store metadata about replicas, when using Replicated tables.
Optional. If you don't use replicated tables, you could omit that.
See https://clickhouse.yandex/docs/en/table_engines/replication/
-->
<zookeeper incl="zookeeper" optional="true" />
<!-- Substitutions for parameters of replicated tables.
Optional. If you don't use replicated tables, you could omit that.
See https://clickhouse.yandex/docs/en/table_engines/replication/#creating-replicated-tables
-->
<macros incl="macros" optional="true" />
<!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
<builtin_dictionaries_reload_interval>3600</builtin_dictionaries_reload_interval>
<!-- Maximum session timeout, in seconds. Default: 3600. -->
<max_session_timeout>3600</max_session_timeout>
<!-- Default session timeout, in seconds. Default: 60. -->
<default_session_timeout>60</default_session_timeout>
<!-- Sending data to Graphite for monitoring. Several sections can be defined. -->
<!--
interval - send every X second
root_path - prefix for keys
hostname_in_path - append hostname to root_path (default = true)
metrics - send data from table system.metrics
events - send data from table system.events
asynchronous_metrics - send data from table system.asynchronous_metrics
-->
<!--
<graphite>
<host>localhost</host>
<port>42000</port>
<timeout>0.1</timeout>
<interval>60</interval>
<root_path>one_min</root_path>
<hostname_in_path>true</hostname_in_path>
<metrics>true</metrics>
<events>true</events>
<asynchronous_metrics>true</asynchronous_metrics>
</graphite>
<graphite>
<host>localhost</host>
<port>42000</port>
<timeout>0.1</timeout>
<interval>1</interval>
<root_path>one_sec</root_path>
<metrics>true</metrics>
<events>true</events>
<asynchronous_metrics>false</asynchronous_metrics>
</graphite>
-->
<!-- Query log. Used only for queries with setting log_queries = 1. -->
<query_log>
<!-- What table to insert data. If table is not exist, it will be created.
When query log structure is changed after system update,
then old table will be renamed and new table will be created automatically.
-->
<database>system</database>
<table>query_log</table>
<!--
PARTITION BY expr https://clickhouse.yandex/docs/en/table_engines/custom_partitioning_key/
Example:
event_date
toMonday(event_date)
toYYYYMM(event_date)
toStartOfHour(event_time)
-->
<partition_by>toYYYYMM(event_date)</partition_by>
<!-- Interval of flushing data. -->
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</query_log>
<!-- Trace log. Stores stack traces collected by query profilers.
See query_profiler_real_time_period_ns and query_profiler_cpu_time_period_ns settings. -->
<trace_log>
<database>system</database>
<table>trace_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</trace_log>
<!-- Query thread log. Has information about all threads participated in query execution.
Used only for queries with setting log_query_threads = 1. -->
<query_thread_log>
<database>system</database>
<table>query_thread_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</query_thread_log>
<!-- Uncomment if use part log.
Part log contains information about all actions with parts in MergeTree tables (creation, deletion, merges, downloads).
<part_log>
<database>system</database>
<table>part_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</part_log>
-->
<!-- Uncomment to write text log into table.
Text log contains all information from usual server log but stores it in structured and efficient way.
<text_log>
<database>system</database>
<table>text_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</text_log>
-->
<!-- Parameters for embedded dictionaries, used in Yandex.Metrica.
See https://clickhouse.yandex/docs/en/dicts/internal_dicts/
-->
<!-- Path to file with region hierarchy. -->
<!-- <path_to_regions_hierarchy_file>/opt/geo/regions_hierarchy.txt</path_to_regions_hierarchy_file> -->
<!-- Path to directory with files containing names of regions -->
<!-- <path_to_regions_names_files>/opt/geo/</path_to_regions_names_files> -->
<!-- Configuration of external dictionaries. See:
https://clickhouse.yandex/docs/en/dicts/external_dicts/
-->
<dictionaries_config>*_dictionary.xml</dictionaries_config>
<!-- Uncomment if you want data to be compressed 30-100% better.
Don't do that if you just started using ClickHouse.
-->
<compression incl="compression">
<!--
<!- - Set of variants. Checked in order. Last matching case wins. If nothing matches, lz4 will be used. - ->
<case>
<!- - Conditions. All must be satisfied. Some conditions may be omitted. - ->
<min_part_size>10000000000</min_part_size> <!- - Min part size in bytes. - ->
<min_part_size_ratio>0.01</min_part_size_ratio> <!- - Min size of part relative to whole table size. - ->
<!- - What compression method to use. - ->
<method>zstd</method>
</case>
-->
</compression>
<!-- Allow to execute distributed DDL queries (CREATE, DROP, ALTER, RENAME) on cluster.
Works only if ZooKeeper is enabled. Comment it if such functionality isn't required. -->
<distributed_ddl>
<!-- Path in ZooKeeper to queue with DDL queries -->
<path>/clickhouse/task_queue/ddl</path>
<!-- Settings from this profile will be used to execute DDL queries -->
<!-- <profile>default</profile> -->
</distributed_ddl>
<!-- Settings to fine tune MergeTree tables. See documentation in source code, in MergeTreeSettings.h -->
<!--
<merge_tree>
<max_suspicious_broken_parts>5</max_suspicious_broken_parts>
</merge_tree>
-->
<!-- Protection from accidental DROP.
If size of a MergeTree table is greater than max_table_size_to_drop (in bytes) than table could not be dropped with any DROP query.
If you want do delete one table and don't want to restart clickhouse-server, you could create special file <clickhouse-path>/flags/force_drop_table and make DROP once.
By default max_table_size_to_drop is 50GB; max_table_size_to_drop=0 allows to DROP any tables.
The same for max_partition_size_to_drop.
Uncomment to disable protection.
-->
<!-- <max_table_size_to_drop>0</max_table_size_to_drop> -->
<!-- <max_partition_size_to_drop>0</max_partition_size_to_drop> -->
<!-- Example of parameters for GraphiteMergeTree table engine -->
<graphite_rollup_example>
<pattern>
<regexp>click_cost</regexp>
<function>any</function>
<retention>
<age>0</age>
<precision>3600</precision>
</retention>
<retention>
<age>86400</age>
<precision>60</precision>
</retention>
</pattern>
<default>
<function>max</function>
<retention>
<age>0</age>
<precision>60</precision>
</retention>
<retention>
<age>3600</age>
<precision>300</precision>
</retention>
<retention>
<age>86400</age>
<precision>3600</precision>
</retention>
</default>
</graphite_rollup_example>
<!-- Directory in <clickhouse-path> containing schema files for various input formats.
The directory will be created if it doesn't exist.
-->
<format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
<!-- Uncomment to disable ClickHouse internal DNS caching. -->
<!-- <disable_internal_dns_cache>1</disable_internal_dns_cache> -->
</yandex>

View File

@ -0,0 +1,8 @@
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEAua92DDli13gJ+//ZXyGaggjIuidqB0crXfhUlsrBk9BV1hH3i7fR
XGP9rUdk2ubnB3k2ejBStL5oBrkHm9SzUFSQHqfDjLZjKoUpOEmuDc4cHvX1XTR5
Pr1vf5cd0yEncJWG5W4zyUB8k++SUdL2qaeslSs+f491HBLDYn/h8zCgRbBvxhxb
9qeho1xcbnWeqkN6Kc9bgGozA16P9NLuuLttNnOblkH+lMBf42BSne/TWt3AlGZf
slKmmZcySUhF8aKfJnLKbkBCFqOtFRh8zBA9a7g+BT/lSANATCDPaAk1YVih2EKb
dpc3briTDbRsiqg2JKMI7+VdULY9bh3EawIBAg==
-----END DH PARAMETERS-----

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIC/TCCAeWgAwIBAgIJANjx1QSR77HBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDAgFw0xODA3MzAxODE2MDhaGA8yMjkyMDUxNDE4MTYwOFow
FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAs9uSo6lJG8o8pw0fbVGVu0tPOljSWcVSXH9uiJBwlZLQnhN4SFSFohfI
4K8U1tBDTnxPLUo/V1K9yzoLiRDGMkwVj6+4+hE2udS2ePTQv5oaMeJ9wrs+5c9T
4pOtlq3pLAdm04ZMB1nbrEysceVudHRkQbGHzHp6VG29Fw7Ga6YpqyHQihRmEkTU
7UCYNA+Vk7aDPdMS/khweyTpXYZimaK9f0ECU3/VOeG3fH6Sp2X6FN4tUj/aFXEj
sRmU5G2TlYiSIUMF2JPdhSihfk1hJVALrHPTU38SOL+GyyBRWdNcrIwVwbpvsvPg
pryMSNxnpr0AK0dFhjwnupIv5hJIOQIDAQABo1AwTjAdBgNVHQ4EFgQUjPLb3uYC
kcamyZHK4/EV8jAP0wQwHwYDVR0jBBgwFoAUjPLb3uYCkcamyZHK4/EV8jAP0wQw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAM/ocuDvfPus/KpMVD51j
4IdlU8R0vmnYLQ+ygzOAo7+hUWP5j0yvq4ILWNmQX6HNvUggCgFv9bjwDFhb/5Vr
85ieWfTd9+LTjrOzTw4avdGwpX9G+6jJJSSq15tw5ElOIFb/qNA9O4dBiu8vn03C
L/zRSXrARhSqTW5w/tZkUcSTT+M5h28+Lgn9ysx4Ff5vi44LJ1NnrbJbEAIYsAAD
+UA+4MBFKx1r6hHINULev8+lCfkpwIaeS8RL+op4fr6kQPxnULw8wT8gkuc8I4+L
P9gg/xDHB44T3ADGZ5Ib6O0DJaNiToO6rnoaaxs0KkotbvDWvRoxEytSbXKoYjYp
0g==
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCz25KjqUkbyjyn
DR9tUZW7S086WNJZxVJcf26IkHCVktCeE3hIVIWiF8jgrxTW0ENOfE8tSj9XUr3L
OguJEMYyTBWPr7j6ETa51LZ49NC/mhox4n3Cuz7lz1Pik62WreksB2bThkwHWdus
TKxx5W50dGRBsYfMenpUbb0XDsZrpimrIdCKFGYSRNTtQJg0D5WTtoM90xL+SHB7
JOldhmKZor1/QQJTf9U54bd8fpKnZfoU3i1SP9oVcSOxGZTkbZOViJIhQwXYk92F
KKF+TWElUAusc9NTfxI4v4bLIFFZ01ysjBXBum+y8+CmvIxI3GemvQArR0WGPCe6
ki/mEkg5AgMBAAECggEATrbIBIxwDJOD2/BoUqWkDCY3dGevF8697vFuZKIiQ7PP
TX9j4vPq0DfsmDjHvAPFkTHiTQXzlroFik3LAp+uvhCCVzImmHq0IrwvZ9xtB43f
7Pkc5P6h1l3Ybo8HJ6zRIY3TuLtLxuPSuiOMTQSGRL0zq3SQ5DKuGwkz+kVjHXUN
MR2TECFwMHKQ5VLrC+7PMpsJYyOMlDAWhRfUalxC55xOXTpaN8TxNnwQ8K2ISVY5
212Jz/a4hn4LdwxSz3Tiu95PN072K87HLWx3EdT6vW4Ge5P/A3y+smIuNAlanMnu
plHBRtpATLiTxZt/n6npyrfQVbYjSH7KWhB8hBHtaQKBgQDh9Cq1c/KtqDtE0Ccr
/r9tZNTUwBE6VP+3OJeKdEdtsfuxjOCkS1oAjgBJiSDOiWPh1DdoDeVZjPKq6pIu
Mq12OE3Doa8znfCXGbkSzEKOb2unKZMJxzrz99kXt40W5DtrqKPNb24CNqTiY8Aa
CjtcX+3weat82VRXvph6U8ltMwKBgQDLxjiQQzNoY7qvg7CwJCjf9qq8jmLK766g
1FHXopqS+dTxDLM8eJSRrpmxGWJvNeNc1uPhsKsKgotqAMdBUQTf7rSTbt4MyoH5
bUcRLtr+0QTK9hDWMOOvleqNXha68vATkohWYfCueNsC60qD44o8RZAS6UNy3ENq
cM1cxqe84wKBgQDKkHutWnooJtajlTxY27O/nZKT/HA1bDgniMuKaz4R4Gr1PIez
on3YW3V0d0P7BP6PWRIm7bY79vkiMtLEKdiKUGWeyZdo3eHvhDb/3DCawtau8L2K
GZsHVp2//mS1Lfz7Qh8/L/NedqCQ+L4iWiPnZ3THjjwn3CoZ05ucpvrAMwKBgB54
nay039MUVq44Owub3KDg+dcIU62U+cAC/9oG7qZbxYPmKkc4oL7IJSNecGHA5SbU
2268RFdl/gLz6tfRjbEOuOHzCjFPdvAdbysanpTMHLNc6FefJ+zxtgk9sJh0C4Jh
vxFrw9nTKKzfEl12gQ1SOaEaUIO0fEBGbe8ZpauRAoGAMAlGV+2/K4ebvAJKOVTa
dKAzQ+TD2SJmeR1HZmKDYddNqwtZlzg3v4ZhCk4eaUmGeC1Bdh8MDuB3QQvXz4Dr
vOIP4UVaOr+uM+7TgAgVnP4/K6IeJGzUDhX93pmpWhODfdu/oojEKVcpCojmEmS1
KCBtmIrQLqzMpnBpLNuSY+Q=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,133 @@
<?xml version="1.0"?>
<yandex>
<!-- Profiles of settings. -->
<profiles>
<!-- Default settings. -->
<default>
<!-- Maximum memory usage for processing single query, in bytes. -->
<max_memory_usage>10000000000</max_memory_usage>
<!-- Use cache of uncompressed blocks of data. Meaningful only for processing many of very short queries. -->
<use_uncompressed_cache>0</use_uncompressed_cache>
<!-- How to choose between replicas during distributed query processing.
random - choose random replica from set of replicas with minimum number of errors
nearest_hostname - from set of replicas with minimum number of errors, choose replica
with minimum number of different symbols between replica's hostname and local hostname
(Hamming distance).
in_order - first live replica is chosen in specified order.
first_or_random - if first replica one has higher number of errors, pick a random one from replicas with minimum number of errors.
-->
<load_balancing>random</load_balancing>
</default>
<!-- Profile that allows only read queries. -->
<readonly>
<readonly>1</readonly>
</readonly>
</profiles>
<!-- Users and ACL. -->
<users>
<!-- If user name was not specified, 'default' user is used. -->
<default>
<!-- Password could be specified in plaintext or in SHA256 (in hex format).
If you want to specify password in plaintext (not recommended), place it in 'password' element.
Example: <password>qwerty</password>.
Password could be empty.
If you want to specify SHA256, place it in 'password_sha256_hex' element.
Example: <password_sha256_hex>65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5</password_sha256_hex>
Restrictions of SHA256: impossibility to connect to ClickHouse using MySQL JS client (as of July 2019).
If you want to specify double SHA1, place it in 'password_double_sha1_hex' element.
Example: <password_double_sha1_hex>e395796d6546b1b65db9d665cd43f0e858dd4303</password_double_sha1_hex>
How to generate decent password:
Execute: PASSWORD=$(base64 < /dev/urandom | head -c8); echo "$PASSWORD"; echo -n "$PASSWORD" | sha256sum | tr -d '-'
In first line will be password and in second - corresponding SHA256.
How to generate double SHA1:
Execute: PASSWORD=$(base64 < /dev/urandom | head -c8); echo "$PASSWORD"; echo -n "$PASSWORD" | openssl dgst -sha1 -binary | openssl dgst -sha1
In first line will be password and in second - corresponding double SHA1.
-->
<password></password>
<!-- List of networks with open access.
To open access from everywhere, specify:
<ip>::/0</ip>
To open access only from localhost, specify:
<ip>::1</ip>
<ip>127.0.0.1</ip>
Each element of list has one of the following forms:
<ip> IP-address or network mask. Examples: 213.180.204.3 or 10.0.0.1/8 or 10.0.0.1/255.255.255.0
2a02:6b8::3 or 2a02:6b8::3/64 or 2a02:6b8::3/ffff:ffff:ffff:ffff::.
<host> Hostname. Example: server01.yandex.ru.
To check access, DNS query is performed, and all received addresses compared to peer address.
<host_regexp> Regular expression for host names. Example, ^server\d\d-\d\d-\d\.yandex\.ru$
To check access, DNS PTR query is performed for peer address and then regexp is applied.
Then, for result of PTR query, another DNS query is performed and all received addresses compared to peer address.
Strongly recommended that regexp is ends with $
All results of DNS requests are cached till server restart.
-->
<networks incl="networks" replace="replace">
<ip>::/0</ip>
</networks>
<!-- Settings profile for user. -->
<profile>default</profile>
<!-- Quota for user. -->
<quota>default</quota>
<!-- Allow access management -->
<access_management>1</access_management>
<!-- Example of row level security policy. -->
<!-- <databases>
<test>
<filtered_table1>
<filter>a = 1</filter>
</filtered_table1>
<filtered_table2>
<filter>a + b &lt; 1 or c - d &gt; 5</filter>
</filtered_table2>
</test>
</databases> -->
</default>
<!-- Example of user with readonly access. -->
<!-- <readonly>
<password></password>
<networks incl="networks" replace="replace">
<ip>::1</ip>
<ip>127.0.0.1</ip>
</networks>
<profile>readonly</profile>
<quota>default</quota>
</readonly> -->
</users>
<!-- Quotas. -->
<quotas>
<!-- Name of quota. -->
<default>
<!-- Limits for time interval. You could specify many intervals with different limits. -->
<interval>
<!-- Length of interval. -->
<duration>3600</duration>
<!-- No limits. Just calculate resource usage for time interval. -->
<queries>0</queries>
<errors>0</errors>
<result_rows>0</result_rows>
<read_rows>0</read_rows>
<execution_time>0</execution_time>
</interval>
</default>
</quotas>
</yandex>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<yandex>
<macros>
<replica>clickhouse1</replica>
<shard>01</shard>
<shard2>01</shard2>
</macros>
</yandex>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<yandex>
<macros>
<replica>clickhouse2</replica>
<shard>01</shard>
<shard2>02</shard2>
</macros>
</yandex>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<yandex>
<macros>
<replica>clickhouse3</replica>
<shard>01</shard>
<shard2>03</shard2>
</macros>
</yandex>

View File

@ -0,0 +1,28 @@
version: '2.3'
services:
clickhouse:
image: yandex/clickhouse-integration-test
expose:
- "9000"
- "9009"
- "8123"
volumes:
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/config.d:/etc/clickhouse-server/config.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/users.d/:/etc/clickhouse-server/users.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/ssl:/etc/clickhouse-server/ssl"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/config.xml:/etc/clickhouse-server/config.xml"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/users.xml:/etc/clickhouse-server/users.xml"
- "${CLICKHOUSE_TESTS_SERVER_BIN_PATH:-/usr/bin/clickhouse}:/usr/bin/clickhouse"
- "${CLICKHOUSE_TESTS_ODBC_BRIDGE_BIN_PATH:-/usr/bin/clickhouse-odbc-bridge}:/usr/bin/clickhouse-odbc-bridge"
entrypoint: bash -c "clickhouse server --config-file=/etc/clickhouse-server/config.xml --log-file=/var/log/clickhouse-server/clickhouse-server.log --errorlog-file=/var/log/clickhouse-server/clickhouse-server.err.log"
healthcheck:
test: clickhouse client --query='select 1'
interval: 3s
timeout: 2s
retries: 40
start_period: 2s
cap_add:
- SYS_PTRACE
security_opt:
- label:disable

View File

@ -0,0 +1,73 @@
version: '2.3'
services:
zookeeper:
extends:
file: zookeeper-service.yml
service: zookeeper
mysql1:
extends:
file: mysql-service.yml
service: mysql
hostname: mysql1
volumes:
- "${CLICKHOUSE_TESTS_DIR}/_instances/mysql1/database:/var/lib/mysql"
clickhouse1:
extends:
file: clickhouse-service.yml
service: clickhouse
hostname: clickhouse1
volumes:
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse1/database/:/var/lib/clickhouse/"
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse1/logs/:/var/log/clickhouse-server/"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse1/config.d:/etc/clickhouse-server/config.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse1/users.d:/etc/clickhouse-server/users.d"
depends_on:
zookeeper:
condition: service_healthy
clickhouse2:
extends:
file: clickhouse-service.yml
service: clickhouse
hostname: clickhouse2
volumes:
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse2/database/:/var/lib/clickhouse/"
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse2/logs/:/var/log/clickhouse-server/"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse2/config.d:/etc/clickhouse-server/config.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse2/users.d:/etc/clickhouse-server/users.d"
depends_on:
zookeeper:
condition: service_healthy
clickhouse3:
extends:
file: clickhouse-service.yml
service: clickhouse
hostname: clickhouse3
volumes:
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse3/database/:/var/lib/clickhouse/"
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse3/logs/:/var/log/clickhouse-server/"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse3/config.d:/etc/clickhouse-server/config.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse3/users.d:/etc/clickhouse-server/users.d"
depends_on:
zookeeper:
condition: service_healthy
# dummy service which does nothing, but allows to postpone
# 'docker-compose up -d' till all dependecies will go healthy
all_services_ready:
image: hello-world
depends_on:
mysql1:
condition: service_healthy
clickhouse1:
condition: service_healthy
clickhouse2:
condition: service_healthy
clickhouse3:
condition: service_healthy
zookeeper:
condition: service_healthy

View File

@ -0,0 +1,19 @@
version: '2.3'
services:
mysql:
image: mysql:5.7.30
restart: always
environment:
MYSQL_DATABASE: 'db'
MYSQL_USER: 'user'
MYSQL_PASSWORD: 'password'
MYSQL_ROOT_PASSWORD: 'password'
expose:
- '3306'
healthcheck:
test: mysql -D db -u user --password=password -e "select 1;"
interval: 3s
timeout: 2s
retries: 40
start_period: 2s

View File

@ -0,0 +1,18 @@
version: '2.3'
services:
zookeeper:
image: zookeeper:3.4.12
expose:
- "2181"
environment:
ZOO_TICK_TIME: 500
ZOO_MY_ID: 1
healthcheck:
test: echo stat | nc localhost 2181
interval: 3s
timeout: 2s
retries: 5
start_period: 2s
security_opt:
- label:disable

View File

@ -0,0 +1,73 @@
#!/usr/bin/env python3
import sys
from testflows.core import *
append_path(sys.path, "..")
from helpers.cluster import Cluster
from helpers.argparser import argparser
from aes_encryption.requirements import *
xfails = {
# encrypt
"encrypt/invalid key or iv length for mode/mode=\"'aes-???-gcm'\", key_len=??, iv_len=12, aad=True/iv is too short":
[(Fail, "known issue")],
"encrypt/invalid key or iv length for mode/mode=\"'aes-???-gcm'\", key_len=??, iv_len=12, aad=True/iv is too long":
[(Fail, "known issue")],
# encrypt_mysql
"encrypt_mysql/key or iv length for mode/mode=\"'aes-???-ecb'\", key_len=??, iv_len=None":
[(Fail, "https://altinity.atlassian.net/browse/CH-190")],
"encrypt_mysql/invalid parameters/iv not valid for mode":
[(Fail, "https://altinity.atlassian.net/browse/CH-190")],
"encrypt_mysql/invalid parameters/no parameters":
[(Fail, "https://altinity.atlassian.net/browse/CH-191")],
# decrypt_mysql
"decrypt_mysql/key or iv length for mode/mode=\"'aes-???-ecb'\", key_len=??, iv_len=None:":
[(Fail, "https://altinity.atlassian.net/browse/CH-190")],
# compatibility
"compatibility/insert/encrypt using materialized view/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/insert/decrypt using materialized view/:":
[(Error, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/insert/aes encrypt mysql using materialized view/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/insert/aes decrypt mysql using materialized view/:":
[(Error, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/select/decrypt unique":
[(Fail, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/mysql/:engine/decrypt/mysql_datatype='TEXT'/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-194")],
"compatibility/mysql/:engine/decrypt/mysql_datatype='VARCHAR(100)'/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-194")],
"compatibility/mysql/:engine/encrypt/mysql_datatype='TEXT'/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-194")],
"compatibility/mysql/:engine/encrypt/mysql_datatype='VARCHAR(100)'/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-194")]
}
@TestFeature
@Name("aes encryption")
@ArgumentParser(argparser)
@Requirements(
RQ_SRS008_AES_Functions("1.0"),
RQ_SRS008_AES_Functions_DifferentModes("1.0")
)
@XFails(xfails)
def regression(self, local, clickhouse_binary_path):
"""ClickHouse AES encryption functions regression module.
"""
nodes = {
"clickhouse": ("clickhouse1", "clickhouse2", "clickhouse3"),
}
with Cluster(local, clickhouse_binary_path, nodes=nodes) as cluster:
self.context.cluster = cluster
Feature(run=load("aes_encryption.tests.encrypt", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.decrypt", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.encrypt_mysql", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.decrypt_mysql", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.feature", "feature"), flags=TE)
if main():
regression()

View File

@ -0,0 +1 @@
from .requirements import *

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
modes = [
# mode, key_len, iv_len, aad
("'aes-128-ecb'", 16, None, None),
("'aes-192-ecb'", 24, None, None),
("'aes-256-ecb'", 32, None, None),
# cbc
("'aes-128-cbc'", 16, None, None),
("'aes-192-cbc'", 24, None, None),
("'aes-256-cbc'", 32, None, None),
("'aes-128-cbc'", 16, 16, None),
("'aes-192-cbc'", 24, 16, None),
("'aes-256-cbc'", 32, 16, None),
# cfb1
("'aes-128-cfb1'", 16, None, None),
("'aes-192-cfb1'", 24, None, None),
("'aes-256-cfb1'", 32, None, None),
("'aes-128-cfb1'", 16, 16, None),
("'aes-192-cfb1'", 24, 16, None),
("'aes-256-cfb1'", 32, 16, None),
# cfb8
("'aes-128-cfb8'", 16, None, None),
("'aes-192-cfb8'", 24, None, None),
("'aes-256-cfb8'", 32, None, None),
("'aes-128-cfb8'", 16, 16, None),
("'aes-192-cfb8'", 24, 16, None),
("'aes-256-cfb8'", 32, 16, None),
# cfb128
("'aes-128-cfb128'", 16, None, None),
("'aes-192-cfb128'", 24, None, None),
("'aes-256-cfb128'", 32, None, None),
("'aes-128-cfb128'", 16, 16, None),
("'aes-192-cfb128'", 24, 16, None),
("'aes-256-cfb128'", 32, 16, None),
# ofb
("'aes-128-ofb'", 16, None, None),
("'aes-192-ofb'", 24, None, None),
("'aes-256-ofb'", 32, None, None),
("'aes-128-ofb'", 16, 16, None),
("'aes-192-ofb'", 24, 16, None),
("'aes-256-ofb'", 32, 16, None),
# gcm
("'aes-128-gcm'", 16, 12, None),
("'aes-192-gcm'", 24, 12, None),
("'aes-256-gcm'", 32, 12, None),
("'aes-128-gcm'", 16, 12, True),
("'aes-192-gcm'", 24, 12, True),
("'aes-256-gcm'", 32, 12, True),
# ctr
("'aes-128-ctr'", 16, None, None),
("'aes-192-ctr'", 24, None, None),
("'aes-256-ctr'", 32, None, None),
("'aes-128-ctr'", 16, 16, None),
("'aes-192-ctr'", 24, 16, None),
("'aes-256-ctr'", 32, 16, None),
]
mysql_modes = [
# mode, key_len, iv_len
("'aes-128-ecb'", 16, None),
("'aes-128-ecb'", 24, None),
("'aes-192-ecb'", 24, None),
("'aes-192-ecb'", 32, None),
("'aes-256-ecb'", 32, None),
("'aes-256-ecb'", 64, None),
# cbc
("'aes-128-cbc'", 16, None),
("'aes-192-cbc'", 24, None),
("'aes-256-cbc'", 32, None),
("'aes-128-cbc'", 16, 16),
("'aes-128-cbc'", 24, 24),
("'aes-192-cbc'", 24, 16),
("'aes-192-cbc'", 32, 32),
("'aes-256-cbc'", 32, 16),
("'aes-256-cbc'", 64, 64),
# cfb1
("'aes-128-cfb1'", 16, None),
("'aes-192-cfb1'", 24, None),
("'aes-256-cfb1'", 32, None),
("'aes-128-cfb1'", 16, 16),
("'aes-128-cfb1'", 24, 24),
("'aes-192-cfb1'", 24, 16),
("'aes-192-cfb1'", 32, 32),
("'aes-256-cfb1'", 32, 16),
("'aes-256-cfb1'", 64, 64),
# cfb8
("'aes-128-cfb8'", 16, None),
("'aes-192-cfb8'", 24, None),
("'aes-256-cfb8'", 32, None),
("'aes-128-cfb8'", 16, 16),
("'aes-128-cfb8'", 24, 24),
("'aes-192-cfb8'", 24, 16),
("'aes-192-cfb8'", 32, 32),
("'aes-256-cfb8'", 32, 16),
("'aes-256-cfb8'", 64, 64),
# cfb128
("'aes-128-cfb128'", 16, None),
("'aes-192-cfb128'", 24, None),
("'aes-256-cfb128'", 32, None),
("'aes-128-cfb128'", 16, 16),
("'aes-128-cfb128'", 24, 24),
("'aes-192-cfb128'", 24, 16),
("'aes-192-cfb128'", 32, 32),
("'aes-256-cfb128'", 32, 16),
("'aes-256-cfb128'", 64, 64),
# ofb
("'aes-128-ofb'", 16, None),
("'aes-192-ofb'", 24, None),
("'aes-256-ofb'", 32, None),
("'aes-128-ofb'", 16, 16),
("'aes-128-ofb'", 24, 24),
("'aes-192-ofb'", 24, 16),
("'aes-192-ofb'", 32, 32),
("'aes-256-ofb'", 32, 16),
("'aes-256-ofb'", 64, 64),
]
plaintexts = [
("bytes", "unhex('0')"),
("emptystring", "''"),
("utf8string", "'Gãńdåłf_Thê_Gręât'"),
("utf8fixedstring", "toFixedString('Gãńdåłf_Thê_Gręât', 24)"),
("String", "'1'"),
("FixedString", "toFixedString('1', 1)"),
("UInt8", "toUInt8('1')"),
("UInt16", "toUInt16('1')"),
("UInt32", "toUInt32('1')"),
("UInt64", "toUInt64('1')"),
("Int8", "toInt8('1')"),
("Int16", "toInt16('1')"),
("Int32", "toInt32('1')"),
("Int64", "toInt64('1')"),
("Float32", "toFloat32('1')"),
("Float64", "toFloat64('1')"),
("Decimal32", "toDecimal32(2, 4)"),
("Decimal64", "toDecimal64(2, 4)"),
("Decimal128", "toDecimal128(2, 4)"),
("UUID", "toUUID('61f0c404-5cb3-11e7-907b-a6006ad3dba0')"),
("Date", "toDate('2020-01-01')"),
("DateTime", "toDateTime('2020-01-01 20:01:02')"),
("DateTime64", "toDateTime64('2020-01-01 20:01:02.123', 3)"),
("LowCardinality", "toLowCardinality('1')"),
("Array", "[1,2]"),
#("Tuple", "(1,'a')") - not supported
#("Nullable, "Nullable(X)") - not supported
("NULL", "toDateOrNull('foo')"),
("IPv4", "toIPv4('171.225.130.45')"),
("IPv6", "toIPv6('2001:0db8:0000:85a3:0000:0000:ac1f:8001')"),
("Enum8", r"CAST('a', 'Enum8(\'a\' = 1, \'b\' = 2)')"),
("Enum16", r"CAST('a', 'Enum16(\'a\' = 1, \'b\' = 2)')"),
]
_hex = hex
def hex(s):
"""Convert string to hex.
"""
if isinstance(s, str):
return "".join(['%X' % ord(c) for c in s])
if isinstance(s, bytes):
return "".join(['%X' % c for c in s])
return _hex(s)

View File

@ -0,0 +1,17 @@
from testflows.core import *
from aes_encryption.requirements import *
@TestFeature
@Name("compatibility")
@Requirements(
RQ_SRS008_AES_Functions_DataFromMultipleSources("1.0")
)
def feature(self, node="clickhouse1"):
"""Check encryption functions usage compatibility.
"""
self.context.node = self.context.cluster.node(node)
Feature(run=load("aes_encryption.tests.compatibility.insert", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.select", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.mysql.feature", "feature"), flags=TE)

View File

@ -0,0 +1,414 @@
import os
import textwrap
from contextlib import contextmanager
from importlib.machinery import SourceFileLoader
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts.helpers import varname
from testflows.asserts import values, error, snapshot
from aes_encryption.tests.common import modes, mysql_modes
@contextmanager
def table(name):
node = current().context.node
try:
with Given("table"):
sql = f"""
CREATE TABLE {name}
(
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = Memory()
"""
with By("dropping table if exists"):
node.query(f"DROP TABLE IF EXISTS {name}")
with And("creating a table"):
node.query(textwrap.dedent(sql))
yield
finally:
with Finally("I drop the table", flags=TE):
node.query(f"DROP TABLE IF EXISTS {name}")
@contextmanager
def mv_transform(table, transform):
node = current().context.node
try:
with Given("tables for input transformation"):
with By("creating Null input table"):
sql = f"""
CREATE TABLE {table}_input
(
date Nullable(Date),
name Nullable(String),
secret Nullable(String),
mode String,
key String,
iv String,
aad String
)
ENGINE=Null()
"""
node.query(textwrap.dedent(sql))
with And("creating materialized view table"):
sql = f"""
CREATE MATERIALIZED VIEW {table}_input_mv TO {table} AS
SELECT date, name, {transform}
FROM {table}_input
"""
node.query(textwrap.dedent(sql))
yield
finally:
with Finally("I drop tables for input transformation", flags=TE):
with By("dropping materialized view table", flags=TE):
node.query(f"DROP TABLE IF EXISTS {table}_input_mv")
with And("dropping Null input table", flags=TE):
node.query(f"DROP TABLE IF EXISTS {table}_input")
@TestScenario
def encrypt_using_materialized_view(self):
"""Check that we can use `encrypt` function when inserting
data into a table using a materialized view for input
data transformation.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"encrypt(mode, secret, key{', iv' if example_iv else ''}{', aad' if example_aad else ''})"
with table("user_data"):
with mv_transform("user_data", example_transform):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO user_data_input
(date, name, secret, mode, key)
VALUES
('2020-01-01', 'user0', 'user0_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}),
('2020-01-02', 'user1', 'user1_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}),
('2020-01-03', 'user2', 'user2_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""})
""")
with And("I read inserted data back"):
node.query("SELECT date, name, hex(secret) FROM user_data ORDER BY date")
with Then("output must match the snapshot"):
with values() as that:
assert that(snapshot(r.output.strip(), "insert", name=f"encrypt_mv_example_{varname(basename(self.name))}")), error()
@TestScenario
def aes_encrypt_mysql_using_materialized_view(self):
"""Check that we can use `aes_encrypt_mysql` function when inserting
data into a table using a materialized view for input
data transformation.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"aes_encrypt_mysql(mode, secret, key{', iv' if example_iv else ''})"
with table("user_data"):
with mv_transform("user_data", example_transform):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO user_data_input
(date, name, secret, mode, key)
VALUES
('2020-01-01', 'user0', 'user0_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}),
('2020-01-02', 'user1', 'user1_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}),
('2020-01-03', 'user2', 'user2_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""})
""")
with And("I read inserted data back"):
node.query("SELECT date, name, hex(secret) FROM user_data ORDER BY date")
with Then("output must match the snapshot"):
with values() as that:
assert that(snapshot(r.output.strip(), "insert", name=f"aes_encrypt_mysql_mv_example_{varname(basename(self.name))}")), error()
@TestScenario
def encrypt_using_input_table_function(self):
"""Check that we can use `encrypt` function when inserting
data into a table using insert select and `input()` table
function.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"encrypt({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''}{(', ' + example_aad) if example_aad else ''})"
with table("user_data"):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO
user_data
SELECT
date, name, {example_transform}
FROM
input('date Date, name String, secret String')
FORMAT Values ('2020-01-01', 'user0', 'user0_secret'), ('2020-01-02', 'user1', 'user1_secret'), ('2020-01-03', 'user2', 'user2_secret')
""")
with And("I read inserted data back"):
r = node.query("SELECT date, name, hex(secret) FROM user_data ORDER BY date")
with Then("output must match the snapshot"):
with values() as that:
assert that(snapshot(r.output.strip(), "insert", name=f"encrypt_input_example_{varname(basename(example.name))}")), error()
@TestScenario
def aes_encrypt_mysql_using_input_table_function(self):
"""Check that we can use `aes_encrypt_mysql` function when inserting
data into a table using insert select and `input()` table
function.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"aes_encrypt_mysql({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''})"
with table("user_data"):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO
user_data
SELECT
date, name, {example_transform}
FROM
input('date Date, name String, secret String')
FORMAT Values ('2020-01-01', 'user0', 'user0_secret'), ('2020-01-02', 'user1', 'user1_secret'), ('2020-01-03', 'user2', 'user2_secret')
""")
with And("I read inserted data back"):
r = node.query("SELECT date, name, hex(secret) FROM user_data ORDER BY date")
with Then("output must match the snapshot"):
with values() as that:
assert that(snapshot(r.output.strip(), "insert", name=f"aes_encrypt_mysql_input_example_{varname(basename(example.name))}")), error()
@TestScenario
def decrypt_using_materialized_view(self):
"""Check that we can use `decrypt` function when inserting
data into a table using a materialized view for input
data transformation.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "insert.py.insert.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"decrypt(mode, secret, key{', iv' if example_iv else ''}{', aad' if example_aad else ''})"
with Given("I have ciphertexts"):
example_name = basename(example.name)
ciphertexts = getattr(snapshot_module, varname(f"encrypt_mv_example_{example_name}"))
example_ciphertexts = ["'{}'".format(l.split("\t")[-1].strup("'")) for l in ciphertexts.split("\n")]
with table("user_data"):
with mv_transform("user_data", example_transform):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO user_data_input
(date, name, secret, mode, key)
VALUES
('2020-01-01', 'user0', 'unhex({example_ciphertexts[0]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}),
('2020-01-02', 'user1', 'unhex({example_ciphertexts[1]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}),
('2020-01-03', 'user2', 'unhex({example_ciphertexts[2]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""})
""")
with And("I read inserted data back"):
r = node.query("SELECT date, name, secret FROM user_data ORDER BY date")
with Then("output must match the expected"):
expected = r"""'2020-01-01\tuser0\tuser0_secret\n2020-01-02\tuser1\tuser1_secret\n2020-01-03\tuser2\tuser2_secret'"""
assert r.output == expected, error()
@TestScenario
def aes_decrypt_mysql_using_materialized_view(self):
"""Check that we can use `aes_decrypt_mysql` function when inserting
data into a table using a materialized view for input
data transformation.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "insert.py.insert.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"aes_decrypt_mysql(mode, secret, key{', iv' if example_iv else ''})"
with Given("I have ciphertexts"):
example_name = basename(example.name)
ciphertexts = getattr(snapshot_module, varname(f"aes_encrypt_mysql_mv_example_{example_name}"))
example_ciphertexts = ["'{}'".format(l.split("\t")[-1].strup("'")) for l in ciphertexts.split("\n")]
with table("user_data"):
with mv_transform("user_data", example_transform):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO user_data_input
(date, name, secret, mode, key)
VALUES
('2020-01-01', 'user0', 'unhex({example_ciphertexts[0]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}),
('2020-01-02', 'user1', 'unhex({example_ciphertexts[1]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}),
('2020-01-03', 'user2', 'unhex({example_ciphertexts[2]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""})
""")
with And("I read inserted data back"):
r = node.query("SELECT date, name, secret FROM user_data ORDER BY date")
with Then("output must match the expected"):
expected = r"""'2020-01-01\tuser0\tuser0_secret\n2020-01-02\tuser1\tuser1_secret\n2020-01-03\tuser2\tuser2_secret'"""
assert r.output == expected, error()
@TestScenario
def decrypt_using_input_table_function(self):
"""Check that we can use `decrypt` function when inserting
data into a table using insert select and `input()` table
function.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "insert.py.insert.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"decrypt({mode}, unhex(secret), {example_key}{(', ' + example_iv) if example_iv else ''}{(', ' + example_aad) if example_aad else ''})"
with Given("I have ciphertexts"):
example_name = basename(example.name)
ciphertexts = getattr(snapshot_module, varname(f"encrypt_input_example_{example_name}"))
example_ciphertexts = [l.split("\\t")[-1].strip("'") for l in ciphertexts.split("\\n")]
with table("user_data"):
with When("I insert decrypted data"):
node.query(textwrap.dedent(f"""
INSERT INTO
user_data
SELECT
date, name, {example_transform}
FROM
input('date Date, name String, secret String')
FORMAT Values ('2020-01-01', 'user0', '{example_ciphertexts[0]}'), ('2020-01-02', 'user1', '{example_ciphertexts[1]}'), ('2020-01-03', 'user2', '{example_ciphertexts[2]}')
"""))
with And("I read inserted data back"):
r = node.query("SELECT date, name, secret FROM user_data ORDER BY date")
expected = """2020-01-01\tuser0\tuser0_secret\n2020-01-02\tuser1\tuser1_secret\n2020-01-03\tuser2\tuser2_secret"""
with Then("output must match the expected", description=expected):
assert r.output == expected, error()
@TestScenario
def aes_decrypt_mysql_using_input_table_function(self):
"""Check that we can use `aes_decrypt_mysql` function when inserting
data into a table using insert select and `input()` table
function.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "insert.py.insert.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"aes_decrypt_mysql({mode}, unhex(secret), {example_key}{(', ' + example_iv) if example_iv else ''})"
with Given("I have ciphertexts"):
example_name = basename(example.name)
ciphertexts = getattr(snapshot_module, varname(f"aes_encrypt_mysql_input_example_{example_name}"))
example_ciphertexts = [l.split("\\t")[-1].strip("'") for l in ciphertexts.split("\\n")]
with table("user_data"):
with When("I insert decrypted data"):
node.query(textwrap.dedent(f"""
INSERT INTO
user_data
SELECT
date, name, {example_transform}
FROM
input('date Date, name String, secret String')
FORMAT Values ('2020-01-01', 'user0', '{example_ciphertexts[0]}'), ('2020-01-02', 'user1', '{example_ciphertexts[1]}'), ('2020-01-03', 'user2', '{example_ciphertexts[2]}')
"""))
with And("I read inserted data back"):
r = node.query("SELECT date, name, secret FROM user_data ORDER BY date")
expected = """2020-01-01\tuser0\tuser0_secret\n2020-01-02\tuser1\tuser1_secret\n2020-01-03\tuser2\tuser2_secret"""
with Then("output must match the expected", description=expected):
assert r.output == expected, error()
@TestFeature
@Name("insert")
def feature(self, node="clickhouse1"):
"""Check encryption functions when used during data insertion into a table.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,196 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts import error
from aes_encryption.requirements import *
from aes_encryption.tests.common import mysql_modes, hex
@contextmanager
def table(name, node, mysql_node, secret_type):
"""Create a table that can be accessed using MySQL database engine.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
id INT NOT NULL AUTO_INCREMENT,
date DATE,
name VARCHAR(100),
secret {secret_type},
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I create a database using MySQL database engine"):
sql = f"""
CREATE DATABASE mysql_db
ENGINE = MySQL('{mysql_node.name}:3306', 'db', 'user', 'password')
"""
with When("I drop database if exists"):
node.query(f"DROP DATABASE IF EXISTS mysql_db")
with And("I create database"):
node.query(textwrap.dedent(sql))
yield
finally:
with And("I drop the database that is using MySQL database engine", flags=TE):
node.query(f"DROP DATABASE IF EXISTS mysql_db")
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def decrypt(self, mysql_datatype):
"""Check that when using a table provided by MySQL database engine that
contains a column encrypted in MySQL stored using specified data type
I can decrypt data in the column using the `decrypt` and `aes_decrypt_mysql`
functions in the select query.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["decrypt", "aes_decrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "decrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
with When("I insert encrypted data in MySQL"):
sql = f"""
SET block_encryption_mode = {example_mode};
INSERT INTO user_data VALUES (NULL, '2020-01-01', 'user0', AES_ENCRYPT('secret', {example_key}{(", " + example_iv) if example_iv else ", ''"}));
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read encrypted data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"}) AS secret FROM user_data;
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read raw encrypted data in MySQL"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT id, date, name, hex(secret) as secret FROM user_data;\"", exitcode=0)
with And("I read raw data using MySQL database engine"):
output = node.query("SELECT id, date, name, hex(secret) AS secret FROM mysql_db.user_data")
with And("I read decrypted data using MySQL database engine"):
output = node.query(f"""SELECT hex({func}({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM mysql_db.user_data""").output.strip()
with Then("output should match the original plain text"):
assert output == hex("secret"), error()
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def encrypt(self, mysql_datatype):
"""Check that when using a table provided by MySQL database engine that
we can encrypt data during insert using the `aes_encrypt_mysql` function
and decrypt it in MySQL.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["encrypt", "aes_encrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "encrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"{func}({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''})"
with When("I insert encrypted data into a table provided by MySQL database engine"):
node.query(textwrap.dedent(f"""
INSERT INTO
mysql_db.user_data
SELECT
id, date, name, {example_transform}
FROM
input('id Int32, date Date, name String, secret String')
FORMAT Values (1, '2020-01-01', 'user0', 'secret')
"""))
with And("I read decrypted data using MySQL database engine"):
output = node.query(f"""SELECT hex(aes_decrypt_mysql({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM mysql_db.user_data""").output.strip()
with Then("decrypted data from MySQL database engine should should match the original plain text"):
assert output == hex("secret"), error()
with And("I read raw data using MySQL database engine to get expected raw data"):
expected_raw_data = node.query("SELECT hex(secret) AS secret FROM mysql_db.user_data").output.strip()
with And("I read raw encrypted data in MySQL"):
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT hex(secret) as secret FROM user_data;\"", exitcode=0).output.strip()
with Then("check that raw encryted data in MySQL matches the expected"):
assert expected_raw_data in output, error()
with And("I decrypt data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, hex(AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"})) AS secret FROM user_data;
"""
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0).output.strip()
with Then("decryted data in MySQL should match the original plain text"):
assert hex("secret") in output, error()
@TestFeature
@Name("database engine")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_Engine_Database_MySQL("1.0")
)
def feature(self, node="clickhouse1", mysql_node="mysql1"):
"""Check usage of encryption functions with [MySQL database engine].
[MySQL database engine]: https://clickhouse.tech/docs/en/engines/database-engines/mysql/
"""
self.context.node = self.context.cluster.node(node)
self.context.mysql_node = self.context.cluster.node(mysql_node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,251 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts import error
from aes_encryption.requirements import *
from aes_encryption.tests.common import mysql_modes, hex
@contextmanager
def dictionary(name, node, mysql_node, secret_type):
"""Create a table in MySQL and use it a source for a dictionary.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
id INT NOT NULL AUTO_INCREMENT,
date DATE,
name VARCHAR(100),
secret {secret_type},
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("dictionary that uses MySQL table as the external source"):
with When("I drop the dictionary if exists"):
node.query(f"DROP DICTIONARY IF EXISTS dict_{name}")
with And("I create the dictionary"):
sql = f"""
CREATE DICTIONARY dict_{name}
(
id Int32,
date Date,
name String,
secret String
)
PRIMARY KEY id
SOURCE(MYSQL(
USER 'user'
PASSWORD 'password'
DB 'db'
TABLE '{name}'
REPLICA(PRIORITY 1 HOST '{mysql_node.name}' PORT 3306)
))
LAYOUT(HASHED())
LIFETIME(0)
"""
node.query(textwrap.dedent(sql))
yield f"dict_{name}"
finally:
with Finally("I drop the dictionary", flags=TE):
node.query(f"DROP DICTIONARY IF EXISTS dict_{name}")
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@contextmanager
def parameters_dictionary(name, node, mysql_node):
"""Create a table in MySQL and use it a source for a dictionary
that stores parameters for the encryption functions.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100),
`mode` VARCHAR(100),
`key` BLOB,
`iv` BLOB,
`text` BLOB,
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("dictionary that uses MySQL table as the external source"):
with When("I drop the dictionary if exists"):
node.query(f"DROP DICTIONARY IF EXISTS dict_{name}")
with And("I create the dictionary"):
sql = f"""
CREATE DICTIONARY dict_{name}
(
id Int32,
name String,
mode String,
key String,
iv String,
text String
)
PRIMARY KEY id
SOURCE(MYSQL(
USER 'user'
PASSWORD 'password'
DB 'db'
TABLE '{name}'
REPLICA(PRIORITY 1 HOST '{mysql_node.name}' PORT 3306)
))
LAYOUT(HASHED())
LIFETIME(0)
"""
node.query(textwrap.dedent(sql))
yield f"dict_{name}"
finally:
with Finally("I drop the dictionary", flags=TE):
node.query(f"DROP DICTIONARY IF EXISTS dict_{name}")
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@TestScenario
def parameter_values(self):
"""Check that we can use a dictionary that uses MySQL table as a source
can be used as a parameters store for the `encrypt`, `decrypt`, and
`aes_encrypt_mysql`, `aes_decrypt_mysql` functions.
"""
node = self.context.node
mysql_node = self.context.mysql_node
mode = "'aes-128-cbc'"
key = f"'{'1' * 16}'"
iv = f"'{'2' * 16}'"
plaintext = "'secret'"
for encrypt, decrypt in [
("encrypt", "decrypt"),
("aes_encrypt_mysql", "aes_decrypt_mysql")
]:
with Example(f"{encrypt} and {decrypt}", description=f"Check using dictionary for parameters of {encrypt} and {decrypt} functions."):
with parameters_dictionary("parameters_data", node, mysql_node) as dict_name:
with When("I insert parameters values in MySQL"):
sql = f"""
INSERT INTO parameters_data VALUES (1, 'user0', {mode}, {key}, {iv}, {plaintext});
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I use dictionary values as parameters"):
sql = f"""
SELECT {decrypt}(
dictGet('default.{dict_name}', 'mode', toUInt64(1)),
{encrypt}(
dictGet('default.{dict_name}', 'mode', toUInt64(1)),
dictGet('default.{dict_name}', 'text', toUInt64(1)),
dictGet('default.{dict_name}', 'key', toUInt64(1)),
dictGet('default.{dict_name}', 'iv', toUInt64(1))
),
dictGet('default.{dict_name}', 'key', toUInt64(1)),
dictGet('default.{dict_name}', 'iv', toUInt64(1))
)
"""
output = node.query(textwrap.dedent(sql)).output.strip()
with Then("output should match the plain text"):
assert f"'{output}'" == plaintext, error()
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def decrypt(self, mysql_datatype):
"""Check that when using a dictionary that uses MySQL table as a source and
contains a data encrypted in MySQL and stored using specified data type
that we can decrypt data from the dictionary using
the `aes_decrypt_mysql` or `decrypt` functions in the select query.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["decrypt", "aes_decrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "decrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with dictionary("user_data", node, mysql_node, mysql_datatype) as dict_name:
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
with When("I insert encrypted data in MySQL"):
sql = f"""
SET block_encryption_mode = {example_mode};
INSERT INTO user_data VALUES (NULL, '2020-01-01', 'user0', AES_ENCRYPT('secret', {example_key}{(", " + example_iv) if example_iv else ", ''"}));
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read encrypted data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"}) AS secret FROM user_data;
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read raw encrypted data in MySQL"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT id, date, name, hex(secret) as secret FROM user_data;\"", exitcode=0)
with And("I read raw data using MySQL dictionary"):
output = node.query(f"SELECT hex(dictGet('default.{dict_name}', 'secret', toUInt64(1))) AS secret")
with And("I read decrypted data using MySQL dictionary"):
output = node.query(textwrap.dedent(f"""
SELECT hex(
{func}(
{example_mode},
dictGet('default.{dict_name}', 'secret', toUInt64(1)),
{example_key}{(", " + example_iv) if example_iv else ""}
)
)
""")).output.strip()
with Then("output should match the original plain text"):
assert output == hex("secret"), error()
@TestFeature
@Name("dictionary")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_Dictionaries("1.0")
)
def feature(self, node="clickhouse1", mysql_node="mysql1"):
"""Check usage of encryption functions with [MySQL dictionary].
[MySQL dictionary]: https://clickhouse.tech/docs/en/sql-reference/dictionaries/external-dictionaries/external-dicts-dict-sources/#dicts-external_dicts_dict_sources-mysql
"""
self.context.node = self.context.cluster.node(node)
self.context.mysql_node = self.context.cluster.node(mysql_node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,18 @@
from testflows.core import *
from aes_encryption.requirements import *
@TestFeature
@Name("mysql")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_MySQL("1.0")
)
def feature(self, node="clickhouse1"):
"""Check encryption functions usage compatibility with MySQL.
"""
self.context.node = self.context.cluster.node(node)
Feature(run=load("aes_encryption.tests.compatibility.mysql.table_engine", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.mysql.database_engine", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.mysql.table_function", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.mysql.dictionary", "feature"), flags=TE)

View File

@ -0,0 +1,202 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts import error
from aes_encryption.requirements import *
from aes_encryption.tests.common import mysql_modes, hex
@contextmanager
def table(name, node, mysql_node, secret_type):
"""Create a table that can be accessed using MySQL table engine.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
id INT NOT NULL AUTO_INCREMENT,
date DATE,
name VARCHAR(100),
secret {secret_type},
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I create a table using MySQL table engine"):
sql = f"""
CREATE TABLE mysql_{name}
(
id Nullable(Int32),
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = MySQL('{mysql_node.name}:3306', 'db', '{name}', 'user', 'password')
"""
with When("I drop table if exists"):
node.query(f"DROP TABLE IF EXISTS mysql_{name}")
with And("I create table"):
node.query(textwrap.dedent(sql))
yield
finally:
with And("I drop a table using MySQL table engine", flags=TE):
node.query(f"DROP TABLE IF EXISTS mysql_{name}")
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def decrypt(self, mysql_datatype):
"""Check that when using a table with MySQL table engine that
contains a column encrypted in MySQL stored using specified data type
I can decrypt data in the column using the `decrypt` and `aes_decrypt_mysql`
functions in the select query.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["decrypt", "aes_decrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "decrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
with When("I insert encrypted data in MySQL"):
sql = f"""
SET block_encryption_mode = {example_mode};
INSERT INTO user_data VALUES (NULL, '2020-01-01', 'user0', AES_ENCRYPT('secret', {example_key}{(", " + example_iv) if example_iv else ", ''"}));
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read encrypted data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"}) AS secret FROM user_data;
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read raw encrypted data in MySQL"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT id, date, name, hex(secret) as secret FROM user_data;\"", exitcode=0)
with And("I read raw data using MySQL table engine"):
output = node.query("SELECT id, date, name, hex(secret) AS secret FROM mysql_user_data")
with And("I read decrypted data via MySQL table engine"):
output = node.query(f"""SELECT hex({func}({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM mysql_user_data""").output.strip()
with Then("the output should match the original plain text"):
assert output == hex("secret"), error()
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def encrypt(self, mysql_datatype):
"""Check that when using a table with MySQL table engine that
we can encrypt data during insert using the `encrypt` and `aes_encrypt_mysql`
functions and decrypt it in MySQL.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["encrypt", "aes_encrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "encrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"{func}({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''})"
with When("I insert encrypted data into MySQL table engine"):
node.query(textwrap.dedent(f"""
INSERT INTO
mysql_user_data
SELECT
id, date, name, {example_transform}
FROM
input('id Nullable(Int32), date Date, name String, secret String')
FORMAT Values (null, '2020-01-01', 'user0', 'secret')
"""))
with And("I read decrypted data via MySQL table engine"):
output = node.query(f"""SELECT hex(aes_decrypt_mysql({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM mysql_user_data""").output.strip()
with Then("decrypted data from MySQL table engine should should match the original plain text"):
assert output == hex("secret"), error()
with And("I read raw data using MySQL table engine to get expected raw data"):
expected_raw_data = node.query("SELECT hex(secret) AS secret FROM mysql_user_data").output.strip()
with And("I read raw encrypted data in MySQL"):
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT hex(secret) as secret FROM user_data;\"", exitcode=0).output.strip()
with Then("check that raw encryted data in MySQL matches the expected"):
assert expected_raw_data in output, error()
with And("I decrypt data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, hex(AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"})) AS secret FROM user_data;
"""
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0).output.strip()
with Then("decryted data in MySQL should match the original plain text"):
assert hex("secret") in output, error()
@TestFeature
@Name("table engine")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_Engine_Table_MySQL("1.0")
)
def feature(self, node="clickhouse1", mysql_node="mysql1"):
"""Check usage of encryption functions with [MySQL table engine].
[MySQL table engine]: https://clickhouse.tech/docs/en/engines/table-engines/integrations/mysql/
"""
self.context.node = self.context.cluster.node(node)
self.context.mysql_node = self.context.cluster.node(mysql_node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,183 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts import error
from aes_encryption.requirements import *
from aes_encryption.tests.common import mysql_modes, hex
@contextmanager
def table(name, node, mysql_node, secret_type):
"""Create a table that can be accessed using MySQL table function.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
id INT NOT NULL AUTO_INCREMENT,
date DATE,
name VARCHAR(100),
secret {secret_type},
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
yield f"mysql('{mysql_node.name}:3306', 'db', 'user_data', 'user', 'password')"
finally:
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def decrypt(self, mysql_datatype):
"""Check that when using a table accessed through MySQL table function that
contains a column encrypted in MySQL stored using specified data type
I can decrypt data in the column using the `decrypt` and `aes_decrypt_mysql`
functions in the select query.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["decrypt", "aes_decrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "decrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype) as table_function:
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
with When("I insert encrypted data in MySQL"):
sql = f"""
SET block_encryption_mode = {example_mode};
INSERT INTO user_data VALUES (NULL, '2020-01-01', 'user0', AES_ENCRYPT('secret', {example_key}{(", " + example_iv) if example_iv else ", ''"}));
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read encrypted data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"}) AS secret FROM user_data;
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read raw encrypted data in MySQL"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT id, date, name, hex(secret) as secret FROM user_data;\"", exitcode=0)
with And("I read raw data using MySQL table function"):
output = node.query(f"SELECT id, date, name, hex(secret) AS secret FROM {table_function}")
with And("I read decrypted data using MySQL table function"):
output = node.query(f"""SELECT hex({func}({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM {table_function}""").output.strip()
with Then("output should match the original plain text"):
assert output == hex("secret"), error()
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def encrypt(self, mysql_datatype):
"""Check that when using a table accessed through MySQL table function that
we can encrypt data during insert using the `aes_encrypt_mysql` function
and decrypt it in MySQL.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["encrypt", "aes_encrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "encrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype) as table_function:
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"{func}({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''})"
with When("I insert encrypted data into a table provided by MySQL database engine"):
node.query(textwrap.dedent(f"""
INSERT INTO TABLE FUNCTION
{table_function}
SELECT
id, date, name, {example_transform}
FROM
input('id Int32, date Date, name String, secret String')
FORMAT Values (1, '2020-01-01', 'user0', 'secret')
"""))
with And("I read decrypted data using MySQL database engine"):
output = node.query(f"""SELECT hex(aes_decrypt_mysql({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM {table_function}""").output.strip()
with Then("decrypted data from MySQL database engine should should match the original plain text"):
assert output == hex("secret"), error()
with And("I read raw data using MySQL database engine to get expected raw data"):
expected_raw_data = node.query(f"SELECT hex(secret) AS secret FROM {table_function}").output.strip()
with And("I read raw encrypted data in MySQL"):
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT hex(secret) as secret FROM user_data;\"", exitcode=0).output.strip()
with Then("check that raw encryted data in MySQL matches the expected"):
assert expected_raw_data in output, error()
with And("I decrypt data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, hex(AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"})) AS secret FROM user_data;
"""
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0).output.strip()
with Then("decryted data in MySQL should match the original plain text"):
assert hex("secret") in output, error()
@TestFeature
@Name("table function")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_TableFunction_MySQL("1.0")
)
def feature(self, node="clickhouse1", mysql_node="mysql1"):
"""Check usage of encryption functions with [MySQL table function].
[MySQL table function]: https://clickhouse.tech/docs/en/sql-reference/table-functions/mysql/
"""
self.context.node = self.context.cluster.node(node)
self.context.mysql_node = self.context.cluster.node(mysql_node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,186 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts.helpers import varname
from testflows.asserts import values, error, snapshot
from aes_encryption.tests.common import modes, mysql_modes
@contextmanager
def table(name, sql):
node = current().context.node
try:
with Given("table"):
with By("dropping table if exists"):
node.query(f"DROP TABLE IF EXISTS {name}")
with And("creating a table"):
node.query(textwrap.dedent(sql.format(name=name)))
yield
finally:
with Finally("I drop the table", flags=TE):
node.query(f"DROP TABLE IF EXISTS {name}")
@TestScenario
def decrypt(self):
"""Check decrypting column when reading data from a table.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len} aad={aad_len}""") as example:
with table("user_table", """
CREATE TABLE {name}
(
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = Memory()
"""):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
with When("I insert encrypted data"):
encrypted_secret = node.query(f"""SELECT hex(encrypt({example_mode}, 'secret', {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}))""").output.strip()
node.query(textwrap.dedent(f"""
INSERT INTO user_table
(date, name, secret)
VALUES
('2020-01-01', 'user0', unhex('{encrypted_secret}'))
"""))
with And("I decrypt data during query"):
output = node.query(f"""SELECT name, decrypt({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}) AS secret FROM user_table FORMAT JSONEachRow""").output.strip()
with Then("I should get back the original plain text"):
assert output == '{"name":"user0","secret":"secret"}', error()
@TestScenario
def decrypt_multiple(self, count=1000):
"""Check decrypting column when reading multiple entries
encrypted with the same parameters for the same user
from a table.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len} aad={aad_len}""") as example:
with table("user_table", """
CREATE TABLE {name}
(
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = Memory()
"""):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
with When("I insert encrypted data"):
encrypted_secret = node.query(f"""SELECT hex(encrypt({example_mode}, 'secret', {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}))""").output.strip()
values = [f"('2020-01-01', 'user0', unhex('{encrypted_secret}'))"] * count
node.query(
"INSERT INTO user_table\n"
" (date, name, secret)\n"
f"VALUES {', '.join(values)}")
with And("I decrypt data", description="using a subquery and get the number of entries that match the plaintext"):
output = node.query(f"""SELECT count() AS count FROM (SELECT name, decrypt({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}) AS secret FROM user_table) WHERE secret = 'secret' FORMAT JSONEachRow""").output.strip()
with Then("I should get back the expected result", description=f"{count}"):
assert output == f'{{"count":"{count}"}}', error()
@TestScenario
def decrypt_unique(self):
"""Check decrypting column when reading multiple entries
encrypted with the different parameters for each user
from a table.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
with table("user_table", """
CREATE TABLE {name}
(
id UInt64,
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = Memory()
"""):
user_modes = []
user_keys = []
with When("I get encrypted data"):
user_id = 0
values = []
for mode, key_len, iv_len, aad_len in modes:
if "gcm" in mode:
continue
user_modes.append(mode)
user_keys.append(f"'{key[:key_len]}'")
with When(f"I get encrypted data for user {user_id}"):
encrypted_secret = node.query(
f"""SELECT hex(encrypt({user_modes[-1]}, 'secret', {user_keys[-1]}))"""
).output.strip()
values.append(f"({user_id}, '2020-01-01', 'user{user_id}', unhex('{encrypted_secret}'))")
user_id += 1
with And("I insert encrypted data for all users"):
node.query(
"INSERT INTO user_table\n"
" (id, date, name, secret)\n"
f"VALUES {', '.join(values)}")
with And("I read decrypted data for all users"):
output = node.query(textwrap.dedent(f"""
SELECT
count() AS count
FROM
(
SELECT
[{",".join(user_modes)}] AS modes,
[{",".join(user_keys)}] AS keys,
name,
decrypt(modes[id], secret, keys[id]) AS secret
FROM user_table
)
WHERE
secret = 'secret'
FORMAT JSONEachRow
""")).output.strip()
with Then("I should get back the expected result", description=f"{count}"):
assert output == f'{{"count":"{count}"}}', error()
@TestFeature
@Name("select")
def feature(self, node="clickhouse1"):
"""Check encryption functions when used during table querying.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,192 @@
aes_encrypt_mysql_input_example_mode_aes_128_ecb_key_16_iv_None = r"""'2020-01-01\tuser0\t8C9598C3C8D8AC241DDF0D1B22020104\n2020-01-02\tuser1\tC5ECE31A240069D8F169B9F8CF687779\n2020-01-03\tuser2\t9FCFA4B05DD49D2B24BA61091F963CE3'"""
aes_encrypt_mysql_input_example_mode_aes_128_ecb_key_24_iv_None = r"""'2020-01-01\tuser0\tB418FF12BCBF9E42FA7C19D6EE26BF0B\n2020-01-02\tuser1\t3147A3FEE47DF418D1D75CBC1BC14DE6\n2020-01-03\tuser2\tAECEFD40C6632A0FC033D040E44CCBCC'"""
aes_encrypt_mysql_input_example_mode_aes_192_ecb_key_24_iv_None = r"""'2020-01-01\tuser0\t897F14C4E497962D986A7E7EA57AA043\n2020-01-02\tuser1\tED84AF2B3447113DA451E4577F649E36\n2020-01-03\tuser2\t4976F9D5AE195E61694A9ADCDD8A076F'"""
aes_encrypt_mysql_input_example_mode_aes_192_ecb_key_32_iv_None = r"""'2020-01-01\tuser0\t044E715357AF77234FD95359666CAFF3\n2020-01-02\tuser1\tB633EF852CE85B4C97827401FD9B606B\n2020-01-03\tuser2\t2AFF7052C748E4BC3BDA8460AFD5A21D'"""
aes_encrypt_mysql_input_example_mode_aes_256_ecb_key_32_iv_None = r"""'2020-01-01\tuser0\tBABD6C071FDEE1C9A33877006FBB0BE6\n2020-01-02\tuser1\t7753E81D1DB9DC91FC8148E88B3E9526\n2020-01-03\tuser2\tD77D1A8DF82C2273BF0D19A14526531F'"""
aes_encrypt_mysql_input_example_mode_aes_256_ecb_key_64_iv_None = r"""'2020-01-01\tuser0\tBFFEC9DF7285A3EC799C941E1450839C\n2020-01-02\tuser1\t3EA0ECBD06326D227A7B9519B1A2955D\n2020-01-03\tuser2\t1478C57DD49523ABDB83A0917F0EDA60'"""
aes_encrypt_mysql_input_example_mode_aes_128_cbc_key_16_iv_None = r"""'2020-01-01\tuser0\t8C9598C3C8D8AC241DDF0D1B22020104\n2020-01-02\tuser1\tC5ECE31A240069D8F169B9F8CF687779\n2020-01-03\tuser2\t9FCFA4B05DD49D2B24BA61091F963CE3'"""
aes_encrypt_mysql_input_example_mode_aes_192_cbc_key_24_iv_None = r"""'2020-01-01\tuser0\t897F14C4E497962D986A7E7EA57AA043\n2020-01-02\tuser1\tED84AF2B3447113DA451E4577F649E36\n2020-01-03\tuser2\t4976F9D5AE195E61694A9ADCDD8A076F'"""
aes_encrypt_mysql_input_example_mode_aes_256_cbc_key_32_iv_None = r"""'2020-01-01\tuser0\tBABD6C071FDEE1C9A33877006FBB0BE6\n2020-01-02\tuser1\t7753E81D1DB9DC91FC8148E88B3E9526\n2020-01-03\tuser2\tD77D1A8DF82C2273BF0D19A14526531F'"""
aes_encrypt_mysql_input_example_mode_aes_128_cbc_key_16_iv_16 = r"""'2020-01-01\tuser0\tFC93C1D5E5E3B054C1F3A5692AAC0A61\n2020-01-02\tuser1\tD6DBC76ABCB14B7C6D93F1A5FCA66B9C\n2020-01-03\tuser2\tD4F4158A650D01EB505CC72EFE455486'"""
aes_encrypt_mysql_input_example_mode_aes_128_cbc_key_24_iv_24 = r"""'2020-01-01\tuser0\t26CEE6B6EBDDE1BF887FDEB75F28FB52\n2020-01-02\tuser1\tF9EC1A75BEEFF70B4DEB39AAD075CEFF\n2020-01-03\tuser2\t3FF84AB3BD40FAEEF70F06BCF6AF9C42'"""
aes_encrypt_mysql_input_example_mode_aes_192_cbc_key_24_iv_16 = r"""'2020-01-01\tuser0\t0E3BAF7F4E0BFCFFAE2589B67F71E277\n2020-01-02\tuser1\t2581CCEE9ABE5770480901D65B3D9222\n2020-01-03\tuser2\tED9F3BD8DB12FDF9F2462FFA572361E7'"""
aes_encrypt_mysql_input_example_mode_aes_192_cbc_key_32_iv_32 = r"""'2020-01-01\tuser0\t07371B5DE2E378EE08A3A8B6B9FEAD13\n2020-01-02\tuser1\t3C0BF5D187421ECFFD3E00474A154452\n2020-01-03\tuser2\t05B253FA783D78D864AF7C4D5E6A492D'"""
aes_encrypt_mysql_input_example_mode_aes_256_cbc_key_32_iv_16 = r"""'2020-01-01\tuser0\t72AC7BA6F283EA94A3C33C4D3E51C7D3\n2020-01-02\tuser1\tDACBBE79062F1C721A01CEEE3E85524F\n2020-01-03\tuser2\tFF5A09D19E5EB2ADD94581308588E44A'"""
aes_encrypt_mysql_input_example_mode_aes_256_cbc_key_64_iv_64 = r"""'2020-01-01\tuser0\t573924F0BB4AA1780D45DB6451F123D6\n2020-01-02\tuser1\t007A54AA7ADE8EF844D28936486D75BC\n2020-01-03\tuser2\tAA7249B514398FE1EE827C44402BCE57'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb1_key_16_iv_None = r"""'2020-01-01\tuser0\t750BE8662F57A095EC0E610C\n2020-01-02\tuser1\t750BE8662E444A6284C0FC72\n2020-01-03\tuser2\t750BE8662C000B61CDCF1C94'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb1_key_24_iv_None = r"""'2020-01-01\tuser0\t5DCC67A043EB776D8B7F5B70\n2020-01-02\tuser1\t5DCC67A042B46DFCC10EFD66\n2020-01-03\tuser2\t5DCC67A040243A8C1346D2DD'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb1_key_32_iv_None = r"""'2020-01-01\tuser0\tFAAC1A7D2CE844F8DEB4C44E\n2020-01-02\tuser1\tFAAC1A7D2DF85A43828C0FF8\n2020-01-03\tuser2\tFAAC1A7D2FC7582CCEFCF330'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb1_key_16_iv_16 = r"""'2020-01-01\tuser0\t7670A865D13B1B65AD46F8ED\n2020-01-02\tuser1\t7670A865D046007A1E218286\n2020-01-03\tuser2\t7670A865D2E5B091492ECCFB'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb1_key_24_iv_24 = r"""'2020-01-01\tuser0\t51EADDE82195C31118D0C171\n2020-01-02\tuser1\t51EADDE82009A46518270271\n2020-01-03\tuser2\t51EADDE8235CB38F95766481'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb1_key_24_iv_16 = r"""'2020-01-01\tuser0\t7F38C051539074C0A635C937\n2020-01-02\tuser1\t7F38C051520A30DFACBE9564\n2020-01-03\tuser2\t7F38C051500DA29FF0E7B799'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb1_key_32_iv_32 = r"""'2020-01-01\tuser0\t2067186DB91666DE730D0708\n2020-01-02\tuser1\t2067186DB83E2E8B0019F839\n2020-01-03\tuser2\t2067186DBB540332BFC84955'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb1_key_32_iv_16 = r"""'2020-01-01\tuser0\t0A216A58A5C0A33215E8E722\n2020-01-02\tuser1\t0A216A58A4E94067ABF030B6\n2020-01-03\tuser2\t0A216A58A6822CAB0318C632'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb1_key_64_iv_64 = r"""'2020-01-01\tuser0\t81BD636E1BF4CA02399943E3\n2020-01-02\tuser1\t81BD636E1A93D5D6DD9DCD8D\n2020-01-03\tuser2\t81BD636E18F15168D19C8117'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb8_key_16_iv_None = r"""'2020-01-01\tuser0\t650D96B9698D20DB12E2E437\n2020-01-02\tuser1\t650D96B968F00D16ABF2852E\n2020-01-03\tuser2\t650D96B96B8141F425E60D6B'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb8_key_24_iv_None = r"""'2020-01-01\tuser0\t72C4724B2F528724A12041C0\n2020-01-02\tuser1\t72C4724B2EF3C6A6FF9E09A9\n2020-01-03\tuser2\t72C4724B2D6EAB1D47709E15'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb8_key_32_iv_None = r"""'2020-01-01\tuser0\tC5FD6C94961765ED204F2BCA\n2020-01-02\tuser1\tC5FD6C9497AB1C1AF1DE671C\n2020-01-03\tuser2\tC5FD6C949491F4A3EA5069B3'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb8_key_16_iv_16 = r"""'2020-01-01\tuser0\t471D217E9CA3593FFEC955C8\n2020-01-02\tuser1\t471D217E9D7F484D85F81F19\n2020-01-03\tuser2\t471D217E9EBBFD2EA9841008'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb8_key_24_iv_24 = r"""'2020-01-01\tuser0\t2EE6147B830481BE36CBE350\n2020-01-02\tuser1\t2EE6147B82DE8F3197AF17A6\n2020-01-03\tuser2\t2EE6147B81FF826E798A0355'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb8_key_24_iv_16 = r"""'2020-01-01\tuser0\t1D98EFFAEB9907457BD3FCB2\n2020-01-02\tuser1\t1D98EFFAEA2D930825C6AE22\n2020-01-03\tuser2\t1D98EFFAE92C1D018438B98B'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb8_key_32_iv_32 = r"""'2020-01-01\tuser0\t4410165F7DCFDDBB1B15573F\n2020-01-02\tuser1\t4410165F7CFE6A0D2FD5CA9C\n2020-01-03\tuser2\t4410165F7FE8E0C081B3FB7B'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb8_key_32_iv_16 = r"""'2020-01-01\tuser0\t1C07B443BB7D7D60E9999C1D\n2020-01-02\tuser1\t1C07B443BA9674A3F68FF3FE\n2020-01-03\tuser2\t1C07B443B95F4B68161A616F'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb8_key_64_iv_64 = r"""'2020-01-01\tuser0\tA6D2368A5F177157D73FBD9D\n2020-01-02\tuser1\tA6D2368A5E695ADF99475359\n2020-01-03\tuser2\tA6D2368A5DB96AFD43311124'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb128_key_16_iv_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb128_key_24_iv_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb128_key_32_iv_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb128_key_16_iv_16 = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb128_key_24_iv_24 = r"""'2020-01-01\tuser0\t2E046787D9EFFED25D69C908\n2020-01-02\tuser1\t2E046787D8EFFED25D69C908\n2020-01-03\tuser2\t2E046787DBEFFED25D69C908'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb128_key_24_iv_16 = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb128_key_32_iv_32 = r"""'2020-01-01\tuser0\t44D3EB069FF443A121590842\n2020-01-02\tuser1\t44D3EB069EF443A121590842\n2020-01-03\tuser2\t44D3EB069DF443A121590842'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb128_key_32_iv_16 = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb128_key_64_iv_64 = r"""'2020-01-01\tuser0\tA69DAA2E8B265618D25D5FE4\n2020-01-02\tuser1\tA69DAA2E8A265618D25D5FE4\n2020-01-03\tuser2\tA69DAA2E89265618D25D5FE4'"""
aes_encrypt_mysql_input_example_mode_aes_128_ofb_key_16_iv_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
aes_encrypt_mysql_input_example_mode_aes_192_ofb_key_24_iv_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
aes_encrypt_mysql_input_example_mode_aes_256_ofb_key_32_iv_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
aes_encrypt_mysql_input_example_mode_aes_128_ofb_key_16_iv_16 = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
aes_encrypt_mysql_input_example_mode_aes_128_ofb_key_24_iv_24 = r"""'2020-01-01\tuser0\t2E046787D9EFFED25D69C908\n2020-01-02\tuser1\t2E046787D8EFFED25D69C908\n2020-01-03\tuser2\t2E046787DBEFFED25D69C908'"""
aes_encrypt_mysql_input_example_mode_aes_192_ofb_key_24_iv_16 = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
aes_encrypt_mysql_input_example_mode_aes_192_ofb_key_32_iv_32 = r"""'2020-01-01\tuser0\t44D3EB069FF443A121590842\n2020-01-02\tuser1\t44D3EB069EF443A121590842\n2020-01-03\tuser2\t44D3EB069DF443A121590842'"""
aes_encrypt_mysql_input_example_mode_aes_256_ofb_key_32_iv_16 = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""
aes_encrypt_mysql_input_example_mode_aes_256_ofb_key_64_iv_64 = r"""'2020-01-01\tuser0\tA69DAA2E8B265618D25D5FE4\n2020-01-02\tuser1\tA69DAA2E8A265618D25D5FE4\n2020-01-03\tuser2\tA69DAA2E89265618D25D5FE4'"""
encrypt_input_example_mode_aes_128_ecb_iv_None_aad_None = r"""'2020-01-01\tuser0\t8C9598C3C8D8AC241DDF0D1B22020104\n2020-01-02\tuser1\tC5ECE31A240069D8F169B9F8CF687779\n2020-01-03\tuser2\t9FCFA4B05DD49D2B24BA61091F963CE3'"""
encrypt_input_example_mode_aes_192_ecb_iv_None_aad_None = r"""'2020-01-01\tuser0\t897F14C4E497962D986A7E7EA57AA043\n2020-01-02\tuser1\tED84AF2B3447113DA451E4577F649E36\n2020-01-03\tuser2\t4976F9D5AE195E61694A9ADCDD8A076F'"""
encrypt_input_example_mode_aes_256_ecb_iv_None_aad_None = r"""'2020-01-01\tuser0\tBABD6C071FDEE1C9A33877006FBB0BE6\n2020-01-02\tuser1\t7753E81D1DB9DC91FC8148E88B3E9526\n2020-01-03\tuser2\tD77D1A8DF82C2273BF0D19A14526531F'"""
encrypt_input_example_mode_aes_128_cbc_iv_None_aad_None = r"""'2020-01-01\tuser0\t8C9598C3C8D8AC241DDF0D1B22020104\n2020-01-02\tuser1\tC5ECE31A240069D8F169B9F8CF687779\n2020-01-03\tuser2\t9FCFA4B05DD49D2B24BA61091F963CE3'"""
encrypt_input_example_mode_aes_192_cbc_iv_None_aad_None = r"""'2020-01-01\tuser0\t897F14C4E497962D986A7E7EA57AA043\n2020-01-02\tuser1\tED84AF2B3447113DA451E4577F649E36\n2020-01-03\tuser2\t4976F9D5AE195E61694A9ADCDD8A076F'"""
encrypt_input_example_mode_aes_256_cbc_iv_None_aad_None = r"""'2020-01-01\tuser0\tBABD6C071FDEE1C9A33877006FBB0BE6\n2020-01-02\tuser1\t7753E81D1DB9DC91FC8148E88B3E9526\n2020-01-03\tuser2\tD77D1A8DF82C2273BF0D19A14526531F'"""
encrypt_input_example_mode_aes_128_cbc_iv_16_aad_None = r"""'2020-01-01\tuser0\tFC93C1D5E5E3B054C1F3A5692AAC0A61\n2020-01-02\tuser1\tD6DBC76ABCB14B7C6D93F1A5FCA66B9C\n2020-01-03\tuser2\tD4F4158A650D01EB505CC72EFE455486'"""
encrypt_input_example_mode_aes_192_cbc_iv_16_aad_None = r"""'2020-01-01\tuser0\t0E3BAF7F4E0BFCFFAE2589B67F71E277\n2020-01-02\tuser1\t2581CCEE9ABE5770480901D65B3D9222\n2020-01-03\tuser2\tED9F3BD8DB12FDF9F2462FFA572361E7'"""
encrypt_input_example_mode_aes_256_cbc_iv_16_aad_None = r"""'2020-01-01\tuser0\t72AC7BA6F283EA94A3C33C4D3E51C7D3\n2020-01-02\tuser1\tDACBBE79062F1C721A01CEEE3E85524F\n2020-01-03\tuser2\tFF5A09D19E5EB2ADD94581308588E44A'"""
encrypt_input_example_mode_aes_128_cfb1_iv_None_aad_None = r"""'2020-01-01\tuser0\t750BE8662F57A095EC0E610C\n2020-01-02\tuser1\t750BE8662E444A6284C0FC72\n2020-01-03\tuser2\t750BE8662C000B61CDCF1C94'"""
encrypt_input_example_mode_aes_192_cfb1_iv_None_aad_None = r"""'2020-01-01\tuser0\t5DCC67A043EB776D8B7F5B70\n2020-01-02\tuser1\t5DCC67A042B46DFCC10EFD66\n2020-01-03\tuser2\t5DCC67A040243A8C1346D2DD'"""
encrypt_input_example_mode_aes_256_cfb1_iv_None_aad_None = r"""'2020-01-01\tuser0\tFAAC1A7D2CE844F8DEB4C44E\n2020-01-02\tuser1\tFAAC1A7D2DF85A43828C0FF8\n2020-01-03\tuser2\tFAAC1A7D2FC7582CCEFCF330'"""
encrypt_input_example_mode_aes_128_cfb1_iv_16_aad_None = r"""'2020-01-01\tuser0\t7670A865D13B1B65AD46F8ED\n2020-01-02\tuser1\t7670A865D046007A1E218286\n2020-01-03\tuser2\t7670A865D2E5B091492ECCFB'"""
encrypt_input_example_mode_aes_192_cfb1_iv_16_aad_None = r"""'2020-01-01\tuser0\t7F38C051539074C0A635C937\n2020-01-02\tuser1\t7F38C051520A30DFACBE9564\n2020-01-03\tuser2\t7F38C051500DA29FF0E7B799'"""
encrypt_input_example_mode_aes_256_cfb1_iv_16_aad_None = r"""'2020-01-01\tuser0\t0A216A58A5C0A33215E8E722\n2020-01-02\tuser1\t0A216A58A4E94067ABF030B6\n2020-01-03\tuser2\t0A216A58A6822CAB0318C632'"""
encrypt_input_example_mode_aes_128_cfb8_iv_None_aad_None = r"""'2020-01-01\tuser0\t650D96B9698D20DB12E2E437\n2020-01-02\tuser1\t650D96B968F00D16ABF2852E\n2020-01-03\tuser2\t650D96B96B8141F425E60D6B'"""
encrypt_input_example_mode_aes_192_cfb8_iv_None_aad_None = r"""'2020-01-01\tuser0\t72C4724B2F528724A12041C0\n2020-01-02\tuser1\t72C4724B2EF3C6A6FF9E09A9\n2020-01-03\tuser2\t72C4724B2D6EAB1D47709E15'"""
encrypt_input_example_mode_aes_256_cfb8_iv_None_aad_None = r"""'2020-01-01\tuser0\tC5FD6C94961765ED204F2BCA\n2020-01-02\tuser1\tC5FD6C9497AB1C1AF1DE671C\n2020-01-03\tuser2\tC5FD6C949491F4A3EA5069B3'"""
encrypt_input_example_mode_aes_128_cfb8_iv_16_aad_None = r"""'2020-01-01\tuser0\t471D217E9CA3593FFEC955C8\n2020-01-02\tuser1\t471D217E9D7F484D85F81F19\n2020-01-03\tuser2\t471D217E9EBBFD2EA9841008'"""
encrypt_input_example_mode_aes_192_cfb8_iv_16_aad_None = r"""'2020-01-01\tuser0\t1D98EFFAEB9907457BD3FCB2\n2020-01-02\tuser1\t1D98EFFAEA2D930825C6AE22\n2020-01-03\tuser2\t1D98EFFAE92C1D018438B98B'"""
encrypt_input_example_mode_aes_256_cfb8_iv_16_aad_None = r"""'2020-01-01\tuser0\t1C07B443BB7D7D60E9999C1D\n2020-01-02\tuser1\t1C07B443BA9674A3F68FF3FE\n2020-01-03\tuser2\t1C07B443B95F4B68161A616F'"""
encrypt_input_example_mode_aes_128_cfb128_iv_None_aad_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
encrypt_input_example_mode_aes_192_cfb128_iv_None_aad_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
encrypt_input_example_mode_aes_256_cfb128_iv_None_aad_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
encrypt_input_example_mode_aes_128_cfb128_iv_16_aad_None = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
encrypt_input_example_mode_aes_192_cfb128_iv_16_aad_None = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
encrypt_input_example_mode_aes_256_cfb128_iv_16_aad_None = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""
encrypt_input_example_mode_aes_128_ofb_iv_None_aad_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
encrypt_input_example_mode_aes_192_ofb_iv_None_aad_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
encrypt_input_example_mode_aes_256_ofb_iv_None_aad_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
encrypt_input_example_mode_aes_128_ofb_iv_16_aad_None = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
encrypt_input_example_mode_aes_192_ofb_iv_16_aad_None = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
encrypt_input_example_mode_aes_256_ofb_iv_16_aad_None = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""
encrypt_input_example_mode_aes_128_gcm_iv_12_aad_None = r"""'2020-01-01\tuser0\t98E5A430C4A01C4429B0F37A4B3CDBC2BDB491651A36D7F904E231E0\n2020-01-02\tuser1\t98E5A430C5A01C4429B0F37A6E108322C2863C1ABF9BC7098CD369DB\n2020-01-03\tuser2\t98E5A430C6A01C4429B0F37A01646A0243D1CB9A516CF61814808196'"""
encrypt_input_example_mode_aes_192_gcm_iv_12_aad_None = r"""'2020-01-01\tuser0\t3F89C3B657596C86202B59F4350807B364DA1E94682EAB679617575D\n2020-01-02\tuser1\t3F89C3B656596C86202B59F4FA03602ED37788B312FDE2AFDBB7F097\n2020-01-03\tuser2\t3F89C3B655596C86202B59F4691EC8880B8132DA9D8838F70D5618C8'"""
encrypt_input_example_mode_aes_256_gcm_iv_12_aad_None = r"""'2020-01-01\tuser0\t23B80948CCDB54DC6D0B62F215132A07B30BA6F15593B4F946726B11\n2020-01-02\tuser1\t23B80948CDDB54DC6D0B62F2A01C1BAE07B8D6B26F60116040CDDB55\n2020-01-03\tuser2\t23B80948CEDB54DC6D0B62F2BD0D4954DA6D46772074FFCB4B0D0B98'"""
encrypt_input_example_mode_aes_128_gcm_iv_12_aad_True = r"""'2020-01-01\tuser0\t98E5A430C4A01C4429B0F37AF9758E0EA4B44A50A7F964C8E51A913C\n2020-01-02\tuser1\t98E5A430C5A01C4429B0F37ADC59D6EEDB86E72F025474386D2BC907\n2020-01-03\tuser2\t98E5A430C6A01C4429B0F37AB32D3FCE5AD110AFECA34529F578214A'"""
encrypt_input_example_mode_aes_192_gcm_iv_12_aad_True = r"""'2020-01-01\tuser0\t3F89C3B657596C86202B59F4B6C662DFF6347EF3B46C170A2F80E946\n2020-01-02\tuser1\t3F89C3B656596C86202B59F479CD05424199E8D4CEBF5EC262204E8C\n2020-01-03\tuser2\t3F89C3B655596C86202B59F4EAD0ADE4996F52BD41CA849AB4C1A6D3'"""
encrypt_input_example_mode_aes_256_gcm_iv_12_aad_True = r"""'2020-01-01\tuser0\t23B80948CCDB54DC6D0B62F28787710BBF3F9A594C387B9F7CA2372B\n2020-01-02\tuser1\t23B80948CDDB54DC6D0B62F2328840A20B8CEA1A76CBDE067A1D876F\n2020-01-03\tuser2\t23B80948CEDB54DC6D0B62F22F991258D6597ADF39DF30AD71DD57A2'"""
encrypt_input_example_mode_aes_128_ctr_iv_None_aad_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
encrypt_input_example_mode_aes_192_ctr_iv_None_aad_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
encrypt_input_example_mode_aes_256_ctr_iv_None_aad_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
encrypt_input_example_mode_aes_128_ctr_iv_16_aad_None = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
encrypt_input_example_mode_aes_192_ctr_iv_16_aad_None = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
encrypt_input_example_mode_aes_256_ctr_iv_16_aad_None = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""

View File

@ -0,0 +1,634 @@
# -*- coding: utf-8 -*-
import os
from importlib.machinery import SourceFileLoader
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts.helpers import varname
from testflows.asserts import error
from aes_encryption.requirements.requirements import *
from aes_encryption.tests.common import *
@TestOutline
def decrypt(self, ciphertext=None, key=None, mode=None, iv=None, aad=None, exitcode=0, message=None, step=When, cast=None, endcast=None, compare=None, no_checks=False):
"""Execute `decrypt` function with the specified parameters.
"""
params = []
if mode is not None:
params.append(mode)
if ciphertext is not None:
params.append(ciphertext)
if key is not None:
params.append(key)
if iv is not None:
params.append(iv)
if aad is not None:
params.append(aad)
sql = f"decrypt(" + ", ".join(params) + ")"
if cast:
sql = f"{cast}({sql}){endcast or ''}"
if compare:
sql = f"{compare} = {sql}"
sql = f"SELECT {sql}"
return current().context.node.query(sql, step=step, exitcode=exitcode, message=message, no_checks=no_checks)
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_CipherText("1.0"),
)
def invalid_ciphertext(self):
"""Check that `decrypt` function does not crash when invalid
`ciphertext` is passed as the first parameter.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
invalid_ciphertexts = plaintexts
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}"""):
d_iv = None if not iv_len else f"'{iv[:iv_len]}'"
d_aad = None if not aad_len else f"'{aad}'"
for datatype, ciphertext in invalid_ciphertexts:
if datatype in ["NULL"]:
continue
with When(f"invalid ciphertext={ciphertext}"):
if "cfb" in mode or "ofb" in mode or "ctr" in mode:
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, iv=d_iv, aad=d_aad, cast="hex")
else:
with When("I execute decrypt function"):
r = decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, iv=d_iv, aad=d_aad, no_checks=True, step=By)
with Then("exitcode is not zero"):
assert r.exitcode in [198, 36]
with And("exception is present in the output"):
assert "DB::Exception:" in r.output
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_InvalidParameters("1.0")
)
def invalid_parameters(self):
"""Check that `decrypt` function returns an error when
we call it with invalid parameters.
"""
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
with Example("no parameters"):
decrypt(exitcode=42, message="DB::Exception: Incorrect number of arguments for function decrypt provided 0, expected 3 to 5")
with Example("missing key and mode"):
decrypt(ciphertext=ciphertext, exitcode=42,
message="DB::Exception: Incorrect number of arguments for function decrypt provided 1")
with Example("missing mode"):
decrypt(ciphertext=ciphertext, key="'123'", exitcode=42,
message="DB::Exception: Incorrect number of arguments for function decrypt provided 2")
with Example("bad key type - UInt8"):
decrypt(ciphertext=ciphertext, key="123", mode="'aes-128-ecb'", exitcode=43,
message="DB::Exception: Received from localhost:9000. DB::Exception: Illegal type of argument #3")
with Example("bad mode type - forgot quotes"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="aes-128-ecb", exitcode=47,
message="DB::Exception: Missing columns: 'ecb' 'aes' while processing query")
with Example("bad mode type - UInt8"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="128", exitcode=43,
message="DB::Exception: Illegal type of argument #1 'mode'")
with Example("bad iv type - UInt8"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-cbc'", iv='128', exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("bad aad type - UInt8"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-gcm'", iv="'012345678912'", aad="123", exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("iv not valid for mode", requirements=[RQ_SRS008_AES_Decrypt_Function_InitializationVector_NotValidForMode("1.0")]):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-ecb'", iv="'012345678912'", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("iv not valid for mode - size 0", requirements=[RQ_SRS008_AES_Decrypt_Function_InitializationVector_NotValidForMode("1.0")]):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key="'0123456789123456'", mode="'aes-128-ecb'", iv="''", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("aad not valid for mode", requirements=[RQ_SRS008_AES_Decrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")]):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-cbc'", iv="'0123456789123456'", aad="'aad'", exitcode=36,
message="DB::Exception: AAD can be only set for GCM-mode")
with Example("invalid mode value", requirements=[RQ_SRS008_AES_Decrypt_Function_Parameters_Mode_Value_Invalid("1.0")]):
with When("typo in the block algorithm"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-eeb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128-eeb")
with When("typo in the key size"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-127-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-127-ecb")
with When("typo in the aes prefix"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aee-128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aee-128-ecb")
with When("missing last dash"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128ecb")
with When("missing first dash"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes128-ecb")
with When("all capitals"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'AES-128-ECB'", exitcode=36,
message="DB::Exception: Invalid mode: AES-128-ECB")
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Key_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Decrypt_Function_InitializationVector_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Decrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len aad", [
# ECB
("'aes-128-ecb'", 16, None, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ecb'", 24, None, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ecb'", 32, None, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_ECB_KeyAndInitializationVector_Length("1.0"))),
# CBC
("'aes-128-cbc'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cbc'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cbc'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CBC_KeyAndInitializationVector_Length("1.0"))),
# CFB1
("'aes-128-cfb1'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb1'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb1'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CFB1_KeyAndInitializationVector_Length("1.0"))),
# CFB8
("'aes-128-cfb8'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb8'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb8'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CFB8_KeyAndInitializationVector_Length("1.0"))),
# CFB128
("'aes-128-cfb128'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb128'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb128'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CFB128_KeyAndInitializationVector_Length("1.0"))),
# OFB
("'aes-128-ofb'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ofb'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ofb'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_OFB_KeyAndInitializationVector_Length("1.0"))),
# CTR
("'aes-128-ctr'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CTR_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ctr'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CTR_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ctr'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CTR_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s %-10s")
def invalid_key_or_iv_length_for_mode_non_gcm(self, mode, key_len, iv_len, aad):
"""Check that an error is returned when key or iv length does not match
the expected value for the mode.
"""
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len+1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
if iv_len is not None:
with When("iv is too short"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
with When("iv is too long"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
if aad is None:
with When("aad is specified but not needed"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1] if iv_len is not None else ''}'", aad="'AAD'", mode=mode, exitcode=36, message="DB::Exception: AAD can be only set for GCM-mode")
else:
with When("iv is specified but not needed"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode, exitcode=36, message="DB::Exception: {} does not support IV".format(mode.strip("'")))
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Key_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Decrypt_Function_InitializationVector_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Decrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len aad", [
# GCM
("'aes-128-gcm'", 16, 8, "'hello there aad'",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-128-gcm'", 16, None, "'hello there aad'",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-gcm'", 24, 8, "''",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-gcm'", 24, None, "''",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-gcm'", 32, 8, "'a'",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-gcm'", 32, None, "'a'",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_GCM_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s %-10s")
def invalid_key_or_iv_length_for_gcm(self, mode, key_len, iv_len, aad):
"""Check that an error is returned when key or iv length does not match
the expected value for the GCM mode.
"""
ciphertext = "'hello there'"
plaintext = "hello there"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len-1]}'", iv=f"'{iv[:iv_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
ciphertext = "unhex('24AEBFEA049D6F4CF85AAB8CADEDF39CCCAA1C3C2AFF99E194789D')"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len+1]}'", iv=f"'{iv[:iv_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
if iv_len is not None:
with When(f"iv is too short"):
ciphertext = "unhex('24AEBFEA049D6F4CF85AAB8CADEDF39CCCAA1C3C2AFF99E194789D')"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=198, message="DB::Exception:")
else:
with When("iv is not specified"):
ciphertext = "unhex('1CD4EC93A4B0C687926E8F8C2AA3B4CE1943D006DAE3A774CB1AE5')"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size 0 != expected size 12")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_AdditionalAuthenticatedData("1.0"),
RQ_SRS008_AES_Decrypt_Function_AdditionalAuthenticationData_Length("1.0")
)
def aad_parameter_types_and_length(self):
"""Check that `decrypt` function accepts `aad` parameter as the fifth argument
of either `String` or `FixedString` types and that the length is not limited.
"""
plaintext = "hello there"
iv = "'012345678912'"
mode = "'aes-128-gcm'"
key = "'0123456789123456'"
with When("aad is specified using String type"):
ciphertext = "unhex('19A1183335B374C626B24208AAEC97F148732CE05621AC87B21526')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="'aad'", message=plaintext)
with When("aad is specified using String with UTF8 characters"):
ciphertext = "unhex('19A1183335B374C626B242C68D9618A8C2664D7B6A3FE978104B39')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="'Gãńdåłf_Thê_Gręât'", message=plaintext)
with When("aad is specified using FixedString type"):
ciphertext = "unhex('19A1183335B374C626B24208AAEC97F148732CE05621AC87B21526')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="toFixedString('aad', 3)", message=plaintext)
with When("aad is specified using FixedString with UTF8 characters"):
ciphertext = "unhex('19A1183335B374C626B242C68D9618A8C2664D7B6A3FE978104B39')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="toFixedString('Gãńdåłf_Thê_Gręât', 24)", message=plaintext)
with When("aad is 0 bytes"):
ciphertext = "unhex('19A1183335B374C626B242DF92BB3F57F5D82BEDF41FD5D49F8BC9')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="''", message=plaintext)
with When("aad is 1 byte"):
ciphertext = "unhex('19A1183335B374C626B242D1BCFC63B09CFE9EAD20285044A01035')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="'1'", message=plaintext)
with When("aad is 256 bytes"):
ciphertext = "unhex('19A1183335B374C626B242355AD3DD2C5D7E36AEECBB847BF9E8A7')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad=f"'{'1' * 256}'", message=plaintext)
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_InitializationVector("1.0")
)
def iv_parameter_types(self):
"""Check that `decrypt` function accepts `iv` parameter as the fourth argument
of either `String` or `FixedString` types.
"""
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("iv is specified using String type"):
decrypt(ciphertext="unhex('F024F9372FA0D8B974894D29FFB8A7F7')", key=key, mode=mode, iv=iv, message="hello there")
with When("iv is specified using String with UTF8 characters"):
decrypt(ciphertext="unhex('7A4EC0FF3796F46BED281F4778ACE1DC')", key=key, mode=mode, iv="'Gãńdåłf_Thê'", message="hello there")
with When("iv is specified using FixedString type"):
decrypt(ciphertext="unhex('F024F9372FA0D8B974894D29FFB8A7F7')", key=key, mode=mode, iv=f"toFixedString({iv}, 16)", message="hello there")
with When("iv is specified using FixedString with UTF8 characters"):
decrypt(ciphertext="unhex('7A4EC0FF3796F46BED281F4778ACE1DC')", key=key, mode=mode, iv=f"toFixedString('Gãńdåłf_Thê', 16)", message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_Key("1.0")
)
def key_parameter_types(self):
"""Check that `decrypt` function accepts `key` parameter as the second argument
of either `String` or `FixedString` types.
"""
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("key is specified using String type"):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=mode, message="hello there")
with When("key is specified using String with UTF8 characters"):
decrypt(ciphertext="unhex('180086AA42AD57B71C706EEC372D0C3D')", key="'Gãńdåłf_Thê'", mode=mode, message="hello there")
with When("key is specified using FixedString type"):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=f"toFixedString({key}, 16)", mode=mode, message="hello there")
with When("key is specified using FixedString with UTF8 characters"):
decrypt(ciphertext="unhex('180086AA42AD57B71C706EEC372D0C3D')", key=f"toFixedString('Gãńdåłf_Thê', 16)", mode=mode, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_Mode("1.0"),
)
def mode_parameter_types(self):
"""Check that `decrypt` function accepts `mode` parameter as the third argument
of either `String` or `FixedString` types.
"""
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("mode is specified using String type"):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=mode, message="hello there")
with When("mode is specified using FixedString type"):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=f"toFixedString({mode}, 12)", message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_ReturnValue("1.0")
)
def return_value(self):
"""Check that `decrypt` functions returns String data type.
"""
ciphertext = "unhex('F024F9372FA0D8B974894D29FFB8A7F7')"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("I get type of the return value"):
sql = "SELECT toTypeName(decrypt(" + mode + "," + ciphertext + "," + key + "," + iv + "))"
r = self.context.node.query(sql)
with Then("type should be String"):
assert r.output.strip() == "String", error()
with When("I get the return value"):
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Syntax("1.0"),
)
def syntax(self):
"""Check that `decrypt` function supports syntax
```sql
decrypt(ciphertext, key, mode, [iv, aad])
```
"""
ciphertext = "19A1183335B374C626B242A6F6E8712E2B64DCDC6A468B2F654614"
sql = f"SELECT decrypt('aes-128-gcm', unhex('{ciphertext}'), '0123456789123456', '012345678912', 'AAD')"
self.context.node.query(sql, step=When, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_CipherText("1.0"),
RQ_SRS008_AES_Decrypt_Function_Parameters_Mode("1.0"),
RQ_SRS008_AES_Decrypt_Function_Parameters_Mode_ValuesFormat("1.0"),
)
def decryption(self):
"""Check that `decrypt` functions accepts `ciphertext` as the second parameter
and `mode` as the first parameter and we can convert the decrypted value into the original
value with the original data type.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
for datatype, plaintext in plaintexts:
requirement = globals().get(f"""RQ_SRS008_AES_Decrypt_Function_Parameters_Mode_Value_{mode.strip("'").replace("-","_").upper()}""")("1.0")
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""",
requirements=[requirement]) as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
cast = None
endcast = None
ciphertext = f"unhex({ciphertext})"
compare = plaintext
if datatype == "IPv4":
cast = "toIPv4(IPv4NumToString(reinterpretAsUInt32"
endcast = "))"
elif datatype in ["DateTime64", "UUID", "IPv6", "LowCardinality", "Enum8", "Enum16", "Decimal32", "Decimal64", "Decimal128", "Array"]:
xfail(reason="no conversion")
elif datatype == "NULL":
ciphertext = "NULL"
cast = "isNull"
compare = None
elif datatype in ["Float32", "Float64", "Date", "DateTime"] or "Int" in datatype:
cast = f"reinterpretAs{datatype}"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
aad=(None if not aad_len else f"'{aad}'"),
cast=cast, endcast=endcast, compare=compare, message="1")
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_Key("1.0")
)
def mismatched_key(self):
"""Check that `decrypt` function returns garbage or an error when key parameter does not match.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
datatype = "String"
plaintext = "'1'"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched key"):
r = decrypt(ciphertext=f"unhex({ciphertext})", key=f"'a{key[:key_len-1]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
aad=(None if not aad_len else f"'{aad}'"), no_checks=True, cast="hex")
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_IV("1.0")
)
def mismatched_iv(self):
"""Check that `decrypt` function returns garbage or an error when iv parameter does not match.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
datatype = "String"
plaintext = "'1'"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
if not iv_len:
continue
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched iv"):
r = decrypt(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mode,
iv=f"'a{iv[:iv_len-1]}'",
aad=(None if not aad_len else f"'{aad}'"), no_checks=True, cast="hex")
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_AAD("1.0")
)
def mismatched_aad(self):
"""Check that `decrypt` function returns garbage or an error when aad parameter does not match.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
datatype = "String"
plaintext = "'1'"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
if not aad_len:
continue
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched aad"):
r = decrypt(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
aad=(None if not aad_len else f"'a{aad}'"), no_checks=True, cast="hex")
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_Mode("1.0")
)
def mismatched_mode(self):
"""Check that `decrypt` function returns garbage or an error when mode parameter does not match.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
plaintext = hex('Gãńdåłf_Thê_Gręât'.encode("utf-8"))
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} datatype=utf8string iv={iv_len} aad={aad_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
for mismatched_mode, _, _, _ in modes:
if mismatched_mode == mode:
continue
with When(f"I decrypt using mismatched mode {mismatched_mode}"):
r = decrypt(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mismatched_mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
aad=(None if not aad_len else f"'{aad}'"), no_checks=True, cast="hex")
with Then("exitcode shoud be 0 or 36 or 198"):
assert r.exitcode in [0, 36, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
condition = "Exception: Failed to decrypt" in output \
or 'Exception: Invalid key size' in output \
or output != plaintext
assert condition, error()
@TestFeature
@Name("decrypt")
@Requirements(
RQ_SRS008_AES_Decrypt_Function("1.0")
)
def feature(self, node="clickhouse1"):
"""Check the behavior of the `decrypt` function.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,521 @@
# -*- coding: utf-8 -*-
import os
from importlib.machinery import SourceFileLoader
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts.helpers import varname
from testflows.asserts import error
from aes_encryption.requirements.requirements import *
from aes_encryption.tests.common import *
@TestOutline
def aes_decrypt_mysql(self, ciphertext=None, key=None, mode=None, iv=None, aad=None, exitcode=0, message=None,
step=When, cast=None, endcast=None, compare=None, no_checks=False):
"""Execute `aes_decrypt_mysql` function with the specified parameters.
"""
params = []
if mode is not None:
params.append(mode)
if ciphertext is not None:
params.append(ciphertext)
if key is not None:
params.append(key)
if iv is not None:
params.append(iv)
if aad is not None:
params.append(aad)
sql = f"aes_decrypt_mysql(" + ", ".join(params) + ")"
if cast:
sql = f"{cast}({sql}){endcast or ''}"
if compare:
sql = f"{compare} = {sql}"
sql = f"SELECT {sql}"
return current().context.node.query(sql, step=step, exitcode=exitcode, message=message, no_checks=no_checks)
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_CipherText("1.0"),
)
def invalid_ciphertext(self):
"""Check that `aes_decrypt_mysql` function does not crash when invalid
`ciphertext` is passed as the first parameter.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
invalid_ciphertexts = plaintexts
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len}"""):
d_iv = None if not iv_len else f"'{iv[:iv_len]}'"
for datatype, ciphertext in invalid_ciphertexts:
if datatype in ["NULL"]:
continue
with When(f"invalid ciphertext={ciphertext}"):
if "cfb" in mode or "ofb" in mode or "ctr" in mode:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, iv=d_iv, cast="hex")
else:
with When("I execute aes_decrypt_mysql function"):
r = aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, iv=d_iv, no_checks=True, step=By)
with Then("exitcode is not zero"):
assert r.exitcode in [198, 36]
with And("exception is present in the output"):
assert "DB::Exception:" in r.output
@TestOutline(Scenario)
@Examples("mode", [
("'aes-128-gcm'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_128_GCM_Error("1.0"))),
("'aes-192-gcm'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_192_GCM_Error("1.0"))),
("'aes-256-gcm'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_256_GCM_Error("1.0"))),
("'aes-128-ctr'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_128_CTR_Error("1.0"))),
("'aes-192-ctr'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_192_CTR_Error("1.0"))),
("'aes-256-ctr'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_256_CTR_Error("1.0"))),
])
def unsupported_modes(self, mode):
"""Check that `aes_decrypt_mysql` function returns an error when unsupported modes are specified.
"""
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
aes_decrypt_mysql(ciphertext=ciphertext, mode=mode, key=f"'{'1'* 32}'", exitcode=36, message="DB::Exception: Unsupported cipher mode")
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_InvalidParameters("1.0")
)
def invalid_parameters(self):
"""Check that `aes_decrypt_mysql` function returns an error when
we call it with invalid parameters.
"""
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
with Example("no parameters"):
aes_decrypt_mysql(exitcode=42, message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 0, expected 3 to 4")
with Example("missing key and mode"):
aes_decrypt_mysql(ciphertext=ciphertext, exitcode=42,
message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 1")
with Example("missing mode"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'123'", exitcode=42,
message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 2")
with Example("bad key type - UInt8"):
aes_decrypt_mysql(ciphertext=ciphertext, key="123", mode="'aes-128-ecb'", exitcode=43,
message="DB::Exception: Received from localhost:9000. DB::Exception: Illegal type of argument #3")
with Example("bad mode type - forgot quotes"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="aes-128-ecb", exitcode=47,
message="DB::Exception: Missing columns: 'ecb' 'aes' while processing query")
with Example("bad mode type - UInt8"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="128", exitcode=43,
message="DB::Exception: Illegal type of argument #1 'mode'")
with Example("bad iv type - UInt8"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-cbc'", iv='128', exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("iv not valid for mode", requirements=[RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_NotValidForMode("1.0")]):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key="'0123456789123456'", mode="'aes-128-ecb'", iv="'012345678912'", exitcode=0,
message=None)
with Example("iv not valid for mode - size 0", requirements=[RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_NotValidForMode("1.0")]):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key="'0123456789123456'", mode="'aes-128-ecb'", iv="''", exitcode=0,
message=None)
with Example("aad passed by mistake"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-cbc'", iv="'0123456789123456'", aad="'aad'", exitcode=42,
message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 5")
with Example("aad passed by mistake type - UInt8"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-gcm'", iv="'012345678912'", aad="123", exitcode=42,
message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 5")
with Example("invalid mode value", requirements=[RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_Invalid("1.0")]):
with When("typo in the block algorithm"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-eeb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128-eeb")
with When("typo in the key size"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-127-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-127-ecb")
with When("typo in the aes prefix"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aee-128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aee-128-ecb")
with When("missing last dash"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128ecb")
with When("missing first dash"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes128-ecb")
with When("all capitals"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'AES-128-ECB'", exitcode=36,
message="DB::Exception: Invalid mode: AES-128-ECB")
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Key_Length_TooShortError("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_Key_Length_TooLong("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_Length_TooShortError("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_Length_TooLong("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len", [
# ECB
("'aes-128-ecb'", 16, None,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ecb'", 24, None,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ecb'", 32, None,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_ECB_KeyAndInitializationVector_Length("1.0"))),
# CBC
("'aes-128-cbc'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cbc'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cbc'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_CBC_KeyAndInitializationVector_Length("1.0"))),
# CFB1
("'aes-128-cfb1'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb1'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb1'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_CFB1_KeyAndInitializationVector_Length("1.0"))),
# CFB8
("'aes-128-cfb8'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb8'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb8'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_CFB8_KeyAndInitializationVector_Length("1.0"))),
# CFB128
("'aes-128-cfb128'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb128'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb128'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_CFB128_KeyAndInitializationVector_Length("1.0"))),
# OFB
("'aes-128-ofb'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ofb'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ofb'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_OFB_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s")
def key_or_iv_length_for_mode(self, mode, key_len, iv_len):
"""Check that key or iv length for mode.
"""
ciphertext = "unhex('31F4C847CAB873AB34584368E3E85E3A')"
if mode == "'aes-128-ecb'":
ciphertext = "unhex('31F4C847CAB873AB34584368E3E85E3B')"
elif mode == "'aes-192-ecb'":
ciphertext = "unhex('073868ECDECA94133A61A0FFA282E877')"
elif mode == "'aes-256-ecb'":
ciphertext = "unhex('1729E5354D6EC44D89900ABDB09DC297')"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
if "ecb" in mode or "cbc" in mode:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len+1]}'", mode=mode, exitcode=198, message="DB::Exception: Failed to decrypt")
else:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len+1]}'", mode=mode, cast="hex")
if iv_len is not None:
with When("iv is too short"):
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
with When("iv is too long"):
if "ecb" in mode or "cbc" in mode:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode, exitcode=198, message="DB::Exception: Failed to decrypt")
else:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode, cast="hex")
else:
with When("iv is specified but not needed"):
if "ecb" in mode or "cbc" in mode:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode, exitcode=198, message="DB::Exception: Failed to decrypt")
else:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode)
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_InitializationVector("1.0")
)
def iv_parameter_types(self):
"""Check that `aes_decrypt_mysql` function accepts `iv` parameter as the fourth argument
of either `String` or `FixedString` types.
"""
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("iv is specified using String type"):
aes_decrypt_mysql(ciphertext="unhex('F024F9372FA0D8B974894D29FFB8A7F7')", key=key, mode=mode, iv=iv, message="hello there")
with When("iv is specified using String with UTF8 characters"):
aes_decrypt_mysql(ciphertext="unhex('7A4EC0FF3796F46BED281F4778ACE1DC')", key=key, mode=mode, iv="'Gãńdåłf_Thê'", message="hello there")
with When("iv is specified using FixedString type"):
aes_decrypt_mysql(ciphertext="unhex('F024F9372FA0D8B974894D29FFB8A7F7')", key=key, mode=mode, iv=f"toFixedString({iv}, 16)", message="hello there")
with When("iv is specified using FixedString with UTF8 characters"):
aes_decrypt_mysql(ciphertext="unhex('7A4EC0FF3796F46BED281F4778ACE1DC')", key=key, mode=mode, iv=f"toFixedString('Gãńdåłf_Thê', 16)", message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Key("1.0")
)
def key_parameter_types(self):
"""Check that `aes_decrypt` function accepts `key` parameter as the second argument
of either `String` or `FixedString` types.
"""
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("key is specified using String type"):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=mode, message="hello there")
with When("key is specified using String with UTF8 characters"):
aes_decrypt_mysql(ciphertext="unhex('180086AA42AD57B71C706EEC372D0C3D')", key="'Gãńdåłf_Thê'", mode=mode, message="hello there")
with When("key is specified using FixedString type"):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=f"toFixedString({key}, 16)", mode=mode, message="hello there")
with When("key is specified using FixedString with UTF8 characters"):
aes_decrypt_mysql(ciphertext="unhex('180086AA42AD57B71C706EEC372D0C3D')", key=f"toFixedString('Gãńdåłf_Thê', 16)", mode=mode, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode("1.0"),
)
def mode_parameter_types(self):
"""Check that `aes_decrypt_mysql` function accepts `mode` parameter as the third argument
of either `String` or `FixedString` types.
"""
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("mode is specified using String type"):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=mode, message="hello there")
with When("mode is specified using FixedString type"):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=f"toFixedString({mode}, 12)", message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_ReturnValue("1.0")
)
def return_value(self):
"""Check that `aes_decrypt_mysql` functions returns String data type.
"""
ciphertext = "unhex('F024F9372FA0D8B974894D29FFB8A7F7')"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("I get type of the return value"):
sql = "SELECT toTypeName(aes_decrypt_mysql(" + mode + "," + ciphertext + "," + key + "," + iv + "))"
r = self.context.node.query(sql)
with Then("type should be String"):
assert r.output.strip() == "String", error()
with When("I get the return value"):
aes_decrypt_mysql(ciphertext=ciphertext, key=key, mode=mode, iv=iv, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Syntax("1.0"),
)
def syntax(self):
"""Check that `aes_decrypt_mysql` function supports syntax
```sql
aes_decrypt_mysql(ciphertext, key, mode, [iv])
```
"""
ciphertext = "70FE78410D6EE237C2DE4A"
sql = f"SELECT aes_decrypt_mysql('aes-128-ofb', unhex('{ciphertext}'), '0123456789123456', '0123456789123456')"
self.context.node.query(sql, step=When, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_CipherText("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_ValuesFormat("1.0"),
)
def decryption(self):
"""Check that `aes_decrypt_mysql` functions accepts `mode` as the first parameter
and `ciphertext` as the second parameter and we can convert the decrypted value into the original
value with the original data type.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt_mysql.py.encrypt_mysql.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
for datatype, plaintext in plaintexts:
requirement = globals().get(f"""RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_{mode.strip("'").replace("-","_").upper()}""")("1.0")
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} key={key_len} iv={iv_len}""",
requirements=[requirement]) as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
cast = None
endcast = None
ciphertext = f"unhex({ciphertext})"
compare = plaintext
if datatype == "IPv4":
cast = "toIPv4(IPv4NumToString(reinterpretAsUInt32"
endcast = "))"
elif datatype in ["DateTime64", "UUID", "IPv6", "LowCardinality", "Enum8", "Enum16", "Decimal32", "Decimal64", "Decimal128", "Array"]:
xfail(reason="no conversion")
elif datatype == "NULL":
ciphertext = "NULL"
cast = "isNull"
compare = None
elif datatype in ["Float32", "Float64", "Date", "DateTime"] or "Int" in datatype:
cast = f"reinterpretAs{datatype}"
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
cast=cast, endcast=endcast, compare=compare, message="1")
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_Key("1.0")
)
def mismatched_key(self):
"""Check that `aes_decrypt_mysql` function returns garbage or an error when key parameter does not match.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt_mysql.py.encrypt_mysql.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} datatype=String key={key_len} iv={iv_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched key"):
r = aes_decrypt_mysql(ciphertext=f"unhex({ciphertext})", key=f"'a{key[:key_len-1]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
cast="hex", no_checks=True)
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_IV("1.0")
)
def mismatched_iv(self):
"""Check that `aes_decrypt_mysql` function returns garbage or an error when iv parameter does not match.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt_mysql.py.encrypt_mysql.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
if not iv_len:
continue
with Example(f"""mode={mode.strip("'")} datatype=String key={key_len} iv={iv_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched key"):
r = aes_decrypt_mysql(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mode,
iv=f"'a{iv[:iv_len-1]}'",
cast="hex", no_checks=True)
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_Mode("1.0")
)
def mismatched_mode(self):
"""Check that `aes_decrypt_mysql` function returns garbage or an error when mode parameter does not match.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
plaintext = hex('Gãńdåłf_Thê_Gręât'.encode("utf-8"))
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt_mysql.py.encrypt_mysql.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
if not iv_len:
continue
with Example(f"""mode={mode.strip("'")} datatype=utf8string key={key_len} iv={iv_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
for mismatched_mode, _, _ in mysql_modes:
if mismatched_mode == mode:
continue
with When(f"I decrypt using a mismatched mode {mismatched_mode}"):
r = aes_decrypt_mysql(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mismatched_mode,
iv=f"'{iv[:iv_len]}'",
cast="hex", no_checks=True)
with Then("exitcode shoud be 0 or 36 or 198"):
assert r.exitcode in [0, 36, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != plaintext, error()
@TestFeature
@Name("decrypt_mysql")
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function("1.0")
)
def feature(self, node="clickhouse1"):
"""Check the behavior of the `aes_decrypt_mysql` function.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,408 @@
# -*- coding: utf-8 -*-
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts import values, error, snapshot
from aes_encryption.requirements.requirements import *
from aes_encryption.tests.common import *
@TestOutline
def encrypt(self, plaintext=None, key=None, mode=None, iv=None, aad=None, exitcode=0, message=None, step=When):
"""Execute `encrypt` function with the specified parameters.
"""
params = []
if mode is not None:
params.append(mode)
if plaintext is not None:
params.append(plaintext)
if key is not None:
params.append(key)
if iv is not None:
params.append(iv)
if aad is not None:
params.append(aad)
sql = "SELECT hex(encrypt(" + ", ".join(params) + "))"
return current().context.node.query(sql, step=step, exitcode=exitcode, message=message)
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_InvalidParameters("1.0")
)
def invalid_parameters(self):
"""Check that `encrypt` function returns an error when
we call it with invalid parameters.
"""
with Example("no parameters"):
encrypt(exitcode=42, message="DB::Exception: Incorrect number of arguments for function encrypt provided 0, expected 3 to 5")
with Example("missing key and mode"):
encrypt(plaintext="'hello there'", exitcode=42, message="DB::Exception: Incorrect number of arguments for function encrypt provided 1")
with Example("missing mode"):
encrypt(plaintext="'hello there'", key="'123'", exitcode=42, message="DB::Exception: Incorrect number of arguments for function encrypt provided 2")
with Example("bad key type - UInt8"):
encrypt(plaintext="'hello there'", key="123", mode="'aes-128-ecb'", exitcode=43,
message="DB::Exception: Received from localhost:9000. DB::Exception: Illegal type of argument #3")
with Example("bad mode type - forgot quotes"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="aes-128-ecb", exitcode=47,
message="DB::Exception: Missing columns: 'ecb' 'aes' while processing query")
with Example("bad mode type - UInt8"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="128", exitcode=43,
message="DB::Exception: Illegal type of argument #1 'mode'")
with Example("bad iv type - UInt8"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-cbc'", iv='128', exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("bad aad type - UInt8"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-gcm'", iv="'012345678912'", aad="123", exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("iv not valid for mode", requirements=[RQ_SRS008_AES_Encrypt_Function_InitializationVector_NotValidForMode("1.0")]):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-ecb'", iv="'012345678912'", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("iv not valid for mode - size 0", requirements=[RQ_SRS008_AES_Encrypt_Function_InitializationVector_NotValidForMode("1.0")]):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-ecb'", iv="''", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("aad not valid for mode", requirements=[RQ_SRS008_AES_Encrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")]):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-cbc'", iv="'0123456789123456'", aad="'aad'", exitcode=36,
message="DB::Exception: AAD can be only set for GCM-mode")
with Example("invalid mode value", requirements=[RQ_SRS008_AES_Encrypt_Function_Parameters_Mode_Value_Invalid("1.0")]):
with When("typo in the block algorithm"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-eeb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128-eeb")
with When("typo in the key size"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-127-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-127-ecb")
with When("typo in the aes prefix"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aee-128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aee-128-ecb")
with When("missing last dash"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128ecb")
with When("missing first dash"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes128-ecb")
with When("all capitals"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'AES-128-ECB'", exitcode=36,
message="DB::Exception: Invalid mode: AES-128-ECB")
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Key_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Encrypt_Function_InitializationVector_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Encrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len aad", [
# ECB
("'aes-128-ecb'", 16, None, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ecb'", 24, None, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ecb'", 32, None, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_ECB_KeyAndInitializationVector_Length("1.0"))),
# CBC
("'aes-128-cbc'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cbc'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cbc'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CBC_KeyAndInitializationVector_Length("1.0"))),
# CFB1
("'aes-128-cfb1'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb1'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb1'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CFB1_KeyAndInitializationVector_Length("1.0"))),
# CFB8
("'aes-128-cfb8'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb8'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb8'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CFB8_KeyAndInitializationVector_Length("1.0"))),
# CFB128
("'aes-128-cfb128'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb128'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb128'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CFB128_KeyAndInitializationVector_Length("1.0"))),
# OFB
("'aes-128-ofb'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ofb'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ofb'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_OFB_KeyAndInitializationVector_Length("1.0"))),
# CTR
("'aes-128-ctr'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CTR_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ctr'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CTR_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ctr'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CTR_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s %-10s")
def invalid_key_or_iv_length_for_mode_non_gcm(self, mode, key_len, iv_len, aad):
"""Check that an error is returned when key or iv length does not match
the expected value for the mode.
"""
plaintext = "'hello there'"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len+1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
if iv_len is not None:
with When("iv is too short"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
with When("iv is too long"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
if aad is None:
with When("aad is specified but not needed"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1] if iv_len is not None else ''}'", aad="'AAD'", mode=mode, exitcode=36, message="DB::Exception: AAD can be only set for GCM-mode")
else:
with When("iv is specified but not needed"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode, exitcode=36, message="DB::Exception: {} does not support IV".format(mode.strip("'")))
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Key_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Encrypt_Function_InitializationVector_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Encrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len aad", [
# GCM
("'aes-128-gcm'", 16, 8, "'hello there aad'",
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-gcm'", 24, 8, "''",
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-gcm'", 32, 8, "'a'",
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_GCM_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s %-10s")
def invalid_key_or_iv_length_for_gcm(self, mode, key_len, iv_len, aad):
"""Check that an error is returned when key or iv length does not match
the expected value for the GCM mode.
"""
plaintext = "'hello there'"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len-1]}'", iv=f"'{iv[:iv_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len+1]}'", iv=f"'{iv[:iv_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
if iv_len is not None:
with When(f"iv is too short"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=198, message="DB::Exception:")
else:
with When("iv is not specified"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
if aad is not None:
with When(f"aad is {aad}"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len]}'", aad=f"{aad}", mode=mode)
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_AdditionalAuthenticatedData("1.0"),
RQ_SRS008_AES_Encrypt_Function_AdditionalAuthenticationData_Length("1.0")
)
def aad_parameter_types_and_length(self):
"""Check that `encrypt` function accepts `aad` parameter as the fifth argument
of either `String` or `FixedString` types and that the length is not limited.
"""
plaintext = "'hello there'"
iv = "'012345678912'"
mode = "'aes-128-gcm'"
key = "'0123456789123456'"
with When("aad is specified using String type"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="'aad'", message="19A1183335B374C626B24208AAEC97F148732CE05621AC87B21526")
with When("aad is specified using String with UTF8 characters"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="'Gãńdåłf_Thê_Gręât'", message="19A1183335B374C626B242C68D9618A8C2664D7B6A3FE978104B39")
with When("aad is specified using FixedString type"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="toFixedString('aad', 3)", message="19A1183335B374C626B24208AAEC97F148732CE05621AC87B21526")
with When("aad is specified using FixedString with UTF8 characters"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="toFixedString('Gãńdåłf_Thê_Gręât', 24)", message="19A1183335B374C626B242C68D9618A8C2664D7B6A3FE978104B39")
with When("aad is 0 bytes"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="''", message="19A1183335B374C626B242DF92BB3F57F5D82BEDF41FD5D49F8BC9")
with When("aad is 1 byte"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="'1'", message="19A1183335B374C626B242D1BCFC63B09CFE9EAD20285044A01035")
with When("aad is 256 bytes"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad=f"'{'1' * 256}'", message="19A1183335B374C626B242355AD3DD2C5D7E36AEECBB847BF9E8A7")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_InitializationVector("1.0")
)
def iv_parameter_types(self):
"""Check that `encrypt` function accepts `iv` parameter as the fourth argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("iv is specified using String type"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, message="F024F9372FA0D8B974894D29FFB8A7F7")
with When("iv is specified using String with UTF8 characters"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv="'Gãńdåłf_Thê'", message="7A4EC0FF3796F46BED281F4778ACE1DC")
with When("iv is specified using FixedString type"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=f"toFixedString({iv}, 16)", message="F024F9372FA0D8B974894D29FFB8A7F7")
with When("iv is specified using FixedString with UTF8 characters"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv="toFixedString('Gãńdåłf_Thê', 16)", message="7A4EC0FF3796F46BED281F4778ACE1DC")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_Key("1.0")
)
def key_parameter_types(self):
"""Check that `encrypt` function accepts `key` parameter as the second argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("key is specified using String type"):
encrypt(plaintext=plaintext, key=key, mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("key is specified using String with UTF8 characters"):
encrypt(plaintext=plaintext, key="'Gãńdåłf_Thê'", mode=mode, message="180086AA42AD57B71C706EEC372D0C3D")
with When("key is specified using FixedString type"):
encrypt(plaintext=plaintext, key=f"toFixedString({key}, 16)", mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("key is specified using FixedString with UTF8 characters"):
encrypt(plaintext=plaintext, key="toFixedString('Gãńdåłf_Thê', 16)", mode=mode, message="180086AA42AD57B71C706EEC372D0C3D")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_Mode("1.0"),
)
def mode_parameter_types(self):
"""Check that `encrypt` function accepts `mode` parameter as the third argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("mode is specified using String type"):
encrypt(plaintext=plaintext, key=key, mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("mode is specified using FixedString type"):
encrypt(plaintext=plaintext, key=key, mode=f"toFixedString({mode}, 12)", message="49C9ADB81BA9B58C485E7ADB90E70576")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_PlainText("1.0"),
RQ_SRS008_AES_Encrypt_Function_Parameters_Mode("1.0"),
RQ_SRS008_AES_Encrypt_Function_Parameters_Mode_ValuesFormat("1.0"),
)
def encryption(self):
"""Check that `encrypt` functions accepts `plaintext` as the second parameter
with any data type and `mode` as the first parameter.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
for datatype, plaintext in plaintexts:
requirement = globals().get(f"""RQ_SRS008_AES_Encrypt_Function_Parameters_Mode_Value_{mode.strip("'").replace("-","_").upper()}""")("1.0")
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""",
requirements=[requirement]) as example:
r = encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"), aad=(None if not aad_len else f"'{aad}'"))
with Then("I check output against snapshot"):
with values() as that:
example_name = basename(example.name)
assert that(snapshot(r.output.strip(), "encrypt", name=f"example_{example_name.replace(' ', '_')}")), error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_ReturnValue("1.0")
)
def return_value(self):
"""Check that `encrypt` functions returns String data type.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("I get type of the return value"):
sql = "SELECT toTypeName(encrypt(" + mode + "," + plaintext + "," + key + "," + iv + "))"
r = self.context.node.query(sql)
with Then("type should be String"):
assert r.output.strip() == "String", error()
with When("I get return ciphertext as hex"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, message="F024F9372FA0D8B974894D29FFB8A7F7")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Syntax("1.0"),
)
def syntax(self):
"""Check that `encrypt` function supports syntax
```sql
encrypt(plaintext, key, mode, [iv, aad])
```
"""
sql = "SELECT hex(encrypt('aes-128-gcm', 'hello there', '0123456789123456', '012345678912', 'AAD'))"
self.context.node.query(sql, step=When, message="19A1183335B374C626B242A6F6E8712E2B64DCDC6A468B2F654614")
@TestFeature
@Name("encrypt")
@Requirements(
RQ_SRS008_AES_Encrypt_Function("1.0")
)
def feature(self, node="clickhouse1"):
"""Check the behavior of the `encrypt` function.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,326 @@
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts import values, error, snapshot
from aes_encryption.requirements.requirements import *
from aes_encryption.tests.common import *
@TestOutline
def aes_encrypt_mysql(self, plaintext=None, key=None, mode=None, iv=None, exitcode=0, message=None, step=When):
"""Execute `aes_encrypt_mysql` function with the specified parameters.
"""
params = []
if mode is not None:
params.append(mode)
if plaintext is not None:
params.append(plaintext)
if key is not None:
params.append(key)
if iv is not None:
params.append(iv)
sql = "SELECT hex(aes_encrypt_mysql(" + ", ".join(params) + "))"
return current().context.node.query(sql, step=step, exitcode=exitcode, message=message)
@TestOutline(Scenario)
@Examples("mode", [
("'aes-128-gcm'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_128_GCM_Error("1.0"))),
("'aes-192-gcm'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_192_GCM_Error("1.0"))),
("'aes-256-gcm'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_256_GCM_Error("1.0"))),
("'aes-128-ctr'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_128_CTR_Error("1.0"))),
("'aes-192-ctr'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_192_CTR_Error("1.0"))),
("'aes-256-ctr'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_256_CTR_Error("1.0"))),
])
def unsupported_modes(self, mode):
"""Check that `aes_encrypt_mysql` function returns an error when unsupported modes are specified.
"""
aes_encrypt_mysql(plaintext="'hello there'", mode=mode, key=f"'{'1'* 32}'", exitcode=36, message="DB::Exception: Unsupported cipher mode")
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_InvalidParameters("1.0")
)
def invalid_parameters(self):
"""Check that `aes_encrypt_mysql` function returns an error when
we call it with invalid parameters.
"""
with Example("no parameters"):
aes_encrypt_mysql(exitcode=42, message="DB::Exception: Incorrect number of arguments for function aes_encrypt provided 0, expected 3 to 4")
with Example("missing key and mode"):
aes_encrypt_mysql(plaintext="'hello there'", exitcode=42, message="DB::Exception: Incorrect number of arguments for function aes_encrypt_mysql provided 1")
with Example("missing mode"):
aes_encrypt_mysql(plaintext="'hello there'", key="'123'", exitcode=42, message="DB::Exception: Incorrect number of arguments for function aes_encrypt_mysql provided 2")
with Example("bad key type - UInt8"):
aes_encrypt_mysql(plaintext="'hello there'", key="123", mode="'aes-128-ecb'", exitcode=43,
message="DB::Exception: Received from localhost:9000. DB::Exception: Illegal type of argument #3")
with Example("bad mode type - forgot quotes"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="aes-128-ecb", exitcode=47,
message="DB::Exception: Missing columns: 'ecb' 'aes' while processing query")
with Example("bad mode type - UInt8"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="128", exitcode=43,
message="DB::Exception: Illegal type of argument #1 'mode'")
with Example("bad iv type - UInt8"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-cbc'", iv='128', exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("iv not valid for mode", requirements=[RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_NotValidForMode("1.0")]):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-ecb'", iv="'012345678912'", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("iv not valid for mode - size 0", requirements=[RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_NotValidForMode("1.0")]):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-ecb'", iv="''", exitcode=0,
message=None)
with Example("invalid mode value", requirements=[RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_Invalid("1.0")]):
with When("typo in the block algorithm"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-eeb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128-eeb")
with When("typo in the key size"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-127-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-127-ecb")
with When("typo in the aes prefix"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aee-128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aee-128-ecb")
with When("missing last dash"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128ecb")
with When("missing first dash"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes128-ecb")
with When("all capitals"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'AES-128-ECB'", exitcode=36,
message="DB::Exception: Invalid mode: AES-128-ECB")
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Key_Length_TooShortError("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_Key_Length_TooLong("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_Length_TooShortError("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_Length_TooLong("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len", [
# ECB
("'aes-128-ecb'", 16, None,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ecb'", 24, None,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ecb'", 32, None,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_ECB_KeyAndInitializationVector_Length("1.0"))),
# CBC
("'aes-128-cbc'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cbc'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cbc'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_CBC_KeyAndInitializationVector_Length("1.0"))),
# CFB1
("'aes-128-cfb1'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb1'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb1'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_CFB1_KeyAndInitializationVector_Length("1.0"))),
# CFB8
("'aes-128-cfb8'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb8'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb8'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_CFB8_KeyAndInitializationVector_Length("1.0"))),
# CFB128
("'aes-128-cfb128'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb128'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb128'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_CFB128_KeyAndInitializationVector_Length("1.0"))),
# OFB
("'aes-128-ofb'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ofb'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ofb'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_OFB_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s")
def key_or_iv_length_for_mode(self, mode, key_len, iv_len):
"""Check that key or iv length for mode.
"""
plaintext = "'hello there'"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len+1]}'", mode=mode)
if iv_len is not None:
with When("iv is too short"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
with When("iv is too long"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode)
else:
with When("iv is specified but not needed"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_InitializationVector("1.0")
)
def iv_parameter_types(self):
"""Check that `aes_encrypt_mysql` function accepts `iv` parameter as the fourth argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("iv is specified using String type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv=iv, message="F024F9372FA0D8B974894D29FFB8A7F7")
with When("iv is specified using String with UTF8 characters"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv="'Gãńdåłf_Thê'", message="7A4EC0FF3796F46BED281F4778ACE1DC")
with When("iv is specified using FixedString type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv=f"toFixedString({iv}, 16)", message="F024F9372FA0D8B974894D29FFB8A7F7")
with When("iv is specified using FixedString with UTF8 characters"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv="toFixedString('Gãńdåłf_Thê', 16)", message="7A4EC0FF3796F46BED281F4778ACE1DC")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Key("1.0")
)
def key_parameter_types(self):
"""Check that `aes_encrypt_mysql` function accepts `key` parameter as the second argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("key is specified using String type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("key is specified using String with UTF8 characters"):
aes_encrypt_mysql(plaintext=plaintext, key="'Gãńdåłf_Thê'", mode=mode, message="180086AA42AD57B71C706EEC372D0C3D")
with When("key is specified using FixedString type"):
aes_encrypt_mysql(plaintext=plaintext, key=f"toFixedString({key}, 16)", mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("key is specified using FixedString with UTF8 characters"):
aes_encrypt_mysql(plaintext=plaintext, key="toFixedString('Gãńdåłf_Thê', 16)", mode=mode, message="180086AA42AD57B71C706EEC372D0C3D")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode("1.0"),
)
def mode_parameter_types(self):
"""Check that `aes_encrypt_mysql` function accepts `mode` parameter as the third argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("mode is specified using String type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("mode is specified using FixedString type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=f"toFixedString({mode}, 12)", message="49C9ADB81BA9B58C485E7ADB90E70576")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_ReturnValue("1.0")
)
def return_value(self):
"""Check that `aes_encrypt_mysql` functions returns String data type.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("I get type of the return value"):
sql = "SELECT toTypeName(aes_encrypt_mysql("+ mode + "," + plaintext + "," + key + "," + iv + "))"
r = self.context.node.query(sql)
with Then("type should be String"):
assert r.output.strip() == "String", error()
with When("I get return ciphertext as hex"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv=iv, message="F024F9372FA0D8B974894D29FFB8A7F7")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Syntax("1.0"),
)
def syntax(self):
"""Check that `aes_encrypt_mysql` function supports syntax
```sql
aes_encrypt_mysql(plaintext, key, mode, [iv])
```
"""
sql = "SELECT hex(aes_encrypt_mysql('aes-128-ofb', 'hello there', '0123456789123456', '0123456789123456'))"
self.context.node.query(sql, step=When, message="70FE78410D6EE237C2DE4A")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_PlainText("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_ValuesFormat("1.0"),
)
def encryption(self):
"""Check that `aes_encrypt_mysql` functions accepts `plaintext` as the second parameter
with any data type and `mode` as the first parameter.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for mode, key_len, iv_len in mysql_modes:
for datatype, plaintext in plaintexts:
requirement = globals().get(f"""RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_{mode.strip("'").replace("-","_").upper()}""")("1.0")
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} key={key_len} iv={iv_len}""",
requirements=[requirement]) as example:
r = aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"))
with Then("I check output against snapshot"):
with values() as that:
example_name = basename(example.name)
assert that(snapshot(r.output.strip(), "encrypt_mysql", name=f"example_{example_name.replace(' ', '_')}")), error()
@TestFeature
@Name("encrypt_mysql")
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function("1.0")
)
def feature(self, node="clickhouse1"):
"""Check the behavior of the `aes_encrypt_mysql` function.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ def regression(self, local, clickhouse_binary_path):
Feature(test=load("example.regression", "regression"))(**args)
Feature(test=load("ldap.regression", "regression"))(**args)
Feature(test=load("rbac.regression", "regression"))(**args)
Feature(test=load("aes_encryption.regression", "regression"))(**args)
if main():
regression()

View File

@ -0,0 +1,3 @@
# Suppress some failures in contrib so that we can enable UBSan in CI.
# Ideally, we should report these upstream.
src:*/contrib/openssl/*