From 547253a698978e26b57205e9d92c0646f8687ee7 Mon Sep 17 00:00:00 2001 From: ParkSuMin Date: Sat, 23 May 2026 23:31:22 +0300 Subject: [PATCH] Intial shitty commit --- .gitignore | 9 ++ CMakeLists.txt | 153 ++++++++++++++++++++++++++++++++ README.md | 68 ++++++++++++++ main.cpp | 236 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 466 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..586fc9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +build/ +.vs/ +out/ + +*.enc +important_data.txt +restored_data.txt +private.pem +public.pem diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a3475be --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,153 @@ +cmake_minimum_required(VERSION 3.24) # Requires 3.24+ for URL-based FetchContent binaries +project(OpenSSLExample CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(FetchContent) + +if(WIN32) + # Automatically downloads pre-built Windows OpenSSL binaries. + # The original placeholder URL "https://github.com" cannot be unpacked by FetchContent. + FetchContent_Declare( + openssl + URL "https://github.com/TaurusTLS-Developers/OpenSSL-Distribution/releases/download/v3.5.6/openssl-3.5.6-Windows-x64.zip" + ) + FetchContent_MakeAvailable(openssl) + + find_path(OPENSSL_INCLUDE_DIR + NAMES openssl/evp.h + PATHS + "${openssl_SOURCE_DIR}/include" + "${openssl_SOURCE_DIR}/x64/include" + "${openssl_SOURCE_DIR}/win64/include" + "${openssl_SOURCE_DIR}/openssl-3.5.6/include" + "${openssl_SOURCE_DIR}/openssl-3.5.6-Windows-x64/include" + NO_DEFAULT_PATH + ) + + find_library(OPENSSL_CRYPTO_LIB + NAMES libcrypto crypto + PATHS + "${openssl_SOURCE_DIR}/lib" + "${openssl_SOURCE_DIR}/x64/lib" + "${openssl_SOURCE_DIR}/win64/lib" + "${openssl_SOURCE_DIR}/openssl-3.5.6/lib" + "${openssl_SOURCE_DIR}/openssl-3.5.6-Windows-x64/lib" + NO_DEFAULT_PATH + ) + + find_library(OPENSSL_SSL_LIB + NAMES libssl ssl + PATHS + "${openssl_SOURCE_DIR}/lib" + "${openssl_SOURCE_DIR}/x64/lib" + "${openssl_SOURCE_DIR}/win64/lib" + "${openssl_SOURCE_DIR}/openssl-3.5.6/lib" + "${openssl_SOURCE_DIR}/openssl-3.5.6-Windows-x64/lib" + NO_DEFAULT_PATH + ) + + find_file(OPENSSL_CRYPTO_DLL + NAMES libcrypto-3-x64.dll libcrypto-3.dll + PATHS + "${openssl_SOURCE_DIR}/bin" + "${openssl_SOURCE_DIR}/x64/bin" + "${openssl_SOURCE_DIR}/win64/bin" + "${openssl_SOURCE_DIR}/openssl-3.5.6/bin" + "${openssl_SOURCE_DIR}/openssl-3.5.6-Windows-x64/bin" + NO_DEFAULT_PATH + ) + + find_file(OPENSSL_SSL_DLL + NAMES libssl-3-x64.dll libssl-3.dll + PATHS + "${openssl_SOURCE_DIR}/bin" + "${openssl_SOURCE_DIR}/x64/bin" + "${openssl_SOURCE_DIR}/win64/bin" + "${openssl_SOURCE_DIR}/openssl-3.5.6/bin" + "${openssl_SOURCE_DIR}/openssl-3.5.6-Windows-x64/bin" + NO_DEFAULT_PATH + ) + + if(NOT OPENSSL_INCLUDE_DIR) + file(GLOB_RECURSE OPENSSL_EVP_HEADERS "${openssl_SOURCE_DIR}/*/openssl/evp.h") + if(OPENSSL_EVP_HEADERS) + list(GET OPENSSL_EVP_HEADERS 0 OPENSSL_EVP_HEADER) + get_filename_component(OPENSSL_HEADER_DIR "${OPENSSL_EVP_HEADER}" DIRECTORY) + get_filename_component(OPENSSL_INCLUDE_DIR "${OPENSSL_HEADER_DIR}" DIRECTORY) + endif() + endif() + + if(NOT OPENSSL_CRYPTO_LIB) + file(GLOB_RECURSE OPENSSL_CRYPTO_LIBS + "${openssl_SOURCE_DIR}/*libcrypto*.lib" + "${openssl_SOURCE_DIR}/*crypto*.lib" + "${openssl_SOURCE_DIR}/*libcrypto*.dll.a" + ) + if(OPENSSL_CRYPTO_LIBS) + list(GET OPENSSL_CRYPTO_LIBS 0 OPENSSL_CRYPTO_LIB) + endif() + endif() + + if(NOT OPENSSL_SSL_LIB) + file(GLOB_RECURSE OPENSSL_SSL_LIBS + "${openssl_SOURCE_DIR}/*libssl*.lib" + "${openssl_SOURCE_DIR}/*ssl*.lib" + "${openssl_SOURCE_DIR}/*libssl*.dll.a" + ) + if(OPENSSL_SSL_LIBS) + list(GET OPENSSL_SSL_LIBS 0 OPENSSL_SSL_LIB) + endif() + endif() + + if(NOT OPENSSL_CRYPTO_DLL) + file(GLOB_RECURSE OPENSSL_CRYPTO_DLLS + "${openssl_SOURCE_DIR}/*libcrypto-3-x64.dll" + "${openssl_SOURCE_DIR}/*libcrypto-3.dll" + ) + if(OPENSSL_CRYPTO_DLLS) + list(GET OPENSSL_CRYPTO_DLLS 0 OPENSSL_CRYPTO_DLL) + endif() + endif() + + if(NOT OPENSSL_SSL_DLL) + file(GLOB_RECURSE OPENSSL_SSL_DLLS + "${openssl_SOURCE_DIR}/*libssl-3-x64.dll" + "${openssl_SOURCE_DIR}/*libssl-3.dll" + ) + if(OPENSSL_SSL_DLLS) + list(GET OPENSSL_SSL_DLLS 0 OPENSSL_SSL_DLL) + endif() + endif() + + if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_CRYPTO_LIB OR NOT OPENSSL_SSL_LIB) + message(FATAL_ERROR "Downloaded OpenSSL package does not contain the expected include/lib layout.") + endif() +else() + find_package(OpenSSL REQUIRED) + set(OPENSSL_INCLUDE_DIR "${OPENSSL_INCLUDE_DIR}") + set(OPENSSL_CRYPTO_LIB OpenSSL::Crypto) + set(OPENSSL_SSL_LIB OpenSSL::SSL) +endif() + +add_executable(crypto_app main.cpp) + +# Link against the downloaded binaries +target_include_directories(crypto_app PRIVATE "${OPENSSL_INCLUDE_DIR}") +target_link_libraries(crypto_app PRIVATE "${OPENSSL_CRYPTO_LIB}" "${OPENSSL_SSL_LIB}") + +# Copy DLL files to output directory so the executable can run +if(WIN32) + if(NOT OPENSSL_CRYPTO_DLL OR NOT OPENSSL_SSL_DLL) + message(FATAL_ERROR "Downloaded OpenSSL package does not contain the expected DLL files.") + endif() + + add_custom_command(TARGET crypto_app POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${OPENSSL_CRYPTO_DLL}" + "${OPENSSL_SSL_DLL}" + $ + ) +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..2f3579f --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# OpenSSL Hybrid Encryption Test + +Test C++17 project for hybrid file encryption with OpenSSL: + +- RSA key pair generation +- AES file encryption +- RSA encryption of the temporary AES key +- streaming file encryption/decryption + +The application creates a small test file, encrypts it, decrypts it back, and writes the restored result. + +## Requirements + +- CMake 3.24 or newer +- C++17 compiler +- On Windows: Visual Studio Build Tools or Visual Studio with the Desktop development with C++ workload + +On Windows, `CMakeLists.txt` downloads a pre-built OpenSSL package with `FetchContent` and copies the required DLLs next to the executable. + +## Build + +From the project directory: + +```powershell +cmake -S . -B build +cmake --build build --config Release +``` + +If you changed CMake options or headers and want a clean rebuild: + +```powershell +cmake --build build --config Release --clean-first +``` + +## Run + +With Visual Studio generators, the executable is usually in `build\Release`: + +```powershell +cd build\Release +.\crypto_app.exe +``` + +The program generates these files in the current working directory: + +- `private.pem` +- `public.pem` +- `important_data.txt` +- `important_data.enc` +- `restored_data.txt` + +Expected console output: + +```text +Generating RSA-4096 key pair... +Success! Saved 'private.pem' and 'public.pem'. +Encrypting file with public key... +Decrypting file with private key... +Done! Verify 'restored_data.txt' matching original inputs. +``` + +No output means the files match. + +## Notes + +If CMake reports `No CMAKE_CXX_COMPILER could be found`, install Visual Studio Build Tools and enable the C++ desktop workload. + +If the executable cannot find OpenSSL DLLs, rebuild the project so the post-build copy step can place the DLLs next to `crypto_app.exe`. diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..9be02c8 --- /dev/null +++ b/main.cpp @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define DATA_WRITE(data) reinterpret_cast(data) +#define DATA_READ(data) reinterpret_cast(data) + +constexpr size_t BUFFER_SIZE = 4096; +// RAII +struct PKEYDeleter { void operator()(EVP_PKEY* p) const { EVP_PKEY_free(p); } }; +struct CTXDeleter { void operator()(EVP_PKEY_CTX* p) const { EVP_PKEY_CTX_free(p); } }; +struct CipherDeleter { void operator()(EVP_CIPHER_CTX* ctx) const { EVP_CIPHER_CTX_free(ctx); } }; +struct BIODeleter { void operator()(BIO* b) const { BIO_free_all(b); } }; + +typedef std::unique_ptr PRIVATE_KEY; +typedef std::unique_ptr PUBLIC_KEY_CONTEXT; +typedef std::unique_ptr KEY_BIO; +typedef std::unique_ptr CIPTHER_CONTEXT; + +void generate_rsa_keypair(const std::string& private_key_path, const std::string& public_key_path) { + // Initialize the context for key generation + PUBLIC_KEY_CONTEXT ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)); + if (!ctx) { + throw std::runtime_error("Failed to create keygen context."); + } + + if (EVP_PKEY_keygen_init(ctx.get()) <= 0) { + throw std::runtime_error("Failed to initialize keygen context."); + } + + // Set the RSA key size + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), 4096) <= 0) { + throw std::runtime_error("Failed to set RSA key size."); + } + + // Generate key structure + EVP_PKEY* raw_pkey = nullptr; + if (EVP_PKEY_keygen(ctx.get(), &raw_pkey) <= 0) { + throw std::runtime_error("Failed to generate key pair."); + } + PRIVATE_KEY pkey(raw_pkey); + + // Save the Private Key + KEY_BIO priv_bio(BIO_new_file(private_key_path.c_str(), "w")); + if (!priv_bio) { + throw std::runtime_error("Failed to create private key file."); + } + // TODO: use private key with password + if (PEM_write_bio_PrivateKey(priv_bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr) <= 0) { + throw std::runtime_error("Failed to write private key to file."); + } + + // Save the Public Key + KEY_BIO pub_bio(BIO_new_file(public_key_path.c_str(), "w")); + if (!pub_bio) + throw std::runtime_error("Failed to create public key file."); + + if (PEM_write_bio_PUBKEY(pub_bio.get(), pkey.get()) <= 0) + throw std::runtime_error("Failed to write public key to file."); +} + +// Load Public Key from file +PRIVATE_KEY load_public_key(const std::string& path) { + KEY_BIO file(BIO_new_file(path.c_str(), "r")); + if (!file) + throw std::runtime_error("Cannot open public key file."); + + EVP_PKEY* pkey = PEM_read_bio_PUBKEY(file.get(), nullptr, nullptr, nullptr); + if (!pkey) + throw std::runtime_error("Failed to read public key."); + return PRIVATE_KEY(pkey); +} + +// Load Private Key from file +PRIVATE_KEY load_private_key(const std::string& path) { + KEY_BIO file(BIO_new_file(path.c_str(), "r")); + + if (!file) + throw std::runtime_error("Cannot open private key file."); + EVP_PKEY* pkey = PEM_read_bio_PrivateKey(file.get(), nullptr, nullptr, nullptr); + if (!pkey) + throw std::runtime_error("Failed to read private key."); + + return PRIVATE_KEY(pkey); +} + +// ENCRYPTION +void hybrid_encrypt(const std::string& input_path, const std::string& output_path, const std::string& pub_key_path) { + auto pub_key = load_public_key(pub_key_path); + + // Generate ephemeral AES key and IV + unsigned char aes_key[32]; + unsigned char iv[16]; + if (RAND_bytes(aes_key, sizeof(aes_key)) != 1 || RAND_bytes(iv, sizeof(iv)) != 1) { + throw std::runtime_error("Failed to generate random AES key/IV."); + } + + // Encrypt the AES key using the RSA Public Key + PUBLIC_KEY_CONTEXT rsa_ctx(EVP_PKEY_CTX_new(pub_key.get(), nullptr)); + if (!rsa_ctx || EVP_PKEY_encrypt_init(rsa_ctx.get()) <= 0) + throw std::runtime_error("RSA init failed."); + + size_t encrypted_key_len = 0; + if (EVP_PKEY_encrypt(rsa_ctx.get(), nullptr, &encrypted_key_len, aes_key, sizeof(aes_key)) <= 0) { + throw std::runtime_error("RSA encrypted key size calculation failed."); + } + std::vector encrypted_key(encrypted_key_len); + if (EVP_PKEY_encrypt(rsa_ctx.get(), encrypted_key.data(), &encrypted_key_len, aes_key, sizeof(aes_key)) <= 0) + throw std::runtime_error("RSA encryption failed."); + + // Open file streams + std::ifstream in_file(input_path, std::ios::binary); + std::ofstream out_file(output_path, std::ios::binary); + if (!in_file || !out_file) throw std::runtime_error("File stream error."); + + // Write metadata header + uint32_t key_len_header = static_cast(encrypted_key_len); + out_file.write(DATA_WRITE(&key_len_header), sizeof(key_len_header)); + out_file.write(DATA_WRITE(encrypted_key.data()), encrypted_key_len); + out_file.write(DATA_WRITE(iv), sizeof(iv)); + + // Stream encrypt the actual file data via AES + CIPTHER_CONTEXT aes_ctx(EVP_CIPHER_CTX_new()); + if (!aes_ctx || EVP_EncryptInit_ex(aes_ctx.get(), EVP_aes_256_cbc(), nullptr, aes_key, iv) != 1) { + throw std::runtime_error("AES init failed."); + } + + std::vector in_buf(BUFFER_SIZE); + std::vector out_buf(BUFFER_SIZE + EVP_MAX_BLOCK_LENGTH); + int out_len = 0; + + while (in_file.read(in_buf.data(), BUFFER_SIZE) || in_file.gcount() > 0) { + if (EVP_EncryptUpdate(aes_ctx.get(), out_buf.data(), &out_len, + reinterpret_cast(in_buf.data()), in_file.gcount()) != 1) { + throw std::runtime_error("AES update failed."); + } + out_file.write(DATA_WRITE(out_buf.data()), out_len); + } + + if (EVP_EncryptFinal_ex(aes_ctx.get(), out_buf.data(), &out_len) != 1) + throw std::runtime_error("AES final failed."); + out_file.write(DATA_WRITE(out_buf.data()), out_len); +} + +// DECRYPTION +void hybrid_decrypt(const std::string& input_path, const std::string& output_path, const std::string& priv_key_path) { + auto priv_key = load_private_key(priv_key_path); + + std::ifstream input_file(input_path, std::ios::binary); + std::ofstream output_file(output_path, std::ios::binary); + if (!input_file || !output_file) throw std::runtime_error("File stream error."); + + // Read metadata header + uint32_t encrypted_key_len = 0; + input_file.read(DATA_READ(&encrypted_key_len), sizeof(encrypted_key_len)); + + std::vector encrypted_key(encrypted_key_len); + input_file.read(DATA_READ(encrypted_key.data()), encrypted_key_len); + + unsigned char iv[16]{}; + input_file.read(DATA_READ(iv), sizeof(iv)); + + // Decrypt the secret AES key using the RSA Private Key + PUBLIC_KEY_CONTEXT rsa_ctx(EVP_PKEY_CTX_new(priv_key.get(), nullptr)); + if (!rsa_ctx || EVP_PKEY_decrypt_init(rsa_ctx.get()) <= 0) throw std::runtime_error("RSA decrypt init failed."); + + size_t aes_key_len = 0; + if (EVP_PKEY_decrypt(rsa_ctx.get(), nullptr, &aes_key_len, encrypted_key.data(), encrypted_key_len) <= 0) + throw std::runtime_error("RSA decrypted key size calculation failed."); + + std::vector aes_key(aes_key_len); + if (EVP_PKEY_decrypt(rsa_ctx.get(), aes_key.data(), &aes_key_len, encrypted_key.data(), encrypted_key_len) <= 0) { + throw std::runtime_error("RSA decryption failed. Key might be wrong or corrupted."); + } + if (aes_key_len != 32) + throw std::runtime_error("RSA decryption produced an unexpected AES key size."); + + aes_key.resize(aes_key_len); + + // Stream decrypt the file data using the recovered AES key + CIPTHER_CONTEXT aes_ctx(EVP_CIPHER_CTX_new()); + if (!aes_ctx || EVP_DecryptInit_ex(aes_ctx.get(), EVP_aes_256_cbc(), nullptr, aes_key.data(), iv) != 1) { + throw std::runtime_error("AES decrypt init failed."); + } + + std::vector input_buf(BUFFER_SIZE); + std::vector output_buf(BUFFER_SIZE + EVP_MAX_BLOCK_LENGTH); + int out_len = 0; + + while (input_file.read(input_buf.data(), BUFFER_SIZE) || input_file.gcount() > 0) { + if (EVP_DecryptUpdate(aes_ctx.get(), output_buf.data(), &out_len, + reinterpret_cast(input_buf.data()), input_file.gcount()) != 1) { + throw std::runtime_error("AES decrypt update failed."); + } + output_file.write(DATA_READ(output_buf.data()), out_len); + } + + if (EVP_DecryptFinal_ex(aes_ctx.get(), output_buf.data(), &out_len) != 1) { + throw std::runtime_error("Decryption integrity check failed."); + } + output_file.write(DATA_READ(output_buf.data()), out_len); +} + +int main() { + try { + // Setup a test file + std::ofstream f("important_data.txt"); + f << "Top secret hybrid structural data streams."; + + std::cout << "Generating RSA-4096 key pair...\n"; + generate_rsa_keypair("private.pem", "public.pem"); + std::cout << "Success! Saved 'private.pem' and 'public.pem'.\n"; + + std::cout << "Encrypting file with public key...\n"; + hybrid_encrypt("important_data.txt", "important_data.enc", "public.pem"); + + std::cout << "Decrypting file with private key...\n"; + hybrid_decrypt("important_data.enc", "restored_data.txt", "private.pem"); + + std::cout << "Done! Verify 'restored_data.txt' matching original inputs.\n"; + } catch (const std::exception& e) { + std::cerr << "Pipeline failure: " << e.what() << "\n"; + return 1; + } + return 0; +}