From 69899a6ee43f80517362d3952b6fc519efb78f88 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 11 Jul 2022 18:56:28 +0800 Subject: [PATCH] Add sm4 encryption (#295) * Add SM4-CTR encryption algorithm Signed-off-by: Jarvis Zheng * Adjust block size for sm4 encryption Signed-off-by: Jarvis Zheng * Add UT for SM4 encryption Signed-off-by: Jarvis Zheng * Adjust macros indentation for sm4 Signed-off-by: Jarvis Zheng * Fix format for adding sm4 Signed-off-by: Jarvis Zheng --- encryption/encryption.cc | 59 ++++++++++++++++++++++++++--------- encryption/encryption.h | 13 ++++++++ encryption/encryption_test.cc | 17 ++++++++++ include/rocksdb/encryption.h | 3 ++ tools/db_bench_tool.cc | 2 ++ 5 files changed, 80 insertions(+), 14 deletions(-) diff --git a/encryption/encryption.cc b/encryption/encryption.cc index ec2eeefa89e..ecb403669b1 100644 --- a/encryption/encryption.cc +++ b/encryption/encryption.cc @@ -76,8 +76,10 @@ Status AESCTRCipherStream::Cipher(uint64_t file_offset, char* data, return Status::IOError("Failed to create cipher context."); } - uint64_t block_index = file_offset / AES_BLOCK_SIZE; - uint64_t block_offset = file_offset % AES_BLOCK_SIZE; + const size_t block_size = BlockSize(); + + uint64_t block_index = file_offset / block_size; + uint64_t block_offset = file_offset % block_size; // In CTR mode, OpenSSL EVP API treat the IV as a 128-bit big-endien, and // increase it by 1 for each block. @@ -90,7 +92,7 @@ Status AESCTRCipherStream::Cipher(uint64_t file_offset, char* data, if (std::numeric_limits::max() - block_index < initial_iv_low_) { iv_high++; } - unsigned char iv[AES_BLOCK_SIZE]; + unsigned char iv[block_size]; PutBigEndian64(iv_high, iv); PutBigEndian64(iv_low, iv + sizeof(uint64_t)); @@ -105,13 +107,14 @@ Status AESCTRCipherStream::Cipher(uint64_t file_offset, char* data, // multiply of block size. ret = EVP_CIPHER_CTX_set_padding(ctx, 0); if (ret != 1) { + FreeCipherContext(ctx); return Status::IOError("Failed to disable padding for cipher context."); } uint64_t data_offset = 0; size_t remaining_data_size = data_size; int output_size = 0; - unsigned char partial_block[AES_BLOCK_SIZE]; + unsigned char partial_block[block_size]; // In the following we assume EVP_CipherUpdate allow in and out buffer are // the same, to save one memcpy. This is not specified in official man page. @@ -120,18 +123,20 @@ Status AESCTRCipherStream::Cipher(uint64_t file_offset, char* data, // buffer to fake a full block. if (block_offset > 0) { size_t partial_block_size = - std::min(AES_BLOCK_SIZE - block_offset, remaining_data_size); + std::min(block_size - block_offset, remaining_data_size); memcpy(partial_block + block_offset, data, partial_block_size); ret = EVP_CipherUpdate(ctx, partial_block, &output_size, partial_block, - AES_BLOCK_SIZE); + static_cast(block_size)); if (ret != 1) { + FreeCipherContext(ctx); return Status::IOError("Crypter failed for first block, offset " + ToString(file_offset)); } - if (output_size != AES_BLOCK_SIZE) { + if (output_size != static_cast(block_size)) { + FreeCipherContext(ctx); return Status::IOError( "Unexpected crypter output size for first block, expected " + - ToString(AES_BLOCK_SIZE) + " vs actual " + ToString(output_size)); + ToString(block_size) + " vs actual " + ToString(output_size)); } memcpy(data, partial_block + block_offset, partial_block_size); data_offset += partial_block_size; @@ -139,18 +144,20 @@ Status AESCTRCipherStream::Cipher(uint64_t file_offset, char* data, } // Handle full blocks in the middle. - if (remaining_data_size >= AES_BLOCK_SIZE) { + if (remaining_data_size >= block_size) { size_t actual_data_size = - remaining_data_size - remaining_data_size % AES_BLOCK_SIZE; + remaining_data_size - remaining_data_size % block_size; unsigned char* full_blocks = reinterpret_cast(data) + data_offset; ret = EVP_CipherUpdate(ctx, full_blocks, &output_size, full_blocks, static_cast(actual_data_size)); if (ret != 1) { + FreeCipherContext(ctx); return Status::IOError("Crypter failed at offset " + ToString(file_offset + data_offset)); } if (output_size != static_cast(actual_data_size)) { + FreeCipherContext(ctx); return Status::IOError("Unexpected crypter output size, expected " + ToString(actual_data_size) + " vs actual " + ToString(output_size)); @@ -162,21 +169,29 @@ Status AESCTRCipherStream::Cipher(uint64_t file_offset, char* data, // Handle partial block at the end. The parital block is copied to buffer to // fake a full block. if (remaining_data_size > 0) { - assert(remaining_data_size < AES_BLOCK_SIZE); + assert(remaining_data_size < block_size); memcpy(partial_block, data + data_offset, remaining_data_size); ret = EVP_CipherUpdate(ctx, partial_block, &output_size, partial_block, - AES_BLOCK_SIZE); + static_cast(block_size)); if (ret != 1) { + FreeCipherContext(ctx); return Status::IOError("Crypter failed for last block, offset " + ToString(file_offset + data_offset)); } - if (output_size != AES_BLOCK_SIZE) { + if (output_size != static_cast(block_size)) { + FreeCipherContext(ctx); return Status::IOError( "Unexpected crypter output size for last block, expected " + - ToString(AES_BLOCK_SIZE) + " vs actual " + ToString(output_size)); + ToString(block_size) + " vs actual " + ToString(output_size)); } memcpy(data + data_offset, partial_block, remaining_data_size); } + + // Since padding is disabled, and the cipher flow always passes a multiply + // of block size data while each EVP_CipherUpdate, there is no need to call + // EVP_CipherFinal_ex to finish the last block cipher. + // Reference to the implement of EVP_CipherFinal_ex: + // https://github.com/openssl/openssl/blob/OpenSSL_1_1_1-stable/crypto/evp/evp_enc.c#L219 FreeCipherContext(ctx); return Status::OK(); #endif @@ -197,6 +212,16 @@ Status NewAESCTRCipherStream(EncryptionMethod method, const std::string& key, case EncryptionMethod::kAES256_CTR: cipher = EVP_aes_256_ctr(); break; + case EncryptionMethod::kSM4_CTR: +#if OPENSSL_VERSION_NUMBER < 0x1010100fL + return Status::InvalidArgument( + "Unsupport SM4 encryption method under OpenSSL version: " + + std::string(OPENSSL_VERSION_TEXT)); +#else + // Openssl support SM4 after 1.1.1 release version. + cipher = EVP_sm4_ctr(); + break; +#endif default: return Status::InvalidArgument("Unsupported encryption method: " + ToString(static_cast(method))); @@ -265,6 +290,7 @@ Status KeyManagedEncryptedEnv::NewSequentialFile( case EncryptionMethod::kAES128_CTR: case EncryptionMethod::kAES192_CTR: case EncryptionMethod::kAES256_CTR: + case EncryptionMethod::kSM4_CTR: s = encrypted_env_->NewSequentialFile(fname, result, options); // Hack: when upgrading from TiKV <= v5.0.0-rc, the old current // file is encrypted but it could be replaced with a plaintext @@ -300,6 +326,7 @@ Status KeyManagedEncryptedEnv::NewRandomAccessFile( case EncryptionMethod::kAES128_CTR: case EncryptionMethod::kAES192_CTR: case EncryptionMethod::kAES256_CTR: + case EncryptionMethod::kSM4_CTR: s = encrypted_env_->NewRandomAccessFile(fname, result, options); break; default: @@ -332,6 +359,7 @@ Status KeyManagedEncryptedEnv::NewWritableFile( case EncryptionMethod::kAES128_CTR: case EncryptionMethod::kAES192_CTR: case EncryptionMethod::kAES256_CTR: + case EncryptionMethod::kSM4_CTR: s = encrypted_env_->NewWritableFile(fname, result, options); break; default: @@ -360,6 +388,7 @@ Status KeyManagedEncryptedEnv::ReopenWritableFile( case EncryptionMethod::kAES128_CTR: case EncryptionMethod::kAES192_CTR: case EncryptionMethod::kAES256_CTR: + case EncryptionMethod::kSM4_CTR: s = encrypted_env_->ReopenWritableFile(fname, result, options); break; default: @@ -387,6 +416,7 @@ Status KeyManagedEncryptedEnv::ReuseWritableFile( case EncryptionMethod::kAES128_CTR: case EncryptionMethod::kAES192_CTR: case EncryptionMethod::kAES256_CTR: + case EncryptionMethod::kSM4_CTR: s = encrypted_env_->ReuseWritableFile(fname, old_fname, result, options); break; default: @@ -422,6 +452,7 @@ Status KeyManagedEncryptedEnv::NewRandomRWFile( case EncryptionMethod::kAES128_CTR: case EncryptionMethod::kAES192_CTR: case EncryptionMethod::kAES256_CTR: + case EncryptionMethod::kSM4_CTR: s = encrypted_env_->NewRandomRWFile(fname, result, options); break; default: diff --git a/encryption/encryption.h b/encryption/encryption.h index 9c1de2cea70..3415a13b77a 100644 --- a/encryption/encryption.h +++ b/encryption/encryption.h @@ -39,6 +39,13 @@ namespace encryption { #endif +// TODO: OpenSSL Lib does not export SM4_BLOCK_SIZE by now. +// Need to remove SM4_BLOCK_Size once Openssl lib support the definition. +// SM4 uses 128-bit block size as AES. +// Ref: +// https://github.com/openssl/openssl/blob/OpenSSL_1_1_1-stable/include/crypto/sm4.h#L24 +#define SM4_BLOCK_SIZE 16 + class AESCTRCipherStream : public BlockAccessCipherStream { public: AESCTRCipherStream(const EVP_CIPHER* cipher, const std::string& key, @@ -51,6 +58,12 @@ class AESCTRCipherStream : public BlockAccessCipherStream { ~AESCTRCipherStream() = default; size_t BlockSize() override { + // Openssl support SM4 after 1.1.1 release version. +#if OPENSSL_VERSION_NUMBER >= 0x1010100fL + if (EVP_CIPHER_nid(cipher_) == NID_sm4_ctr) { + return SM4_BLOCK_SIZE; + } +#endif return AES_BLOCK_SIZE; // 16 } diff --git a/encryption/encryption_test.cc b/encryption/encryption_test.cc index 5928a543bd5..02e7fd0514e 100644 --- a/encryption/encryption_test.cc +++ b/encryption/encryption_test.cc @@ -56,6 +56,12 @@ class EncryptionTest case EncryptionMethod::kAES256_CTR: cipher = EVP_aes_256_ctr(); break; +#if OPENSSL_VERSION_NUMBER >= 0x1010100fL + // Openssl support SM4 after 1.1.1 release version. + case EncryptionMethod::kSM4_CTR: + cipher = EVP_sm4_ctr(); + break; +#endif default: assert(false); } @@ -146,12 +152,23 @@ TEST_P(EncryptionTest, EncryptionTest) { EXPECT_TRUE(TestEncryption(16, 16 * 2, IV_OVERFLOW_FULL)); } +// Openssl support SM4 after 1.1.1 release version. +#if OPENSSL_VERSION_NUMBER < 0x1010100fL INSTANTIATE_TEST_CASE_P( EncryptionTestInstance, EncryptionTest, testing::Combine(testing::Bool(), testing::Values(EncryptionMethod::kAES128_CTR, EncryptionMethod::kAES192_CTR, EncryptionMethod::kAES256_CTR))); +#else +INSTANTIATE_TEST_CASE_P( + EncryptionTestInstance, EncryptionTest, + testing::Combine(testing::Bool(), + testing::Values(EncryptionMethod::kAES128_CTR, + EncryptionMethod::kAES192_CTR, + EncryptionMethod::kAES256_CTR, + EncryptionMethod::kSM4_CTR))); +#endif } // namespace encryption } // namespace rocksdb diff --git a/include/rocksdb/encryption.h b/include/rocksdb/encryption.h index c65e02e8810..c3511369c00 100644 --- a/include/rocksdb/encryption.h +++ b/include/rocksdb/encryption.h @@ -20,6 +20,7 @@ enum class EncryptionMethod : int { kAES128_CTR = 2, kAES192_CTR = 3, kAES256_CTR = 4, + kSM4_CTR = 5, }; inline size_t KeySize(EncryptionMethod method) { @@ -30,6 +31,8 @@ inline size_t KeySize(EncryptionMethod method) { return 24; case EncryptionMethod::kAES256_CTR: return 32; + case EncryptionMethod::kSM4_CTR: + return 16; default: return 0; }; diff --git a/tools/db_bench_tool.cc b/tools/db_bench_tool.cc index e00b3f1af9f..48b38f920e7 100644 --- a/tools/db_bench_tool.cc +++ b/tools/db_bench_tool.cc @@ -6507,6 +6507,8 @@ int db_bench_tool(int argc, char** argv) { method = encryption::EncryptionMethod::kAES192_CTR; } else if (!strcasecmp(FLAGS_encryption_method.c_str(), "AES256CTR")) { method = encryption::EncryptionMethod::kAES256_CTR; + } else if (!strcasecmp(FLAGS_encryption_method.c_str(), "SM4CTR")) { + method = encryption::EncryptionMethod::kSM4_CTR; } if (method == encryption::EncryptionMethod::kUnknown) { fprintf(stderr, "Unknown encryption method %s\n",