From 3ca119e0796384436abc612a0b4199e0d43200e3 Mon Sep 17 00:00:00 2001 From: Wojtech Date: Sun, 4 Feb 2024 21:49:10 +0100 Subject: [PATCH 1/2] Add encryption lib --- encrypt/.gitignore | 1 + encrypt/CMakeLists.txt | 62 ++++++++++++++++++ encrypt/symCrypt/AES256CBC.cpp | 76 ++++++++++++++++++++++ encrypt/symCrypt/AES256CBC.hpp | 18 +++++ encrypt/symCrypt/FakeSymCipher.cpp | 24 +++++++ encrypt/symCrypt/FakeSymCipher.hpp | 13 ++++ encrypt/symCrypt/ISymCipher.hpp | 16 +++++ encrypt/uTests/CMakeLists.txt | 22 +++++++ encrypt/uTests/symCrypt/AES256CBCTests.cpp | 41 ++++++++++++ 9 files changed, 273 insertions(+) create mode 100644 encrypt/.gitignore create mode 100644 encrypt/CMakeLists.txt create mode 100644 encrypt/symCrypt/AES256CBC.cpp create mode 100644 encrypt/symCrypt/AES256CBC.hpp create mode 100644 encrypt/symCrypt/FakeSymCipher.cpp create mode 100644 encrypt/symCrypt/FakeSymCipher.hpp create mode 100644 encrypt/symCrypt/ISymCipher.hpp create mode 100644 encrypt/uTests/CMakeLists.txt create mode 100644 encrypt/uTests/symCrypt/AES256CBCTests.cpp diff --git a/encrypt/.gitignore b/encrypt/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/encrypt/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/encrypt/CMakeLists.txt b/encrypt/CMakeLists.txt new file mode 100644 index 0000000..7b01e3a --- /dev/null +++ b/encrypt/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.14) + +# # Basic Files +file(GLOB project_src + "symCrypt/*.cpp" +) + +project(encrpyt) + +option(ENCRPYT_CPP_BUILD_STANDALONE "Set if you wish to fetch all dependencies and build standalone unit tests." OFF) +option(USE_SYSTEM_OPENSSL "Use the system's OpenSSL if available" OFF) + +include(FindPackageHandleStandardArgs) +find_package(OpenSSL QUIET) +if(NOT OpenSSL_FOUND) + if(USE_SYSTEM_OPENSSL) + message(FATAL_ERROR "System OpenSSL not found. Set USE_SYSTEM_OPENSSL to OFF or install OpenSSL.") + else() + set(OPENSSL_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/openssl-src) # default path by CMake + set(OPENSSL_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/openssl) + set(OPENSSL_INCLUDE_DIR ${OPENSSL_INSTALL_DIR}/include) + set(OPENSSL_CONFIGURE_COMMAND ${OPENSSL_SOURCE_DIR}/config) + include(ExternalProject) + ExternalProject_Add( + OpenSSL + SOURCE_DIR ${OPENSSL_SOURCE_DIR} + GIT_REPOSITORY https://github.com/openssl/openssl.git + GIT_TAG openssl-3.2.1 + USES_TERMINAL_DOWNLOAD TRUE + CONFIGURE_COMMAND + ${OPENSSL_CONFIGURE_COMMAND} + --prefix=${OPENSSL_INSTALL_DIR} + --openssldir=${OPENSSL_INSTALL_DIR} + BUILD_COMMAND make + TEST_COMMAND "" + INSTALL_COMMAND make install + INSTALL_DIR ${OPENSSL_INSTALL_DIR} + ) + + file(MAKE_DIRECTORY ${OPENSSL_INCLUDE_DIR}) + + add_library(OpenSSL::SSL STATIC IMPORTED GLOBAL) + set_property(TARGET OpenSSL::SSL PROPERTY IMPORTED_LOCATION ${OPENSSL_INSTALL_DIR}/lib/libssl.${OPENSSL_LIBRARY_SUFFIX}) + set_property(TARGET OpenSSL::SSL PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${OPENSSL_INCLUDE_DIR}) + add_dependencies(OpenSSL::SSL OpenSSL) + + add_library(OpenSSL::Crypto STATIC IMPORTED GLOBAL) + set_property(TARGET OpenSSL::Crypto PROPERTY IMPORTED_LOCATION ${OPENSSL_INSTALL_DIR}/lib/libcrypto.${OPENSSL_LIBRARY_SUFFIX}) + set_property(TARGET OpenSSL::Crypto PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${OPENSSL_INCLUDE_DIR}) + add_dependencies(OpenSSL::Crypto OpenSSL) + endif() +endif() + +add_library(encrpyt ${project_src}) +target_link_libraries(encrpyt PUBLIC OpenSSL::SSL OpenSSL::Crypto) +target_include_directories(encrpyt PUBLIC "../") +target_compile_options(encrpyt PRIVATE -Wall -Wextra -Wpedantic -Werror) + +if((CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR MODERN_CMAKE_BUILD_TESTING) AND(BUILD_TESTING)) + enable_testing() + add_subdirectory(uTests) +endif() diff --git a/encrypt/symCrypt/AES256CBC.cpp b/encrypt/symCrypt/AES256CBC.cpp new file mode 100644 index 0000000..39e463e --- /dev/null +++ b/encrypt/symCrypt/AES256CBC.cpp @@ -0,0 +1,76 @@ +#include "AES256CBC.hpp" + +#include +#include + +#include + +using namespace encrypt; + +using EVP_CIPHER_CTX_t = + std::unique_ptr; + +AES256CBC::AES256CBC(std::span iv) : iv(iv) {} + +void AES256CBC::encrypt(const std::string &key, + std::span plainText, + std::span &cipherText) const { + size_t blockSize = EVP_CIPHER_block_size(EVP_aes_256_cbc()); + size_t paddingSize = blockSize - (plainText.size() % blockSize); + size_t expectedSize = plainText.size() + paddingSize; + if (cipherText.size() < expectedSize) { + throw std::runtime_error("cipherText buffer is too small"); + } + EVP_CIPHER_CTX_t ctx(EVP_CIPHER_CTX_new(), ::EVP_CIPHER_CTX_free); + int res{}, outlen{}; + size_t totalOut{0}; + res = EVP_EncryptInit(ctx.get(), EVP_aes_256_cbc(), + reinterpret_cast(key.c_str()), + iv.data()); + if (res != 1) { + throw std::runtime_error("Error in EVP_EncryptInit"); + } + res = EVP_EncryptUpdate(ctx.get(), cipherText.data(), &outlen, + plainText.data(), plainText.size()); + if (res != 1) { + throw std::runtime_error("Error in EVP_EncryptUpdate"); + } + totalOut += outlen; + res = EVP_EncryptFinal(ctx.get(), cipherText.data() + totalOut, &outlen); + if (res != 1) { + throw std::runtime_error("Error in EVP_EncryptFinal"); + } + totalOut += outlen; + cipherText = cipherText.first(totalOut); +} + +void AES256CBC::decrypt(const std::string &key, + std::span cipherText, + std::span &plainText) const { + size_t expectedSize = cipherText.size(); + if (plainText.size() < expectedSize) { + throw std::runtime_error("plainText buffer is too small"); + } + EVP_CIPHER_CTX_t ctx(EVP_CIPHER_CTX_new(), ::EVP_CIPHER_CTX_free); + int res{}, outlen{}; + size_t totalOut{0}; + res = EVP_DecryptInit(ctx.get(), EVP_aes_256_cbc(), + reinterpret_cast(key.c_str()), + iv.data()); + if (res != 1) { + throw std::runtime_error("Error in EVP_DecryptInit"); + } + res = EVP_DecryptUpdate(ctx.get(), plainText.data(), &outlen, + cipherText.data(), cipherText.size()); + if (res != 1) { + throw std::runtime_error("Error in EVP_DecryptUpdate"); + } + totalOut += outlen; + + res = EVP_DecryptFinal(ctx.get(), plainText.data() + totalOut, &outlen); + if (res != 1) { + throw std::runtime_error("Error in EVP_DecryptFinal"); + } + totalOut += outlen; + plainText = plainText.first(totalOut); +} diff --git a/encrypt/symCrypt/AES256CBC.hpp b/encrypt/symCrypt/AES256CBC.hpp new file mode 100644 index 0000000..f52fa40 --- /dev/null +++ b/encrypt/symCrypt/AES256CBC.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "ISymCipher.hpp" + +namespace encrypt { +class AES256CBC : public ISymCipher { + public: + explicit AES256CBC(std::span iv); + + void encrypt(const std::string& key, std::span plainText, + std::span& secret) const override; + + void decrypt(const std::string& key, std::span secret, + std::span& plainText) const override; + + private: + std::span iv; +}; +} // namespace encrypt diff --git a/encrypt/symCrypt/FakeSymCipher.cpp b/encrypt/symCrypt/FakeSymCipher.cpp new file mode 100644 index 0000000..52d17b3 --- /dev/null +++ b/encrypt/symCrypt/FakeSymCipher.cpp @@ -0,0 +1,24 @@ +#include "FakeSymCipher.hpp" + +#include +#include + +using namespace encrypt; + +void FakeSymCipher::encrypt(const std::string& /* key */, + std::span plainText, + std::span& cipherText) const { + if (cipherText.size() < plainText.size()) { + throw std::runtime_error("cipherText buffer is too small"); + } + std::copy(plainText.begin(), plainText.end(), cipherText.begin()); +} + +void FakeSymCipher::decrypt(const std::string& /* key */, + std::span cipherText, + std::span& plainText) const { + if (plainText.size() < cipherText.size()) { + throw std::runtime_error("plainText buffer is too small"); + } + std::copy(cipherText.begin(), cipherText.end(), plainText.begin()); +} diff --git a/encrypt/symCrypt/FakeSymCipher.hpp b/encrypt/symCrypt/FakeSymCipher.hpp new file mode 100644 index 0000000..f8e82fb --- /dev/null +++ b/encrypt/symCrypt/FakeSymCipher.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "ISymCipher.hpp" + +namespace encrypt { +class FakeSymCipher : public ISymCipher { + public: + void encrypt(const std::string& key, std::span plainText, + std::span& secret) const override; + + void decrypt(const std::string& key, std::span secret, + std::span& plainText) const override; +}; +} // namespace encrypt diff --git a/encrypt/symCrypt/ISymCipher.hpp b/encrypt/symCrypt/ISymCipher.hpp new file mode 100644 index 0000000..bb1b169 --- /dev/null +++ b/encrypt/symCrypt/ISymCipher.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace encrypt { +struct ISymCipher { + virtual void encrypt(const std::string &key, + std::span plainText, + std::span &cipherText) const = 0; + + virtual void decrypt(const std::string &key, + std::span cipherText, + std::span &plainText) const = 0; +}; +} // namespace encrypt diff --git a/encrypt/uTests/CMakeLists.txt b/encrypt/uTests/CMakeLists.txt new file mode 100644 index 0000000..825f6fa --- /dev/null +++ b/encrypt/uTests/CMakeLists.txt @@ -0,0 +1,22 @@ +include(FetchContent) + +set(TEST_NAME ${PROJECT_NAME}-UT) + +FetchContent_Declare( + googletest + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG release-1.11.0 +) + +FetchContent_MakeAvailable(googletest) + +file(GLOB_RECURSE test_src + "*.cpp" +) + +add_executable(${TEST_NAME} ${test_src}) +target_link_libraries(${TEST_NAME} gtest_main ${PROJECT_NAME}) +set_target_properties(${TEST_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/test") + +include(GoogleTest) +gtest_discover_tests(${TEST_NAME}) diff --git a/encrypt/uTests/symCrypt/AES256CBCTests.cpp b/encrypt/uTests/symCrypt/AES256CBCTests.cpp new file mode 100644 index 0000000..2be9efb --- /dev/null +++ b/encrypt/uTests/symCrypt/AES256CBCTests.cpp @@ -0,0 +1,41 @@ +#include + +#include "encrypt/symCrypt/AES256CBC.hpp" +#include "gtest/gtest.h" + +using namespace encrypt; + +void generateAESKeyAndIV(std::vector &key, + std::vector &iv) { + key.resize(32); // AES 256-bit key size + if (RAND_bytes(key.data(), key.size()) != 1) { + throw std::runtime_error("Error generating AES key"); + } + + iv.resize(16); // IV size for AES CBC mode + if (RAND_bytes(iv.data(), iv.size()) != 1) { + throw std::runtime_error("Error generating IV"); + } +} + +TEST(AES256CBCTests, simpleEncryptDecrypt) { + std::vector aesKey, iv; + generateAESKeyAndIV(aesKey, iv); + AES256CBC aes({iv.data(), iv.size()}); + std::string plainText{"Hello word!"}; + + std::vector cipherText(16); + std::span cipherBuff = {cipherText.data(), cipherText.size()}; + aes.encrypt( + {aesKey.begin(), aesKey.end()}, + {reinterpret_cast(plainText.data()), plainText.size()}, + cipherBuff); + + std::vector decryptVec(16); + std::span decryptBuff = {decryptVec.data(), decryptVec.size()}; + + aes.decrypt({aesKey.begin(), aesKey.end()}, cipherBuff, decryptBuff); + + EXPECT_EQ(plainText, std::string(decryptBuff.data(), + decryptBuff.data() + decryptBuff.size())); +} From 3b24e24c6e4c44bd64476b38dd600fda018c47fd Mon Sep 17 00:00:00 2001 From: Wojtech Date: Wed, 28 Feb 2024 21:46:49 +0100 Subject: [PATCH 2/2] Add hash and readme --- encrypt/CMakeLists.txt | 2 +- encrypt/README.md | 57 +++++++++++++++++++++++++ encrypt/hash/FakeHash.cpp | 6 +++ encrypt/hash/FakeHash.hpp | 10 +++++ encrypt/hash/IHash.hpp | 11 +++++ encrypt/hash/SHA512.cpp | 31 ++++++++++++++ encrypt/hash/SHA512.hpp | 10 +++++ encrypt/symCrypt/ISymCipher.hpp | 1 + encrypt/uTests/symCrypt/SHA512Tests.cpp | 21 +++++++++ 9 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 encrypt/README.md create mode 100644 encrypt/hash/FakeHash.cpp create mode 100644 encrypt/hash/FakeHash.hpp create mode 100644 encrypt/hash/IHash.hpp create mode 100644 encrypt/hash/SHA512.cpp create mode 100644 encrypt/hash/SHA512.hpp create mode 100644 encrypt/uTests/symCrypt/SHA512Tests.cpp diff --git a/encrypt/CMakeLists.txt b/encrypt/CMakeLists.txt index 7b01e3a..54d3d6c 100644 --- a/encrypt/CMakeLists.txt +++ b/encrypt/CMakeLists.txt @@ -3,11 +3,11 @@ cmake_minimum_required(VERSION 3.14) # # Basic Files file(GLOB project_src "symCrypt/*.cpp" + "hash/*.cpp" ) project(encrpyt) -option(ENCRPYT_CPP_BUILD_STANDALONE "Set if you wish to fetch all dependencies and build standalone unit tests." OFF) option(USE_SYSTEM_OPENSSL "Use the system's OpenSSL if available" OFF) include(FindPackageHandleStandardArgs) diff --git a/encrypt/README.md b/encrypt/README.md new file mode 100644 index 0000000..dad4470 --- /dev/null +++ b/encrypt/README.md @@ -0,0 +1,57 @@ +# encrypt + +`encrypt` is a straightforward wrapper library built on top of the OpenSSL library, offering implementations of cryptographic algorithms such as SHA-512 hashing and AES-256-CBC encryption. + +## Features + +- Efficient implementation of SHA-512 hashing. +- Implementation of AES-256-CBC encryption with OpenSSL. +- Utilizes `std::span` for handling input and output data efficiently. + +## Usage + +### SHA512 + +To compute the SHA-512 hash of a message, you can use the `SHA512` class: + +```cpp +#include "encrypt/hash/SHA512.hpp" + +using namespace encrypt; + +int main() { + std::string message = "Hello, World!"; + SHA512 sha512; + std::string hash = sha512.hash({message.begin(), message.end()}); + std::cout << "SHA-512 hash of '" << message << "': " << hash << std::endl; + return 0; +} +``` +### AES256CBC +To perform AES-256-CBC encryption and decryption, you can use the `AES256CBC` class: + +```cpp +#include "encrypt/symCrypt/AES256CBC.hpp" + +using namespace encrypt; + +int main() { + std::string key = "YourKeyHere"; + std::string iv = "YourInitializationVectorHere"; + AES256CBC aes256cbc({iv.begin(), iv.end()}); + + std::string plaintext = "YourPlainTextHere"; + std::string ciphertext(plaintext.size() + AES_BLOCK_SIZE, '\0'); + + // Encryption + aes256cbc.encrypt(key, {plaintext.begin(), plaintext.end()}, {ciphertext.begin(), ciphertext.end()}); + std::cout << "Encrypted text: " << ciphertext << std::endl; + + // Decryption + std::string decryptedtext(plaintext.size(), '\0'); + aes256cbc.decrypt(key, {ciphertext.begin(), ciphertext.end()}, {decryptedtext.begin(), decryptedtext.end()}); + std::cout << "Decrypted text: " << decryptedtext << std::endl; + + return 0; +} +``` diff --git a/encrypt/hash/FakeHash.cpp b/encrypt/hash/FakeHash.cpp new file mode 100644 index 0000000..da16ca1 --- /dev/null +++ b/encrypt/hash/FakeHash.cpp @@ -0,0 +1,6 @@ +#include "FakeHash.hpp" +using namespace encrypt; + +std::string FakeHash::hash(std::span input) const { + return std::string(input.begin(), input.end()); +} diff --git a/encrypt/hash/FakeHash.hpp b/encrypt/hash/FakeHash.hpp new file mode 100644 index 0000000..a2200f5 --- /dev/null +++ b/encrypt/hash/FakeHash.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "IHash.hpp" + +namespace encrypt { +class FakeHash : public IHash { + public: + std::string hash(std::span input) const override; +}; +} // namespace encrypt diff --git a/encrypt/hash/IHash.hpp b/encrypt/hash/IHash.hpp new file mode 100644 index 0000000..430fd88 --- /dev/null +++ b/encrypt/hash/IHash.hpp @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +namespace encrypt { +class IHash { + public: + virtual ~IHash() = default; + virtual std::string hash(std::span input) const = 0; +}; +} // namespace encrypt diff --git a/encrypt/hash/SHA512.cpp b/encrypt/hash/SHA512.cpp new file mode 100644 index 0000000..f4940b5 --- /dev/null +++ b/encrypt/hash/SHA512.cpp @@ -0,0 +1,31 @@ +#include "SHA512.hpp" + +#include +#include + +#include +#include +#include + +using namespace encrypt; + +using EVP_MD_CTX_ptr = + std::unique_ptr; + +std::string SHA512::hash(std::span input) const { + EVP_MD_CTX_ptr ctx(EVP_MD_CTX_new(), ::EVP_MD_CTX_free); + if (!ctx) { + throw std::runtime_error("Error creating EVP_MD_CTX"); + } + if (!EVP_DigestInit_ex(ctx.get(), EVP_sha512(), nullptr)) { + throw std::runtime_error("Error initializing SHA-512 digest"); + } + if (!EVP_DigestUpdate(ctx.get(), input.data(), input.size())) { + throw std::runtime_error("Error updating SHA-512 digest"); + } + std::vector hash(SHA512_DIGEST_LENGTH); + if (!EVP_DigestFinal_ex(ctx.get(), hash.data(), nullptr)) { + throw std::runtime_error("Error finalizing SHA-512 digest"); + } + return std::string(hash.begin(), hash.end()); +} diff --git a/encrypt/hash/SHA512.hpp b/encrypt/hash/SHA512.hpp new file mode 100644 index 0000000..b9e2f8c --- /dev/null +++ b/encrypt/hash/SHA512.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "IHash.hpp" + +namespace encrypt { +class SHA512 : public IHash { + public: + std::string hash(std::span input) const override; +}; +} // namespace encrypt diff --git a/encrypt/symCrypt/ISymCipher.hpp b/encrypt/symCrypt/ISymCipher.hpp index bb1b169..792e686 100644 --- a/encrypt/symCrypt/ISymCipher.hpp +++ b/encrypt/symCrypt/ISymCipher.hpp @@ -5,6 +5,7 @@ namespace encrypt { struct ISymCipher { + virtual ~ISymCipher() = default; virtual void encrypt(const std::string &key, std::span plainText, std::span &cipherText) const = 0; diff --git a/encrypt/uTests/symCrypt/SHA512Tests.cpp b/encrypt/uTests/symCrypt/SHA512Tests.cpp new file mode 100644 index 0000000..658ff51 --- /dev/null +++ b/encrypt/uTests/symCrypt/SHA512Tests.cpp @@ -0,0 +1,21 @@ +#include + +#include "encrypt/hash/SHA512.hpp" +#include "gtest/gtest.h" + +using namespace encrypt; + +TEST(SHA512Tests, simpleHash) { + std::vector input(1024); + if (RAND_bytes(input.data(), input.size()) != 1) { + throw std::runtime_error("Error generating"); + } + SHA512 hash; + + auto s1 = hash.hash({input.data(), input.size()}); + auto s2 = hash.hash({input.data(), input.size()}); + auto s3 = hash.hash({input.data(), input.size()}); + EXPECT_EQ(s1, s2); + EXPECT_EQ(s1, s3); + EXPECT_NE(s1, std::string(input.begin(), input.end())); +}