mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-12 17:32:32 +00:00
6d45d0c374
* Use fingerprints instead of key IDs to find keys in encrypted disks. Always use little endian in the headers of encryption files. * Add tests. * Fix copying binary files to test containers. * Fix ownership for copied files in test containers. * Add comments after review. --------- Co-authored-by: Nikita Mikhaylov <mikhaylovnikitka@gmail.com>
155 lines
5.6 KiB
C++
155 lines
5.6 KiB
C++
#pragma once
|
|
|
|
#include "config.h"
|
|
|
|
#if USE_SSL
|
|
#include <Core/Types.h>
|
|
#include <openssl/evp.h>
|
|
|
|
namespace DB
|
|
{
|
|
class ReadBuffer;
|
|
class WriteBuffer;
|
|
|
|
namespace FileEncryption
|
|
{
|
|
|
|
/// Encryption algorithm.
|
|
/// We chose to use CTR cipther algorithms because they have the following features which are important for us:
|
|
/// - No right padding, so we can append encrypted files without deciphering;
|
|
/// - One byte is always ciphered as one byte, so we get random access to encrypted files easily.
|
|
enum class Algorithm
|
|
{
|
|
AES_128_CTR, /// Size of key is 16 bytes.
|
|
AES_192_CTR, /// Size of key is 24 bytes.
|
|
AES_256_CTR, /// Size of key is 32 bytes.
|
|
MAX
|
|
};
|
|
|
|
String toString(Algorithm algorithm);
|
|
Algorithm parseAlgorithmFromString(const String & str);
|
|
|
|
/// Throws an exception if a specified key size doesn't correspond a specified encryption algorithm.
|
|
void checkKeySize(size_t key_size, Algorithm algorithm);
|
|
|
|
|
|
/// Initialization vector. Its size is always 16 bytes.
|
|
class InitVector
|
|
{
|
|
public:
|
|
static constexpr const size_t kSize = 16;
|
|
|
|
InitVector() = default;
|
|
explicit InitVector(const UInt128 & counter_) { set(counter_); }
|
|
|
|
void set(const UInt128 & counter_) { counter = counter_; }
|
|
UInt128 get() const { return counter; }
|
|
|
|
void read(ReadBuffer & in);
|
|
void write(WriteBuffer & out) const;
|
|
|
|
/// Write 16 bytes of the counter to a string in big endian order.
|
|
/// We need big endian because the used cipher algorithms treat an initialization vector as a counter in big endian.
|
|
String toString() const;
|
|
|
|
/// Converts a string of 16 bytes length in big endian order to a counter.
|
|
static InitVector fromString(const String & str_);
|
|
|
|
/// Adds a specified offset to the counter.
|
|
InitVector & operator++() { ++counter; return *this; }
|
|
InitVector operator++(int) { InitVector res = *this; ++counter; return res; } /// NOLINT
|
|
InitVector & operator+=(size_t offset) { counter += offset; return *this; }
|
|
InitVector operator+(size_t offset) const { InitVector res = *this; return res += offset; }
|
|
|
|
/// Generates a random initialization vector.
|
|
static InitVector random();
|
|
|
|
private:
|
|
UInt128 counter = 0;
|
|
};
|
|
|
|
|
|
/// Encrypts or decrypts data.
|
|
class Encryptor
|
|
{
|
|
public:
|
|
/// The `key` should have size 16 or 24 or 32 bytes depending on which `algorithm` is specified.
|
|
Encryptor(Algorithm algorithm_, const String & key_, const InitVector & iv_);
|
|
|
|
/// Sets the current position in the data stream from the very beginning of data.
|
|
/// It affects how the data will be encrypted or decrypted because
|
|
/// the initialization vector is increased by an index of the current block
|
|
/// and the index of the current block is calculated from this offset.
|
|
void setOffset(size_t offset_) { offset = offset_; }
|
|
size_t getOffset() const { return offset; }
|
|
|
|
/// Encrypts some data.
|
|
/// Also the function moves `offset` by `size` (for successive encryptions).
|
|
void encrypt(const char * data, size_t size, WriteBuffer & out);
|
|
|
|
/// Decrypts some data.
|
|
/// The used cipher algorithms generate the same number of bytes in output as they were in input,
|
|
/// so the function always writes `size` bytes of the plaintext to `out`.
|
|
/// Also the function moves `offset` by `size` (for successive decryptions).
|
|
void decrypt(const char * data, size_t size, char * out);
|
|
|
|
private:
|
|
const String key;
|
|
const InitVector init_vector;
|
|
const EVP_CIPHER * const evp_cipher;
|
|
|
|
/// The current position in the data stream from the very beginning of data.
|
|
size_t offset = 0;
|
|
};
|
|
|
|
|
|
/// File header which is stored at the beginning of encrypted files.
|
|
///
|
|
/// The format of that header is following:
|
|
/// +--------+------+--------------------------------------------------------------------------+
|
|
/// | offset | size | description |
|
|
/// +--------+------+--------------------------------------------------------------------------+
|
|
/// | 0 | 3 | 'E', 'N', 'C' (file's signature) |
|
|
/// | 3 | 2 | version of this header (1..2) |
|
|
/// | 5 | 2 | encryption algorithm (0..2, 0=AES_128_CTR, 1=AES_192_CTR, 2=AES_256_CTR) |
|
|
/// | 7 | 16 | fingerprint of encryption key (SipHash) |
|
|
/// | 23 | 16 | initialization vector (randomly generated) |
|
|
/// | 39 | 25 | reserved for future use |
|
|
/// +--------+------+--------------------------------------------------------------------------+
|
|
///
|
|
struct Header
|
|
{
|
|
/// Versions:
|
|
/// 1 - Initial version
|
|
/// 2 - The header of an encrypted file contains the fingerprint of a used encryption key instead of a pair {key_id, very_small_hash(key)}.
|
|
/// The header is always stored in little endian.
|
|
static constexpr const UInt16 kCurrentVersion = 2;
|
|
|
|
UInt16 version = kCurrentVersion;
|
|
|
|
/// Encryption algorithm.
|
|
Algorithm algorithm = Algorithm::AES_128_CTR;
|
|
|
|
/// Fingerprint of a key.
|
|
UInt128 key_fingerprint = 0;
|
|
|
|
InitVector init_vector;
|
|
|
|
/// The size of this header in bytes, including reserved bytes.
|
|
static constexpr const size_t kSize = 64;
|
|
|
|
void read(ReadBuffer & in);
|
|
void write(WriteBuffer & out) const;
|
|
};
|
|
|
|
/// Calculates the fingerprint of a passed encryption key.
|
|
UInt128 calculateKeyFingerprint(const String & key);
|
|
|
|
/// Calculates kind of the fingerprint of a passed encryption key & key ID as it was implemented in version 1.
|
|
UInt128 calculateV1KeyFingerprint(const String & key, UInt64 key_id);
|
|
|
|
}
|
|
}
|
|
|
|
#endif
|