├── tests ├── testImage │ └── ele.jpg ├── steganography_test.cpp └── crypto_test.cpp ├── .gitignore ├── batch ├── clean.bat ├── run.bat └── build.bat ├── external ├── tinyaes │ ├── aes.hpp │ ├── aes.h │ └── aes.c └── stb │ └── stb_image_write.h ├── src ├── stb_impl.cpp ├── Steganography.h ├── Crypto.h ├── main.cpp ├── Application.h ├── Steganography.cpp ├── Crypto.cpp └── Application.cpp ├── LICENSE ├── CMakeLists.txt └── README.md /tests/testImage/ele.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psdoood/SteganoPass/HEAD/tests/testImage/ele.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.exe 3 | *.o 4 | notes.txt 5 | .vscode 6 | imagesInput 7 | imagesOutput 8 | imgui.ini -------------------------------------------------------------------------------- /batch/clean.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd .. 3 | if exist build rmdir /s /q build 4 | mkdir build 5 | echo Build directory cleaned and recreated. -------------------------------------------------------------------------------- /batch/run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | ..\build\Release\SteganoPass_test.exe 3 | echo Tests Completed. Running Executable... 4 | ..\build\Release\SteganoPass.exe 5 | -------------------------------------------------------------------------------- /batch/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd .. 3 | if not exist build mkdir build 4 | cd build 5 | cmake .. 6 | cmake --build . --config Release 7 | cd .. 8 | echo Build completed. Executable and Tests can be found in build\Release\ -------------------------------------------------------------------------------- /external/tinyaes/aes.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _AES_HPP_ 2 | #define _AES_HPP_ 3 | 4 | #ifndef __cplusplus 5 | #error Do not include the hpp header in a c project! 6 | #endif //__cplusplus 7 | 8 | extern "C" { 9 | #include "aes.h" 10 | } 11 | 12 | #endif //_AES_HPP_ 13 | -------------------------------------------------------------------------------- /src/stb_impl.cpp: -------------------------------------------------------------------------------- 1 | //Single file that includes the full implementations of 2 | //both stb_image.h and stb_image_write.h 3 | #define STB_IMAGE_IMPLEMENTATION 4 | #define STB_IMAGE_WRITE_IMPLEMENTATION 5 | #include "../external/stb/stb_image.h" 6 | #include "../external/stb/stb_image_write.h" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2024 psdoood 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/Steganography.h: -------------------------------------------------------------------------------- 1 | //Steganography.h - Header file for handling the hiding and extracting of data from images. 2 | 3 | #ifndef STEGANOGRAPHY_H 4 | #define STEGANOGRAPHY_H 5 | 6 | #include 7 | #include 8 | #include "../external/stb/stb_image.h" 9 | #include "../external/stb/stb_image_write.h" 10 | 11 | //Image type to store and keep track of loaded image data 12 | struct Image{ 13 | unsigned char* data; 14 | int width; 15 | int height; 16 | int channels; 17 | }; 18 | 19 | class Steganography { 20 | public: 21 | Steganography(){} 22 | ~Steganography(){} 23 | 24 | //Hides data in input image, and saves it to the output image 25 | bool hideData(Image& img, const std::vector& data); 26 | 27 | //Extracts hidden data from an input image 28 | std::vector extractData(const Image& img); 29 | 30 | //Checks if a given data size will fit in image 31 | bool canHideData(const Image& img, size_t dataSize); 32 | 33 | //Returns the max amount of data (in bytes) that can be hidden in the image 34 | size_t maxDataSize(const Image& img); 35 | 36 | //Loads and converts image to PNG format 37 | Image loadAndConvert(const std::string& inPath); 38 | 39 | //Saves the loaded image to location specified in outPath 40 | bool saveImage(const Image& img, const std::string& outPath); 41 | 42 | //Cleans image data from memory 43 | void cleanImage(Image& img); 44 | 45 | //Converts binary data to type string 46 | std::string convertToStr(const std::vector& data); 47 | }; 48 | 49 | #endif -------------------------------------------------------------------------------- /src/Crypto.h: -------------------------------------------------------------------------------- 1 | //Crypto.h - Header file for handling of the encryption and decryption of data. 2 | 3 | #ifndef CRYPTO_H 4 | #define CRYPTO_H 5 | 6 | #include 7 | #include 8 | #include "../external/tinyaes/aes.hpp" 9 | 10 | class Crypto { 11 | public: 12 | Crypto(){} 13 | ~Crypto(){} 14 | 15 | //Sets the encryption/decryption key 16 | void setKey(const std::string& inKey); 17 | 18 | //Checks if a key has been set 19 | bool isKeySet(); 20 | 21 | //Inputs data to be encrypted 22 | std::vector encryptData(const std::vector& data); 23 | 24 | //Inputs data to be decrypted 25 | std::vector decryptData(const std::vector& data); 26 | 27 | //Byte to string conversion, and vice versa 28 | std::string bytesToString(const std::vector& bytes); 29 | std::vector stringToBytes(const std::string& str); 30 | 31 | private: 32 | std::vector m_key; 33 | bool m_keyIsSet = false; 34 | std::vector m_iv; 35 | AES_ctx m_ctx; 36 | 37 | //Inits AES context with m_key and m_iv 38 | void initCtx(); 39 | 40 | //Generates a random IV (Initialization vector) 41 | void generateIV(); 42 | 43 | //Padding functions 44 | //Makes sure key/data is correct length (block size) for AES 45 | std::vector padKey(const std::vector& key); 46 | std::vector padData(const std::vector& data); 47 | std::vector unpad(const std::vector& data); 48 | 49 | //Resets obj for next use 50 | void reset(); 51 | 52 | //Checks if deecrypted data is non empty 53 | bool isDecryptionValid(const std::vector& decryptedData); 54 | }; 55 | 56 | #endif -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Application.h" 3 | 4 | int main(int, char**) 5 | { 6 | if (!glfwInit()){return 1;} 7 | 8 | //GL 3.0 + GLSL 130 9 | const char* glsl_version = "#version 130"; 10 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 11 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 12 | 13 | //Create window with graphics context 14 | GLFWwindow* window = glfwCreateWindow(1280, 720, "SteganoPass", NULL, NULL); 15 | if (window == NULL) 16 | return 1; 17 | glfwMakeContextCurrent(window); 18 | glfwSwapInterval(1); //Enable vsync 19 | 20 | //Setup Dear ImGui context 21 | IMGUI_CHECKVERSION(); 22 | ImGui::CreateContext(); 23 | ImGuiIO& io = ImGui::GetIO(); (void)io; 24 | 25 | //Setup Dear ImGui style 26 | ImGui::StyleColorsDark(); 27 | 28 | //Setup Platform/Renderer backends 29 | ImGui_ImplGlfw_InitForOpenGL(window, true); 30 | ImGui_ImplOpenGL3_Init(glsl_version); 31 | 32 | //Main loop 33 | while(!glfwWindowShouldClose(window)) 34 | { 35 | glfwPollEvents(); 36 | 37 | //Start the Dear ImGui frame 38 | ImGui_ImplOpenGL3_NewFrame(); 39 | ImGui_ImplGlfw_NewFrame(); 40 | ImGui::NewFrame(); 41 | 42 | //ImGui::ShowDemoWindow(); 43 | appUI::renderUI(); 44 | 45 | //Rendering 46 | ImGui::Render(); 47 | int display_w, display_h; 48 | glfwGetFramebufferSize(window, &display_w, &display_h); 49 | glViewport(0, 0, display_w, display_h); 50 | glClearColor(0.45f, 0.55f, 0.60f, 1.00f); 51 | glClear(GL_COLOR_BUFFER_BIT); 52 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 53 | 54 | glfwSwapBuffers(window); 55 | } 56 | 57 | //Cleanup 58 | ImGui_ImplOpenGL3_Shutdown(); 59 | ImGui_ImplGlfw_Shutdown(); 60 | ImGui::DestroyContext(); 61 | 62 | glfwDestroyWindow(window); 63 | glfwTerminate(); 64 | 65 | return 0; 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /tests/steganography_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Steganography.h" 3 | #include 4 | #include 5 | namespace fs = std::filesystem; 6 | 7 | class SteganographyTest : public ::testing::Test{ 8 | protected: 9 | Steganography steg; 10 | const std::string testImgPath = (fs::path(__FILE__).parent_path() / "testImage" / "ele.jpg").string(); 11 | const std::string outImgPath = (fs::path(__FILE__).parent_path() / "testImage" / "outputEle.png").string(); 12 | 13 | void SetUp() override {} 14 | void TearDown() override{ 15 | std::remove(outImgPath.c_str()); 16 | } 17 | }; 18 | 19 | TEST_F(SteganographyTest, HideAndExtract){ 20 | std::string originalData = "TestSecretData123"; 21 | std::vector data(originalData.begin(), originalData.end()); 22 | 23 | Image img = steg.loadAndConvert(testImgPath); 24 | steg.hideData(img, data); 25 | 26 | std::string extractedData = steg.convertToStr(steg.extractData(img)); 27 | 28 | EXPECT_EQ(originalData, extractedData); 29 | } 30 | 31 | TEST_F(SteganographyTest, canHideData){ 32 | Image img = steg.loadAndConvert(testImgPath); 33 | size_t maxSize = steg.maxDataSize(img); 34 | 35 | EXPECT_TRUE(steg.canHideData(img, maxSize)); 36 | EXPECT_FALSE(steg.canHideData(img, maxSize + 1)); 37 | } 38 | 39 | TEST_F(SteganographyTest, maxDataSize){ 40 | Image img = steg.loadAndConvert(testImgPath); 41 | size_t maxSize = ((img.height * img.width * 4) / 8) - 1; 42 | 43 | EXPECT_EQ(maxSize, steg.maxDataSize(img)); 44 | } 45 | 46 | TEST_F(SteganographyTest, loadAndConvert){ 47 | Image img = steg.loadAndConvert(testImgPath); 48 | 49 | EXPECT_NE(img.data, nullptr); 50 | EXPECT_GT(img.height, 0); 51 | EXPECT_GT(img.width, 0); 52 | EXPECT_EQ(img.channels, 4); 53 | } 54 | 55 | TEST_F(SteganographyTest, saveImage){ 56 | Image img = steg.loadAndConvert(testImgPath); 57 | 58 | EXPECT_TRUE(steg.saveImage(img, outImgPath)); 59 | } 60 | 61 | TEST_F(SteganographyTest, cleanImage){ 62 | Image img = steg.loadAndConvert(testImgPath); 63 | steg.cleanImage(img); 64 | 65 | EXPECT_EQ(img.data, nullptr); 66 | EXPECT_EQ(img.height, 0); 67 | EXPECT_EQ(img.width, 0); 68 | EXPECT_EQ(img.channels, 0); 69 | } 70 | 71 | TEST_F(SteganographyTest, hideDataTooLarge){ 72 | Image img = steg.loadAndConvert(testImgPath); 73 | std::vector largeData(steg.maxDataSize(img) + 1, '0'); 74 | 75 | EXPECT_FALSE(steg.hideData(img, largeData)); 76 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(SteganoPass VERSION 1.0) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | # ImGui 8 | include(FetchContent) 9 | FetchContent_Declare( 10 | imgui 11 | GIT_REPOSITORY https://github.com/ocornut/imgui.git 12 | GIT_SHALLOW TRUE 13 | ) 14 | FetchContent_MakeAvailable(imgui) 15 | 16 | # GLFW 17 | FetchContent_Declare( 18 | glfw 19 | GIT_REPOSITORY https://github.com/glfw/glfw.git 20 | GIT_TAG 3.3.8 21 | ) 22 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) 23 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) 24 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 25 | FetchContent_MakeAvailable(glfw) 26 | 27 | # TinyAES 28 | add_library(tinyaes STATIC 29 | ${CMAKE_SOURCE_DIR}/external/tinyaes/aes.c 30 | ) 31 | target_include_directories(tinyaes PUBLIC 32 | ${CMAKE_SOURCE_DIR}/external/tinyaes 33 | ) 34 | target_compile_definitions(tinyaes PUBLIC CBC=1) 35 | 36 | # Find OpenGL 37 | find_package(OpenGL REQUIRED) 38 | 39 | # Main executable 40 | add_executable(SteganoPass 41 | src/main.cpp 42 | src/Application.cpp 43 | src/Steganography.cpp 44 | src/stb_impl.cpp 45 | src/Crypto.cpp 46 | ${imgui_SOURCE_DIR}/imgui.cpp 47 | ${imgui_SOURCE_DIR}/imgui_demo.cpp 48 | ${imgui_SOURCE_DIR}/imgui_draw.cpp 49 | ${imgui_SOURCE_DIR}/imgui_tables.cpp 50 | ${imgui_SOURCE_DIR}/imgui_widgets.cpp 51 | ${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp 52 | ${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp 53 | ) 54 | 55 | target_include_directories(SteganoPass PRIVATE 56 | ${CMAKE_SOURCE_DIR}/src 57 | ${CMAKE_SOURCE_DIR}/external/stb 58 | ${CMAKE_SOURCE_DIR}/external/tinyaes 59 | ${imgui_SOURCE_DIR} 60 | ${imgui_SOURCE_DIR}/backends 61 | ${glfw_SOURCE_DIR}/include 62 | ) 63 | 64 | target_link_libraries(SteganoPass PRIVATE 65 | tinyaes 66 | glfw 67 | OpenGL::GL 68 | ) 69 | 70 | # GTest 71 | find_package(GTest QUIET) 72 | 73 | if(NOT GTest_FOUND) 74 | FetchContent_Declare( 75 | googletest 76 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 77 | DOWNLOAD_EXTRACT_TIMESTAMP TRUE 78 | ) 79 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 80 | FetchContent_MakeAvailable(googletest) 81 | endif() 82 | 83 | enable_testing() 84 | 85 | # Test executable 86 | add_executable( 87 | SteganoPass_test 88 | tests/crypto_test.cpp 89 | tests/steganography_test.cpp 90 | src/Crypto.cpp 91 | src/Steganography.cpp 92 | src/stb_impl.cpp 93 | ) 94 | 95 | if(GTest_FOUND) 96 | target_link_libraries(SteganoPass_test GTest::gtest_main) 97 | else() 98 | target_link_libraries(SteganoPass_test gtest_main) 99 | endif() 100 | 101 | target_link_libraries(SteganoPass_test tinyaes) 102 | 103 | target_include_directories(SteganoPass_test PRIVATE 104 | ${CMAKE_SOURCE_DIR}/src 105 | ${CMAKE_SOURCE_DIR}/external/stb 106 | ${CMAKE_SOURCE_DIR}/external/tinyaes 107 | ) 108 | 109 | include(GoogleTest) 110 | gtest_discover_tests(SteganoPass_test) 111 | 112 | -------------------------------------------------------------------------------- /external/tinyaes/aes.h: -------------------------------------------------------------------------------- 1 | #ifndef _AES_H_ 2 | #define _AES_H_ 3 | 4 | #include 5 | #include 6 | 7 | // #define the macros below to 1/0 to enable/disable the mode of operation. 8 | // 9 | // CBC enables AES encryption in CBC-mode of operation. 10 | // CTR enables encryption in counter-mode. 11 | // ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously. 12 | 13 | // The #ifndef-guard allows it to be configured before #include'ing or at compile time. 14 | #ifndef CBC 15 | #define CBC 1 16 | #endif 17 | 18 | #ifndef ECB 19 | #define ECB 1 20 | #endif 21 | 22 | #ifndef CTR 23 | #define CTR 1 24 | #endif 25 | 26 | 27 | #define AES128 1 28 | //#define AES192 1 29 | //#define AES256 1 30 | 31 | #define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only 32 | 33 | #if defined(AES256) && (AES256 == 1) 34 | #define AES_KEYLEN 32 35 | #define AES_keyExpSize 240 36 | #elif defined(AES192) && (AES192 == 1) 37 | #define AES_KEYLEN 24 38 | #define AES_keyExpSize 208 39 | #else 40 | #define AES_KEYLEN 16 // Key length in bytes 41 | #define AES_keyExpSize 176 42 | #endif 43 | 44 | struct AES_ctx 45 | { 46 | uint8_t RoundKey[AES_keyExpSize]; 47 | #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) 48 | uint8_t Iv[AES_BLOCKLEN]; 49 | #endif 50 | }; 51 | 52 | void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); 53 | #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) 54 | void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); 55 | void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); 56 | #endif 57 | 58 | #if defined(ECB) && (ECB == 1) 59 | // buffer size is exactly AES_BLOCKLEN bytes; 60 | // you need only AES_init_ctx as IV is not used in ECB 61 | // NB: ECB is considered insecure for most uses 62 | void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); 63 | void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); 64 | 65 | #endif // #if defined(ECB) && (ECB == !) 66 | 67 | 68 | #if defined(CBC) && (CBC == 1) 69 | // buffer size MUST be mutile of AES_BLOCKLEN; 70 | // Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme 71 | // NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() 72 | // no IV should ever be reused with the same key 73 | void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); 74 | void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); 75 | 76 | #endif // #if defined(CBC) && (CBC == 1) 77 | 78 | 79 | #if defined(CTR) && (CTR == 1) 80 | 81 | // Same function for encrypting as for decrypting. 82 | // IV is incremented for every block, and used after encryption as XOR-compliment for output 83 | // Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme 84 | // NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() 85 | // no IV should ever be reused with the same key 86 | void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); 87 | 88 | #endif // #if defined(CTR) && (CTR == 1) 89 | 90 | 91 | #endif // _AES_H_ 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SteganoPass - Hidden in Plain Sight 2 | 3 | ## Overview 4 | This is a lightweight, privacy focused password concealer that uses steganography to hide encrypted passwords within image files. This approach provides an additional layer of security by hiding sensitive information in plain sight. 5 | 6 | ## Features 7 | - **Encryption** - Encrypts the passwords using user defined key before being hidden in images. 8 | - **Steganography** - Hides the passwords within images using LSB steganography. 9 | - **Multiple Format Support** - Compatible with image formats such as JPG, BMP, PNG, TGA, and PSD. 10 | - **Simple GUI** - Intuitive interface for hiding and extracting passwords from images easily. 11 | - **File Explorer** - Built in file navigation system for easy image selection. 12 | 13 | ![noImageGUI](https://github.com/user-attachments/assets/ca3edf41-5d21-49cc-aaa2-abfbc42d6af4) 14 | ![loadedImageGUI](https://github.com/user-attachments/assets/59fe9675-04e5-4f93-aa35-277982de914e) 15 | 16 | ## Requirements 17 | - **CMake** - (3.14 or higher) 18 | - **OpenGl** - (3.0 or higher) 19 | - **C++ Compiler** 20 | 21 | ## Platform Compatibility 22 | - **Windows** - Supported 23 | - **macOs** - Needs Testing/ Minor Adjustments 24 | - **Linux** - Needs Testing/ Minor Adjustments 25 | 26 | ## Installation 27 | Note: I have not tested SteganoPass on linux or macOS yet. 28 | 1. Clone the repository 29 | > git clone https://github.com/psdoood/SteganoPass.git 30 | - **For Windows Users** - I have provided batch files in the `batch` diretory to simplify the building process. 31 | 32 | 2. **build.bat** - Either double click or execute this file to start the build process. After it is complete an exe can be found in `build/Release` called `SteganoPass.exe`. 33 | 34 | 3. **run.bat** - Alternatively, you can execute this to run the gtest files and the executable. 35 | 36 | 4. **clean.bat** - Execute this if you want to clean the build directory. 37 | 2. Navigate to cloned repository 38 | > cd SteganoPass 39 | 3. Create a build directory and navigate to it 40 | > mkdir build && cd build 41 | 4. Generate the build files with CMake 42 | > cmake .. 43 | 5. Build the project 44 | > cmake --build . 45 | 6. Launch SteganoPass with the executable found in `build/Release` called `SteganoPass`. 46 | 47 | ## Usage 48 | 1. Launch SteganoPass 49 | 2. Use the File Explorer on the top left to load an image. 50 | 3. Enter the master key and a password you wish to hide in an image in the control window. 51 | 4. Click "Hide Data in Image" to hide the data in the loaded image. 52 | 5. Now save the image using the options in the settings window. 53 | 6. To retrieve password, select image containing the hidden data, enter the key, and click "Extract Data from Image". 54 | 55 | ## Acknowledgements 56 | - [ImGui](https://github.com/ocornut/imgui) for the GUI elements. 57 | - [stb](https://github.com/nothings/stb) for image loading and writing. 58 | - [tinyaes](https://github.com/kokke/tiny-AES-c) for simple AES encryption. 59 | 60 | ## License 61 | SteganoPass is licensed under the MIT [License.](LICENSE) 62 | 63 | ## Disclaimer 64 | This code was a personal project. Use trusted and reputable password managers for sensitive information. 65 | 66 | -------------------------------------------------------------------------------- /tests/crypto_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Crypto.h" 3 | 4 | class CryptoTest : public ::testing::Test{ 5 | protected: 6 | Crypto crypto; 7 | 8 | void SetUp() override {} 9 | }; 10 | 11 | TEST_F(CryptoTest, EncryptionDecryption){ 12 | crypto.setKey("TestMasterKey123"); 13 | std::string originalData = "TestSecretData123"; 14 | std::vector data(originalData.begin(), originalData.end()); 15 | 16 | std::vector encryptedData = crypto.encryptData(data); 17 | ASSERT_FALSE(encryptedData.empty()); 18 | 19 | crypto.setKey("TestMasterKey123"); 20 | std::vector decryptedData = crypto.decryptData(encryptedData); 21 | ASSERT_FALSE(decryptedData.empty()); 22 | 23 | std::string decryptedString(decryptedData.begin(), decryptedData.end()); 24 | EXPECT_EQ(originalData, decryptedString); 25 | } 26 | 27 | TEST_F(CryptoTest, EmptyData){ 28 | std::string originalData = ""; 29 | std::vector data(originalData.begin(), originalData.end()); 30 | 31 | crypto.setKey("TestMasterKey123"); 32 | std::vector encryptedData = crypto.encryptData(data); 33 | EXPECT_TRUE(encryptedData.empty()); 34 | } 35 | 36 | TEST_F(CryptoTest, EmptyKey){ 37 | crypto.setKey(""); 38 | 39 | EXPECT_FALSE(crypto.isKeySet()); 40 | } 41 | 42 | TEST_F(CryptoTest, LargeDataEncryptionDecryption){ 43 | std::string originalData(1000, 'A'); 44 | std::vector data(originalData.begin(), originalData.end()); 45 | 46 | crypto.setKey("TestMasterKey123"); 47 | std::vector encryptedData = crypto.encryptData(data); 48 | ASSERT_FALSE(encryptedData.empty()); 49 | 50 | crypto.setKey("TestMasterKey123"); 51 | std::vector decryptedData = crypto.decryptData(encryptedData); 52 | ASSERT_FALSE(decryptedData.empty()); 53 | 54 | std::string decryptedString(decryptedData.begin(), decryptedData.end()); 55 | EXPECT_EQ(originalData, decryptedString); 56 | } 57 | 58 | TEST_F(CryptoTest, MultipleEncryptionsWithSameKey){ 59 | std::string data1 = "FirstData"; 60 | std::string data2 = "SecondData"; 61 | 62 | crypto.setKey("TestMasterKey123"); 63 | std::vector encrypted1 = crypto.encryptData(std::vector(data1.begin(), data1.end())); 64 | crypto.setKey("TestMasterKey123"); 65 | std::vector encrypted2 = crypto.encryptData(std::vector(data2.begin(), data2.end())); 66 | 67 | EXPECT_NE(encrypted1, encrypted2); 68 | 69 | crypto.setKey("TestMasterKey123"); 70 | std::vector decrypted1 = crypto.decryptData(encrypted1); 71 | crypto.setKey("TestMasterKey123"); 72 | std::vector decrypted2 = crypto.decryptData(encrypted2); 73 | 74 | EXPECT_EQ(data1, std::string(decrypted1.begin(), decrypted1.end())); 75 | EXPECT_EQ(data2, std::string(decrypted2.begin(), decrypted2.end())); 76 | } 77 | 78 | TEST_F(CryptoTest, SpecialCharacters){ 79 | std::string originalData = "!@#$%^&*()_+{}|:<>?~`-=[]\\;',./"; 80 | std::vector data(originalData.begin(), originalData.end()); 81 | 82 | crypto.setKey("TestMasterKey123"); 83 | std::vector encryptedData = crypto.encryptData(data); 84 | ASSERT_FALSE(encryptedData.empty()); 85 | 86 | crypto.setKey("TestMasterKey123"); 87 | std::vector decryptedData = crypto.decryptData(encryptedData); 88 | std::string decryptedString(decryptedData.begin(), decryptedData.end()); 89 | EXPECT_EQ(originalData, decryptedString); 90 | } 91 | 92 | int main(int argc, char **argv){ 93 | ::testing::InitGoogleTest(&argc, argv); 94 | return RUN_ALL_TESTS(); 95 | } -------------------------------------------------------------------------------- /src/Application.h: -------------------------------------------------------------------------------- 1 | //Application.h - header for the general GUI related functionality 2 | 3 | #ifndef APPLICATION_H 4 | #define APPLICATION_H 5 | 6 | #include "imgui.h" 7 | #include "imgui_impl_glfw.h" 8 | #include "imgui_impl_opengl3.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "Crypto.h" 16 | #include "Steganography.h" 17 | 18 | const int MAX_DATA_BUFFER_SIZE = 1024; 19 | const float TOP_SECTION_HEIGHT_RATIO = 0.8f; 20 | const float BOTTOM_SECTION_HEIGHT_RATIO = 0.2f; 21 | 22 | //Struct to hold all variables needed for the function of the GUI 23 | struct AppState{ 24 | Steganography steganoObj; 25 | Crypto cryptoObj; 26 | std::string inImagePath = "Input Image Shown Here"; 27 | std::string currentPath = std::filesystem::current_path().string(); 28 | std::string lastLoadedPath; 29 | GLuint inImageTexture = 0; 30 | char masterKeyBuffer[AES_KEYLEN] = ""; 31 | char dataBuffer[MAX_DATA_BUFFER_SIZE] = ""; 32 | std::string masterKey; 33 | std::string data; 34 | std::string extractedData; 35 | Image loadedImg; 36 | std::string loadedImgFilename; 37 | std::vector files; 38 | 39 | //Popup window warning flags 40 | bool noImageWarning = false; 41 | bool noKeyWarning = false; 42 | bool noDataWarning = false; 43 | bool saveAsWarning = false; 44 | bool saveOverWarning = false; 45 | bool alteredImageNotSaved = false; 46 | 47 | //Iterates through each item in currentPath, and if it is a directory or an 48 | //Image it is added to AppState.files 49 | void updateFiles(){ 50 | files.clear(); 51 | for(const auto& item : std::filesystem::directory_iterator(currentPath)){ 52 | if(item.is_directory() || isImageFile(item.path().extension().string())) { 53 | files.push_back(item.path().filename().string()); 54 | } 55 | } 56 | std::sort(files.begin(), files.end()); 57 | } 58 | 59 | //Returns true if extension parameter is .jpeg, .jpg, .bmp, .png, .tga, .psd 60 | bool isImageFile(const std::string& extension){ 61 | std::vector extensions = {".jpeg", ".jpg", ".bmp", ".png", ".tga", ".psd"}; 62 | return std::find(extensions.begin(), extensions.end(), extension) != extensions.end(); 63 | } 64 | 65 | //Resets masterKeyBuffer, masterKey, dataBuffer, and data for reuse. 66 | void cleanControlFields(){ 67 | memset(masterKeyBuffer, 0, sizeof(masterKeyBuffer)); 68 | masterKey.clear(); 69 | memset(dataBuffer, 0, sizeof(dataBuffer)); 70 | data.clear(); 71 | } 72 | 73 | //Cleans the loaded Image and related variables for use in loading new image. 74 | void cleanInputImage(){ 75 | steganoObj.cleanImage(loadedImg); 76 | if(inImageTexture != 0){ 77 | glDeleteTextures(1, &inImageTexture); 78 | inImageTexture = 0; 79 | } 80 | inImagePath = "Input Image Shown Here"; 81 | loadedImgFilename.clear(); 82 | lastLoadedPath.clear(); 83 | } 84 | }; 85 | 86 | //Functions for GUI rendering, more specific descriptions in Application.cpp 87 | namespace appUI{ 88 | void renderUI(); 89 | void renderFileExplorer(const ImGuiViewport* mainViewport, const ImGuiWindowFlags& windowFlags, const float& halfWidth, const float& topSectionHeight, AppState& appState); 90 | void renderInputImageWindow(const ImGuiViewport* mainViewport, const ImGuiWindowFlags& windowFlags, const float& halfWidth, const float& topSectionHeight, AppState& appState); 91 | void renderControlWindow(const ImGuiViewport* mainViewport, const ImGuiWindowFlags& windowFlags, const float& halfWidth, const float& topSectionHeight, AppState& appState); 92 | void renderSettingsWindow(const ImGuiViewport* mainViewport, const ImGuiWindowFlags& windowFlags, const float& halfWidth, const float& topSectionHeight, AppState& appState); 93 | void renderPopUps(AppState& appState); 94 | GLuint loadTexture(const std::string& path, AppState& appState); 95 | } 96 | 97 | #endif -------------------------------------------------------------------------------- /src/Steganography.cpp: -------------------------------------------------------------------------------- 1 | #include "Steganography.h" 2 | #include 3 | #include 4 | 5 | bool Steganography::hideData(Image& img, const std::vector& data){ 6 | if(!canHideData(img, data.size())){ 7 | std::cerr << "CANNOT HIDE DATA IN IMAGE" << std::endl; 8 | return false; 9 | } 10 | 11 | size_t dataIndex = 0; size_t bitIndex = 0; 12 | size_t totalPixels = img.height * img.width; 13 | uint8_t dataSize = static_cast(data.size()); 14 | //Hide length of data first, so that it can be extracted later 15 | for(size_t i = 0; i < 8; i++){ 16 | bool sizeBit = (dataSize >> i) & 1; 17 | img.data[i] = (img.data[i] & 0xFE) | sizeBit; 18 | } 19 | //Hide the actual bits that are stored in data 20 | for(size_t i = 8; i < totalPixels * 4; i++){ 21 | if(dataIndex >= data.size()){ 22 | break; 23 | } 24 | bool dataBit = (data[dataIndex] >> (7 - bitIndex) & 1); 25 | img.data[i] = (img.data[i] & 0xFE) | dataBit; 26 | bitIndex++; 27 | if(bitIndex >= 8){ 28 | bitIndex = 0; 29 | dataIndex++; 30 | } 31 | } 32 | return true; 33 | } 34 | 35 | //************************************************************************************************************// 36 | 37 | std::vector Steganography::extractData(const Image& img){ 38 | //Determine the size of data, which is stored in the first byte 39 | uint8_t dataSize = 0; 40 | for(size_t i = 0; i < 8; i++){ 41 | bool sizeBit = img.data[i] & 1; 42 | dataSize |= (sizeBit << i); 43 | } 44 | std::vector result(dataSize, 0); 45 | size_t dataIndex = 0; size_t bitIndex = 0; 46 | for(size_t i = 8; i < (dataSize * 8) + 8; i++){ 47 | bool dataBit = img.data[i] & 1; 48 | result[dataIndex] |= (dataBit << (7 - bitIndex));; 49 | bitIndex++; 50 | 51 | if(bitIndex == 8){ 52 | dataIndex++; 53 | bitIndex = 0; 54 | } 55 | } 56 | 57 | return result; 58 | } 59 | 60 | //************************************************************************************************************// 61 | 62 | bool Steganography::canHideData(const Image& img, size_t dataSize){ 63 | size_t maxSize = maxDataSize(img); 64 | return dataSize <= maxSize; 65 | } 66 | 67 | //************************************************************************************************************// 68 | 69 | size_t Steganography::maxDataSize(const Image& img){ 70 | //each pixel can store 4 bits of data (RGBA), but only least signif. bit (lsb), so / 8 71 | size_t maxSize = (4 * img.height * img.width) / 8; 72 | //add room for metadata (size of total hidden data) 73 | maxSize -= 1; 74 | 75 | return maxSize; 76 | } 77 | 78 | //************************************************************************************************************// 79 | 80 | Image Steganography::loadAndConvert(const std::string& inPath){ 81 | int width, height, channels; 82 | unsigned char* data = stbi_load(inPath.c_str(), &width, &height, &channels, 4); 83 | 84 | if(data == nullptr){ 85 | std::cerr << "FAILED TO LOAD IMAGE" << std::endl; 86 | return Image(); 87 | } 88 | 89 | Image img; 90 | img.data = data; 91 | img.height = height; 92 | img.width = width; 93 | img.channels = 4; 94 | 95 | return img; 96 | } 97 | 98 | //************************************************************************************************************// 99 | 100 | bool Steganography::saveImage(const Image& img, const std::string& outPath){ 101 | if(stbi_write_png(outPath.c_str(), img.width, img.height, 4, img.data, img.width * 4) == 0) { 102 | std::cerr << "FAILED TO SAVE THE IMAGE TO: " << outPath << std::endl; 103 | return false; 104 | } 105 | return true; 106 | } 107 | 108 | //************************************************************************************************************// 109 | 110 | void Steganography::cleanImage(Image& img){ 111 | if(img.data != nullptr){ 112 | stbi_image_free(img.data); 113 | img.data = nullptr; 114 | } 115 | img.width = 0; 116 | img.height = 0; 117 | img.channels = 0; 118 | } 119 | 120 | //************************************************************************************************************// 121 | 122 | std::string Steganography::convertToStr(const std::vector& data){ 123 | return std::string(data.begin(), data.end()); 124 | } -------------------------------------------------------------------------------- /src/Crypto.cpp: -------------------------------------------------------------------------------- 1 | #include "Crypto.h" 2 | #include 3 | #include 4 | 5 | //Must be called first 6 | void Crypto::setKey(const std::string& inKey){ 7 | reset(); 8 | if(inKey.size() == 0){ 9 | std::cerr << "MUST PROVIDE INPUT KEY" << std::endl; 10 | m_keyIsSet = false; 11 | return; 12 | } 13 | 14 | m_key = padKey(stringToBytes(inKey)); 15 | m_keyIsSet = true; 16 | } 17 | 18 | //************************************************************************************************************// 19 | 20 | std::vector Crypto::encryptData(const std::vector& data){ 21 | if(m_key.empty() && !isKeySet()){ 22 | std::cerr << "KEY MUST BE SET BEFORE ENCRYPTION" << std::endl; 23 | return std::vector(); 24 | } 25 | 26 | if(data.size() == 0 || data.empty()){ 27 | std::cerr << "MUST PROVIDE DATA TO ENCRYPT" << std::endl; 28 | return std::vector(); 29 | } 30 | 31 | generateIV(); 32 | initCtx(); 33 | 34 | std::vector paddedData = padData(data); 35 | std::vector encryptedData(paddedData.size() + AES_BLOCKLEN); //add room for iv 36 | 37 | std::copy(m_iv.begin(), m_iv.end(), encryptedData.begin()); 38 | 39 | AES_CBC_encrypt_buffer(&m_ctx, paddedData.data(), paddedData.size()); 40 | 41 | std::copy(paddedData.begin(), paddedData.end(), encryptedData.begin() + AES_BLOCKLEN); 42 | 43 | reset(); 44 | return encryptedData; 45 | } 46 | 47 | //************************************************************************************************************// 48 | 49 | std::vector Crypto::decryptData(const std::vector& data){ 50 | if(m_key.empty() && !isKeySet()){ 51 | std::cerr << "KEY MUST BE SET BEFORE DECRYPTION" << std::endl; 52 | return std::vector(); 53 | } 54 | if(data.size() < AES_BLOCKLEN){ 55 | std::cerr << "ENCRYPTED DATA IS TOO SHORT" << std::endl; 56 | return std::vector(); 57 | } 58 | 59 | std::vector iv(data.begin(), data.begin() + AES_BLOCKLEN); 60 | m_iv = iv; 61 | initCtx(); 62 | 63 | std::vector decryptedData(data.begin() + AES_BLOCKLEN, data.end()); 64 | AES_CBC_decrypt_buffer(&m_ctx, decryptedData.data(), decryptedData.size()); 65 | 66 | std::vector unpaddedData = unpad(decryptedData); 67 | if(!isDecryptionValid(unpaddedData)){ 68 | return std::vector(); 69 | } 70 | 71 | reset(); 72 | return unpaddedData; 73 | } 74 | 75 | //************************************************************************************************************// 76 | 77 | std::string Crypto::bytesToString(const std::vector& bytes){ 78 | std::string str(bytes.begin(), bytes.end()); 79 | return str; 80 | } 81 | 82 | //************************************************************************************************************// 83 | 84 | std::vector Crypto::stringToBytes(const std::string& str){ 85 | std::vector bytes(str.begin(), str.end()); 86 | return bytes; 87 | } 88 | 89 | //************************************************************************************************************// 90 | 91 | void Crypto::initCtx(){ 92 | AES_init_ctx_iv(&m_ctx, m_key.data(), m_iv.data()); 93 | } 94 | 95 | //************************************************************************************************************// 96 | 97 | void Crypto::generateIV(){ 98 | std::random_device rd; 99 | std::mt19937 gen(rd()); 100 | std::uniform_int_distribution<> dis(0, 255); 101 | 102 | m_iv.resize(AES_BLOCKLEN); 103 | for(auto& byte : m_iv){ 104 | byte = static_cast(dis(gen)); 105 | } 106 | } 107 | 108 | //************************************************************************************************************// 109 | 110 | std::vector Crypto::padKey(const std::vector& key){ 111 | std::vector paddedKey = key; 112 | if(paddedKey.size() < AES_KEYLEN){ 113 | paddedKey.resize(AES_KEYLEN, 0); 114 | }else{ 115 | paddedKey.resize(AES_KEYLEN); 116 | } 117 | return paddedKey; 118 | } 119 | 120 | //************************************************************************************************************// 121 | 122 | std::vector Crypto::padData(const std::vector& data){ 123 | std::vector paddedData = data; 124 | size_t paddingAmount = AES_BLOCKLEN - (data.size() % AES_BLOCKLEN); 125 | //If no padding needed, still need to store that information at the end, so add empty AES block 126 | if(paddingAmount == 0){ 127 | paddingAmount = AES_BLOCKLEN; 128 | } 129 | paddedData.insert(paddedData.end(), paddingAmount, static_cast(paddingAmount)); 130 | return paddedData; 131 | } 132 | 133 | //************************************************************************************************************// 134 | 135 | std::vector Crypto::unpad(const std::vector& data){ 136 | if(data.empty()){ 137 | std::cerr << "EMPTY DATA" << std::endl; 138 | return std::vector(); 139 | } 140 | 141 | size_t paddingAmount = data.back(); 142 | if(paddingAmount == 0 || paddingAmount > AES_BLOCKLEN){ 143 | std::cerr << "DECRYPTION FAILED" << std::endl; 144 | return std::vector(); 145 | } 146 | 147 | if(data.size() < paddingAmount){ 148 | std::cerr << "DATA TOO SHORT FOR PADDING" << std::endl; 149 | return std::vector(); 150 | } 151 | reset(); 152 | return std::vector(data.begin(), data.end() - paddingAmount); 153 | } 154 | 155 | //************************************************************************************************************// 156 | 157 | void Crypto::reset(){ 158 | std::fill(m_key.begin(), m_key.end(), 0); 159 | std::fill(m_iv.begin(), m_iv.end(), 0); 160 | m_key.clear(); 161 | m_iv.clear(); 162 | } 163 | 164 | //************************************************************************************************************// 165 | 166 | bool Crypto::isKeySet(){ 167 | return m_keyIsSet; 168 | } 169 | 170 | //************************************************************************************************************// 171 | 172 | bool Crypto::isDecryptionValid(const std::vector& decryptedData){ 173 | if(decryptedData.empty()){ 174 | return false; 175 | } 176 | return true; 177 | } -------------------------------------------------------------------------------- /src/Application.cpp: -------------------------------------------------------------------------------- 1 | #include "Application.h" 2 | 3 | namespace appUI 4 | { 5 | AppState appState; 6 | 7 | //Main function for control of the GUI 8 | void renderUI(){ 9 | 10 | ImGuiWindowFlags windowFlags = 0; 11 | windowFlags |= ImGuiWindowFlags_NoCollapse; 12 | windowFlags |= ImGuiWindowFlags_NoResize; 13 | windowFlags |= ImGuiWindowFlags_NoMove; 14 | 15 | //Cause the UI to be fullscreen in relation to the glfw window 16 | const ImGuiViewport* mainViewport = ImGui::GetMainViewport(); 17 | ImGui::SetNextWindowPos(ImVec2(mainViewport->WorkPos.x, mainViewport->WorkPos.y), ImGuiCond_Always); 18 | ImGui::SetNextWindowSize(ImVec2(mainViewport->WorkSize.x, mainViewport->WorkSize.y), ImGuiCond_Always); 19 | ImGui::Begin("SteganoPass", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBringToFrontOnFocus); 20 | appState.updateFiles(); 21 | 22 | //Get width of the window for button and text placement 23 | float windowWidth = ImGui::GetWindowWidth(); 24 | float windowHeight = ImGui::GetWindowHeight(); 25 | float halfWidth = windowWidth * 0.5f; 26 | float topSectionHeight = windowHeight * TOP_SECTION_HEIGHT_RATIO; 27 | float bottomSectionHeight = windowHeight * BOTTOM_SECTION_HEIGHT_RATIO; 28 | 29 | //Main function calls for all GUI elements 30 | renderFileExplorer(mainViewport, windowFlags, halfWidth, topSectionHeight, appState); 31 | renderInputImageWindow(mainViewport, windowFlags, halfWidth, topSectionHeight, appState); 32 | renderControlWindow(mainViewport, windowFlags, halfWidth, topSectionHeight, appState); 33 | renderSettingsWindow(mainViewport, windowFlags, halfWidth, topSectionHeight, appState); 34 | renderPopUps(appState); 35 | 36 | ImGui::End(); 37 | } 38 | 39 | //************************************************************************************************************// 40 | //Location: Upper Left Window 41 | //Creates a simple file explorer window that lists the current directory, including only image files and other directories. 42 | //Provides basic functionality navigating through to other locations, and displays the current path at the top. 43 | void renderFileExplorer(const ImGuiViewport* mainViewport, const ImGuiWindowFlags& windowFlags, const float& halfWidth, const float& topSectionHeight, AppState& appState){ 44 | ImGui::SetNextWindowPos(ImVec2(mainViewport->WorkPos.x, mainViewport->WorkPos.y)); 45 | ImGui::SetNextWindowSize(ImVec2(halfWidth, topSectionHeight)); 46 | ImGui::Begin("File Explorer", nullptr, windowFlags); 47 | 48 | ImGui::Text("Current Path: %s", appState.currentPath.c_str()); 49 | if(ImGui::Button("<- Go Back")){ 50 | appState.currentPath = std::filesystem::path(appState.currentPath).parent_path().string(); 51 | appState.updateFiles(); 52 | } 53 | if(appState.files.empty()){ 54 | ImGui::Text("No images/folders in this directory"); 55 | }else{ 56 | for(const auto& file : appState.files){ 57 | std::string fullPath = (std::filesystem::path(appState.currentPath) / file).string(); 58 | bool isDirectory = std::filesystem::is_directory(fullPath); 59 | if(ImGui::Selectable(file.c_str())){ 60 | //If selected file is a directory, make it the current path and update the file explorer. 61 | 62 | if(isDirectory){ 63 | appState.currentPath = fullPath; 64 | appState.updateFiles(); 65 | }else{ 66 | //Selected path is image, remove previous image texure if needed. 67 | appState.inImagePath = fullPath; 68 | if(appState.inImageTexture != 0){ 69 | glDeleteTextures(1, &appState.inImageTexture); 70 | appState.inImageTexture = 0; 71 | } 72 | //Image is loaded here to loadedImg in the loadTexture function. 73 | appState.inImageTexture = loadTexture(appState.inImagePath, appState); 74 | appState.loadedImgFilename = std::filesystem::path(appState.inImagePath).filename().string(); 75 | } 76 | } 77 | if(ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNone) && appState.alteredImageNotSaved){ 78 | ImGui::SetTooltip("Image hasn't been saved."); 79 | } 80 | } 81 | } 82 | ImGui::End(); 83 | } 84 | 85 | //************************************************************************************************************// 86 | //Location: Upper Right Window 87 | //Creates a window that simply displays the texture created from the input image from the file explorer. 88 | void renderInputImageWindow(const ImGuiViewport* mainViewport, const ImGuiWindowFlags& windowFlags, const float& halfWidth, const float& topSectionHeight, AppState& appState){ 89 | ImGui::SetNextWindowPos(ImVec2(mainViewport->WorkPos.x + halfWidth, mainViewport->WorkPos.y)); 90 | ImGui::SetNextWindowSize(ImVec2(halfWidth, topSectionHeight)); 91 | ImGui::Begin("Input Image", nullptr, windowFlags); 92 | 93 | //Show a button in place of image if no image has been loaded 94 | if(appState.inImageTexture == 0){ 95 | ImGui::Button(appState.inImagePath.c_str(), ImVec2(halfWidth - 20, topSectionHeight - 40)); 96 | } else{ 97 | float aspectRatio = 0.0f; 98 | ImVec2 imageSize(halfWidth - 20, halfWidth - 40); 99 | //Recalculate apsect ratio and image size only when a new image is loaded, prevent recalculation each frame. 100 | if(appState.inImagePath != appState.lastLoadedPath){ 101 | aspectRatio = (float)appState.loadedImg.width / appState.loadedImg.height; 102 | imageSize.x = std::min(imageSize.x, imageSize.y * aspectRatio); 103 | imageSize.y = halfWidth / aspectRatio; 104 | appState.lastLoadedPath = appState.inImagePath; 105 | } 106 | ImGui::SetCursorPos(ImVec2((halfWidth - imageSize.x) * 0.5f, (topSectionHeight - imageSize.y) * 0.5f)); 107 | ImGui::Image((ImTextureID)appState.inImageTexture, imageSize); 108 | } 109 | ImGui::End(); 110 | } 111 | 112 | //************************************************************************************************************// 113 | //Location: Lower Left Window 114 | //Craetes a window for the input of master key, data, and the output of extracted data from images. 115 | void renderControlWindow(const ImGuiViewport* mainViewport, const ImGuiWindowFlags& windowFlags, const float& halfWidth, const float& topSectionHeight, AppState& appState){ 116 | ImGui::SetNextWindowPos(ImVec2(mainViewport->WorkPos.x, mainViewport->WorkPos.y + topSectionHeight)); 117 | ImGui::SetNextWindowSize(ImVec2(halfWidth, mainViewport->WorkSize.y - topSectionHeight)); 118 | ImGui::Begin("Control", nullptr, windowFlags | ImGuiWindowFlags_NoScrollbar); 119 | 120 | if(ImGui::InputTextWithHint("Master Key","", appState.masterKeyBuffer, IM_ARRAYSIZE(appState.masterKeyBuffer), ImGuiInputTextFlags_Password)){ 121 | appState.masterKey = std::string(appState.masterKeyBuffer); 122 | } 123 | if(ImGui::InputTextWithHint("Password ", "