mirror of
https://github.com/apple/foundationdb.git
synced 2025-05-14 09:58:50 +08:00
* Upgrade AES 128 GCM -> AES 256, StreamCipher code refactor Major changes proposed are: 1. Refactor StreamCipher code to enable instantiation of multiple encryption keys. However, code still retains a globalEncryption key semantics used in Backup file encryption usecase. 2. Enhance StreamCipher to provide HMAC signature digest generation. Further, the class implements HMAC encryption key derivation function. 3. Upgrade StreamCipher to use AES 256 GCM mode from currently supported AES 128 GCM mode. Note: The code changes the encryption key size, however, the feature is NOT currently in use, hence, should be OK. 3. Add EncryptionOps validation and benchmark toml supported workload, it does the following: a. Allow user to configure encrypt-decrypt of a fixed size buffer or variable size buffer [100, 512K] b. Allow user to configure number of interactions of the runs, in each iteration: generate random data, derive an encryption key using HMAC SHA256 method, encrypt data and then decrypt data. It collects following metrics: i) time taken to derive encryption key. ii) time taken to encrypt the buffer. iii) time taken to decrypt the buffer. iv) total bytes encrypted and/or decrypted c. Along with stats it basic basic validations on the encrypted and decrypted buffer d. On completion for test, records the above mentioned metrics in trace files.
207 lines
6.9 KiB
C++
207 lines
6.9 KiB
C++
/*
|
|
* EncryptionOps.actor.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "fdbclient/DatabaseContext.h"
|
|
#include "fdbclient/NativeAPI.actor.h"
|
|
#include "flow/IRandom.h"
|
|
#include "flow/StreamCipher.h"
|
|
#include "fdbserver/workloads/workloads.actor.h"
|
|
#include "flow/Trace.h"
|
|
#include "flow/actorcompiler.h" // This must be the last #include.
|
|
|
|
#if ENCRYPTION_ENABLED
|
|
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <memory>
|
|
|
|
#define MEGA_BYTES (1024 * 1024)
|
|
#define NANO_SECOND (1000 * 1000 * 1000)
|
|
|
|
struct WorkloadMetrics {
|
|
double totalEncryptTimeNS;
|
|
double totalDecryptTimeNS;
|
|
double totalKeyDerivationTimeNS;
|
|
int64_t totalBytes;
|
|
|
|
void reset() {
|
|
totalEncryptTimeNS = 0;
|
|
totalDecryptTimeNS = 0;
|
|
totalKeyDerivationTimeNS = 0;
|
|
totalBytes = 0;
|
|
}
|
|
|
|
WorkloadMetrics() { reset(); }
|
|
|
|
double computeEncryptThroughputMBPS() {
|
|
// convert bytes -> MBs & nano-seonds -> seconds
|
|
return (totalBytes * NANO_SECOND) / (totalEncryptTimeNS * MEGA_BYTES);
|
|
}
|
|
|
|
double computeDecryptThroughputMBPS() {
|
|
// convert bytes -> MBs & nano-seonds -> seconds
|
|
return (totalBytes * NANO_SECOND) / (totalDecryptTimeNS * MEGA_BYTES);
|
|
}
|
|
|
|
void updateKeyDerivationTime(double val) { totalKeyDerivationTimeNS += val; }
|
|
void updateEncryptionTime(double val) { totalEncryptTimeNS += val; }
|
|
void updateDecryptionTime(double val) { totalDecryptTimeNS += val; }
|
|
void updateBytes(int64_t val) { totalBytes += val; }
|
|
|
|
void recordMetrics(const std::string& mode, const int numIterations) {
|
|
TraceEvent("EncryptionOpsWorkload")
|
|
.detail("Mode", mode)
|
|
.detail("EncryptTimeMS", totalEncryptTimeNS / 1000)
|
|
.detail("DecryptTimeMS", totalDecryptTimeNS / 1000)
|
|
.detail("EncryptMBPS", computeEncryptThroughputMBPS())
|
|
.detail("DecryptMBPS", computeDecryptThroughputMBPS())
|
|
.detail("KeyDerivationTimeMS", totalKeyDerivationTimeNS / 1000)
|
|
.detail("TotalBytes", totalBytes)
|
|
.detail("AvgCommitSize", totalBytes / numIterations);
|
|
}
|
|
};
|
|
|
|
struct EncryptionOpsWorkload : TestWorkload {
|
|
int mode;
|
|
int64_t numIterations;
|
|
int pageSize;
|
|
int maxBufSize;
|
|
std::unique_ptr<uint8_t[]> buff;
|
|
std::unique_ptr<uint8_t[]> validationBuff;
|
|
|
|
StreamCipher::IV iv;
|
|
std::unique_ptr<HmacSha256StreamCipher> hmacGenerator;
|
|
std::unique_ptr<uint8_t[]> parentKey;
|
|
Arena arena;
|
|
std::unique_ptr<WorkloadMetrics> metrics;
|
|
|
|
EncryptionOpsWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
|
|
mode = getOption(options, LiteralStringRef("fixedSize"), 1);
|
|
numIterations = getOption(options, LiteralStringRef("numIterations"), 10);
|
|
pageSize = getOption(options, LiteralStringRef("pageSize"), 4096);
|
|
maxBufSize = getOption(options, LiteralStringRef("maxBufSize"), 512 * 1024);
|
|
buff = std::make_unique<uint8_t[]>(maxBufSize);
|
|
validationBuff = std::make_unique<uint8_t[]>(maxBufSize);
|
|
|
|
iv = getRandomIV();
|
|
hmacGenerator = std::make_unique<HmacSha256StreamCipher>();
|
|
parentKey = std::make_unique<uint8_t[]>(AES_256_KEY_LENGTH);
|
|
generateRandomData(parentKey.get(), AES_256_KEY_LENGTH);
|
|
|
|
metrics = std::make_unique<WorkloadMetrics>();
|
|
|
|
TraceEvent("EncryptionOpsWorkload").detail("Mode", getModeStr());
|
|
}
|
|
|
|
bool isFixedSizePayload() { return mode == 1; }
|
|
|
|
StreamCipher::IV getRandomIV() {
|
|
generateRandomData(iv.data(), iv.size());
|
|
return iv;
|
|
}
|
|
|
|
std::string getModeStr() const {
|
|
if (mode == 1) {
|
|
return "FixedSize";
|
|
} else if (mode == 0) {
|
|
return "VariableSize";
|
|
}
|
|
// no other mode supported
|
|
throw internal_error();
|
|
}
|
|
|
|
void updateEncryptionKey(StreamCipherKey* cipherKey) {
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
applyHmacKeyDerivationFunc(cipherKey, hmacGenerator.get(), arena);
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
|
|
metrics->updateKeyDerivationTime(std::chrono::duration<double, std::nano>(end - start).count());
|
|
}
|
|
|
|
StringRef doEncryption(const StreamCipherKey* key, uint8_t* payload, int len) {
|
|
EncryptionStreamCipher encryptor(key, iv);
|
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
auto encrypted = encryptor.encrypt(buff.get(), len, arena);
|
|
encryptor.finish(arena);
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
|
|
// validate encrypted buffer size and contents (not matching with plaintext)
|
|
ASSERT(encrypted.size() == len);
|
|
std::copy(encrypted.begin(), encrypted.end(), validationBuff.get());
|
|
ASSERT(memcmp(validationBuff.get(), buff.get(), len) != 0);
|
|
|
|
metrics->updateEncryptionTime(std::chrono::duration<double, std::nano>(end - start).count());
|
|
return encrypted;
|
|
}
|
|
|
|
void doDecryption(const StreamCipherKey* key,
|
|
const StringRef& encrypted,
|
|
int len,
|
|
uint8_t* originalPayload,
|
|
uint8_t* validationBuff) {
|
|
DecryptionStreamCipher decryptor(key, iv);
|
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
Standalone<StringRef> decrypted = decryptor.decrypt(encrypted.begin(), len, arena);
|
|
decryptor.finish(arena);
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
|
|
// validate decrypted buffer size and contents (matching with original plaintext)
|
|
ASSERT(decrypted.size() == len);
|
|
std::copy(decrypted.begin(), decrypted.end(), validationBuff);
|
|
ASSERT(memcmp(validationBuff, originalPayload, len) == 0);
|
|
|
|
metrics->updateDecryptionTime(std::chrono::duration<double, std::nano>(end - start).count());
|
|
}
|
|
|
|
Future<Void> setup(Database const& ctx) override { return Void(); }
|
|
|
|
std::string description() const override { return "EncryptionOps"; }
|
|
|
|
Future<Void> start(Database const& cx) override {
|
|
for (int i = 0; i < numIterations; i++) {
|
|
StreamCipherKey key(AES_256_KEY_LENGTH);
|
|
// derive the encryption key
|
|
updateEncryptionKey(&key);
|
|
|
|
int dataLen = isFixedSizePayload() ? pageSize : deterministicRandom()->randomInt(100, maxBufSize);
|
|
generateRandomData(buff.get(), dataLen);
|
|
|
|
// encrypt the payload
|
|
const auto& encrypted = doEncryption(&key, buff.get(), dataLen);
|
|
|
|
// decrypt the payload
|
|
doDecryption(&key, encrypted, dataLen, buff.get(), validationBuff.get());
|
|
|
|
metrics->updateBytes(dataLen);
|
|
}
|
|
return Void();
|
|
}
|
|
|
|
Future<bool> check(Database const& cx) override { return true; }
|
|
|
|
void getMetrics(std::vector<PerfMetric>& m) override { metrics->recordMetrics(getModeStr(), numIterations); }
|
|
};
|
|
|
|
WorkloadFactory<EncryptionOpsWorkload> EncryptionOpsWorkloadFactory("EncryptionOps");
|
|
|
|
#endif // ENCRYPTION_ENABLED
|