#pragma once #include #include #include #include #include #include namespace drp::util::crypto { /** * Represent an AES key. * Allow for encrypting and decrypting data. * @tparam keySize the size of the key (in bytes) * @tparam ivSize the size of the initialisation vector (in bytes) */ template class AesKey { public: /** * Create a random AES key. */ AesKey() { // generate a random key for (auto& byte : this->_data) byte = randomDistribution(randomGenerator); } explicit AesKey(const std::array& data) { this->_data = data; } /** * Encrypt data with this key. * @param plainData the data to encrypt * @return the encrypted data. It will always be longer than the original data. */ [[nodiscard]] std::vector encrypt(const std::vector& plainData) const { // create an initialization vector std::array iv {}; for (auto& byte : iv) byte = randomDistribution(randomGenerator); // create the cipher context const auto context = std::unique_ptr( EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free ); if (context == nullptr) throw std::runtime_error("Error creating EVP_CIPHER_CTX"); // initialize the encryptor if (EVP_EncryptInit_ex( context.get(), cipherFunction(), nullptr, this->_data.data(), iv.data() ) != 1) throw std::runtime_error("Error initializing encryption"); std::vector encryptedData(ivSize + plainData.size() + EVP_CIPHER_block_size(EVP_aes_256_cbc())); std::copy(iv.begin(), iv.end(), encryptedData.begin()); int length; // encrypt the data if (EVP_EncryptUpdate( context.get(), encryptedData.data() + ivSize, &length, plainData.data(), static_cast(plainData.size()) ) != 1) throw std::runtime_error("Error encrypting data"); int encryptedDataLength = length; // finalize the encryption if (EVP_EncryptFinal_ex( context.get(), encryptedData.data() + ivSize + encryptedDataLength, &length ) != 1) throw std::runtime_error("Error finalizing encryption"); encryptedDataLength += length; encryptedData.resize(ivSize + encryptedDataLength); return encryptedData; } /** * Decrypt data with this key. * @param rawEncryptedData the encrypted data to decrypt. * @return the decrypted data. */ [[nodiscard]] std::vector decrypt(const std::vector& rawEncryptedData) const { // create a cipher context const auto context = std::unique_ptr( EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free ); std::array iv; std::copy(rawEncryptedData.begin(), rawEncryptedData.begin() + ivSize, iv.data()); const std::vector encryptedData(rawEncryptedData.begin() + ivSize, rawEncryptedData.end()); // initialize the decryptor if (EVP_DecryptInit_ex( context.get(), cipherFunction(), nullptr, this->_data.data(), iv.data() ) != 1) throw std::runtime_error("Error initializing decryptor"); std::vector plainData(encryptedData.size()); int length; // decrypt the data if (EVP_DecryptUpdate( context.get(), plainData.data(), &length, encryptedData.data(), static_cast(encryptedData.size()) ) != 1) throw std::runtime_error("Error decrypting data"); // finalize the decryption if (EVP_DecryptFinal_ex( context.get(), plainData.data(), &length ) != 1) throw std::runtime_error("Error finalizing decryptor"); plainData.resize(length); return plainData; } private: static std::mt19937 randomGenerator; static std::uniform_int_distribution randomDistribution; std::array _data; }; template std::mt19937 AesKey::randomGenerator = std::mt19937(std::random_device{}()); template std::uniform_int_distribution AesKey::randomDistribution = std::uniform_int_distribution( std::numeric_limits::min(), std::numeric_limits::max() ); using AesKey256 = AesKey<256/8, 16, EVP_aes_256_cbc>; }