mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-10 09:32:06 +00:00
Merge pull request #11844 from Enmk/AES_encrypt_decrypt
encrypt and decrypt functions
This commit is contained in:
commit
2b00b5391a
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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{})
|
||||
|
63
src/Functions/FunctionsAES.cpp
Normal file
63
src/Functions/FunctionsAES.cpp
Normal 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
|
693
src/Functions/FunctionsAES.h
Normal file
693
src/Functions/FunctionsAES.h
Normal 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
|
31
src/Functions/aes_decrypt_mysql.cpp
Normal file
31
src/Functions/aes_decrypt_mysql.cpp
Normal 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
|
31
src/Functions/aes_encrypt_mysql.cpp
Normal file
31
src/Functions/aes_encrypt_mysql.cpp
Normal 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
31
src/Functions/decrypt.cpp
Normal 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
31
src/Functions/encrypt.cpp
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
84
tests/performance/encrypt_decrypt.xml
Normal file
84
tests/performance/encrypt_decrypt.xml
Normal 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>
|
67
tests/performance/encrypt_decrypt_empty_string.xml
Normal file
67
tests/performance/encrypt_decrypt_empty_string.xml
Normal 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>
|
55
tests/performance/encrypt_decrypt_empty_string_slow.xml
Normal file
55
tests/performance/encrypt_decrypt_empty_string_slow.xml
Normal 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>
|
36
tests/performance/encrypt_decrypt_slow.xml
Normal file
36
tests/performance/encrypt_decrypt_slow.xml
Normal 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>
|
144
tests/queries/0_stateless/01318_decrypt.reference
Normal file
144
tests/queries/0_stateless/01318_decrypt.reference
Normal 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
|
152
tests/queries/0_stateless/01318_decrypt.sql
Normal file
152
tests/queries/0_stateless/01318_decrypt.sql
Normal 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);
|
147
tests/queries/0_stateless/01318_encrypt.reference
Normal file
147
tests/queries/0_stateless/01318_encrypt.reference
Normal 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
|
156
tests/queries/0_stateless/01318_encrypt.sql
Normal file
156
tests/queries/0_stateless/01318_encrypt.sql
Normal 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));
|
@ -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>
|
@ -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>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<yandex>
|
||||
<https_port>8443</https_port>
|
||||
<tcp_port_secure>9440</tcp_port_secure>
|
||||
</yandex>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
436
tests/testflows/aes_encryption/configs/clickhouse/config.xml
Normal file
436
tests/testflows/aes_encryption/configs/clickhouse/config.xml
Normal 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>
|
@ -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-----
|
@ -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-----
|
@ -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-----
|
133
tests/testflows/aes_encryption/configs/clickhouse/users.xml
Normal file
133
tests/testflows/aes_encryption/configs/clickhouse/users.xml
Normal 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 < 1 or c - d > 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>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<yandex>
|
||||
<macros>
|
||||
<replica>clickhouse1</replica>
|
||||
<shard>01</shard>
|
||||
<shard2>01</shard2>
|
||||
</macros>
|
||||
</yandex>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<yandex>
|
||||
<macros>
|
||||
<replica>clickhouse2</replica>
|
||||
<shard>01</shard>
|
||||
<shard2>02</shard2>
|
||||
</macros>
|
||||
</yandex>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<yandex>
|
||||
<macros>
|
||||
<replica>clickhouse3</replica>
|
||||
<shard>01</shard>
|
||||
<shard2>03</shard2>
|
||||
</macros>
|
||||
</yandex>
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
73
tests/testflows/aes_encryption/regression.py
Executable file
73
tests/testflows/aes_encryption/regression.py
Executable 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()
|
1
tests/testflows/aes_encryption/requirements/__init__.py
Normal file
1
tests/testflows/aes_encryption/requirements/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .requirements import *
|
1926
tests/testflows/aes_encryption/requirements/requirements.md
Normal file
1926
tests/testflows/aes_encryption/requirements/requirements.md
Normal file
File diff suppressed because it is too large
Load Diff
3663
tests/testflows/aes_encryption/requirements/requirements.py
Normal file
3663
tests/testflows/aes_encryption/requirements/requirements.py
Normal file
File diff suppressed because it is too large
Load Diff
162
tests/testflows/aes_encryption/tests/common.py
Normal file
162
tests/testflows/aes_encryption/tests/common.py
Normal 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)
|
@ -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)
|
414
tests/testflows/aes_encryption/tests/compatibility/insert.py
Normal file
414
tests/testflows/aes_encryption/tests/compatibility/insert.py
Normal 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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
186
tests/testflows/aes_encryption/tests/compatibility/select.py
Normal file
186
tests/testflows/aes_encryption/tests/compatibility/select.py
Normal 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)
|
@ -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'"""
|
||||
|
634
tests/testflows/aes_encryption/tests/decrypt.py
Normal file
634
tests/testflows/aes_encryption/tests/decrypt.py
Normal 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)
|
521
tests/testflows/aes_encryption/tests/decrypt_mysql.py
Normal file
521
tests/testflows/aes_encryption/tests/decrypt_mysql.py
Normal 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)
|
408
tests/testflows/aes_encryption/tests/encrypt.py
Normal file
408
tests/testflows/aes_encryption/tests/encrypt.py
Normal 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)
|
326
tests/testflows/aes_encryption/tests/encrypt_mysql.py
Normal file
326
tests/testflows/aes_encryption/tests/encrypt_mysql.py
Normal 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
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||
|
3
tests/ubsan_suppressions.txt
Normal file
3
tests/ubsan_suppressions.txt
Normal 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/*
|
Loading…
Reference in New Issue
Block a user