diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dd92408 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog Template + +## Release Notes - Version X.X.X + +### Summary + +Brief overview of the key changes and improvements in this release. + +--- + +### Features + +- **Feature 1:** Description of feature 1. +- ... +- **Feature N:** Description of feature N. + +### Enhancements + +- **Enhancement 1:** Description of enhancement 1. +- .. +- **Enhancement N:** Description of enhancement N. + +### Bug Fixes + +- **Bug Fix 1:** Description of bug fix 1. +- ... +- **Bug Fix N:** Description of bug fix N. + +### Dependencies + +List any new dependencies or updates to existing dependencies. + +### Breaking Changes + +List any changes that could potentially break existing functionality. It's important to highlight these so that users can take proper action. + +### Usage + +Update any usage instructions or provide information on new features. + +### Known Issues + +List any known issues that were not addressed in this release. + +### Contributors + +Acknowledge and credit contributors who contributed to this release. + +### Changelog + +Provide a detailed changelog with commit references or links. + +### Support + +Provide information on how users can get support or report issues. + +--- + +Remove the sections that are not applicable to your changes and the [first line](#changelog-template) \ No newline at end of file diff --git a/src/VanGonography.py b/src/VanGonography.py index ac96a4b..3063766 100644 --- a/src/VanGonography.py +++ b/src/VanGonography.py @@ -2,6 +2,7 @@ import os import io import sys import argparse +import logging import numpy as np @@ -14,6 +15,8 @@ from utils import * SINGLE_RGB_BIT_SIZE = 8 # Each RGB value is composed of 3 colors, each color is composed of 8 bits SINGLE_RGB_PIXEL_BIT_SIZE = SINGLE_RGB_BIT_SIZE * 3 # Each pixel is composed of 3 RGB values, each RGB value is composed of 3 colors, each color is composed of 8 bits +__version__ = "1.1.1-rc" + def add_header(image: str, extension: str, data_length: int, output_directory: str = None) -> None: """ Adds a header to the cover image before hiding data. @@ -381,165 +384,277 @@ def differentiate_image(source, cover, output_directory: str = None) -> None: def main(): - init(autoreset=True) + os.system('cls' if os.name == 'nt' else 'clear') # Clear the terminal - os.system('cls' if os.name == 'nt' else 'clear') - print( - """ - ,-.-. ,---. .-._ _,---. _,.---._ .-._ _,.---._ _,---. ,---. _ __ ,--.-,,-,--, ,-.-. ,-----.--. _.---.,_ _.---.,_ - ,--.-./=/ ,/.--.' \ /==/ \ .-._ _.='.'-, \ ,-.' , - `. /==/ \ .-._ ,-.' , - `. _.='.'-, \ .-.,.---. .--.' \ .-`.' ,`./==/ /|=| |,--.-. .-,--. ,--.-./=/ ,//` ` - /==/ .' - , `.-, .' - , `.-, -/==/, ||=| -|\==\-/\ \ |==|, \/ /, /==.'- / /==/_, , - \|==|, \/ /, /==/_, , - \ /==.'- / /==/ ` \ \==\-/\ \ /==/, - \==|_ ||=|, /==/- / /=/_ / /==/, ||=| -|`-'-. -|==| / - , ,_\==\ / - , ,_\==\ -\==\, \ / ,|/==/-|_\ | |==|- \| /==/ - .-' |==| .=. |==|- \| |==| .=. /==/ - .-' |==|-, .=., |/==/-|_\ | |==| _ .=. |==| ,|/=| _\==\, \/=/. / \==\, \ / ,| | `|==| | .=. |==| | .=. |==| - \==\ - ' - /\==\, - \ |==| , | -|==|_ /_,-.|==|_ : ;=: - |==| , | -|==|_ : ;=: - |==|_ /_,-.|==| '=' /\==\, - \ |==| , '=',|==|- `-' _ |\==\ \/ -/ \==\ - ' - / | -|==| | - :=; : _|==| | - :=; : _|==| - \==\ , | /==/ - ,| |==| - _ |==| , \_.' )==| , '=' |==| - _ |==| , '=' |==| , \_.' )==|- , .' /==/ - ,| |==|- '..'|==| _ | |==| ,_/ \==\ , | | `|==| | `=` , |==| | `=` , |==| - |==| - ,//==/- /\ - \|==| /\ , \==\- , ( \==\ - ,_ /|==| /\ , |\==\ - ,_ /\==\- , (|==|_ . ,'./==/- /\ - \|==|, | |==| .-. ,\ \==\-, / |==| - ,/ .-','|==| .=.\ _, - /==/.=.\ _, - /==/ - \==\ _ / \==\ _.\=\.-'/==/, | |- |/==/ _ , / '.='. - .' /==/, | |- | '.='. - .' /==/ _ , //==/ /\ , )==\ _.\=\.-'/==/ - | /==/, //=/ | /==/._/ \==\ _ / / \==\:=; :`. - .`=.`:=; :`. - .`=.` - `--`--' `--` `--`./ `--``--`------' `--`--'' `--`./ `--` `--`--'' `--`------' `--`-`--`--' `--` `--`---' `--`-' `-`--` `--`-` `--`--' `-----`---``=` ``--'--' `=` ``--'--' - """ - ) - print() - print(Fore.YELLOW + "Version 1.0.0") - print("Welcome to VanGonography! Please select an option:") - print() - print(Fore.LIGHTRED_EX + "[1] " + Fore.WHITE + "Hide a file in an image") - print(Fore.LIGHTRED_EX + "[2] " + Fore.WHITE + "Reveal a hidden file in an image") - print(Fore.LIGHTRED_EX + "[3] " + Fore.WHITE + "Show the difference between two images") - print(Fore.LIGHTRED_EX + "[4] " + Fore.WHITE + "Exit") - print() + # Argument parser + parser = argparse.ArgumentParser(description="Van Gonography is a steganography tool that hides files in images.") - while True: - choice = input("Enter your choice: ") + # Optional arguments + optional_group = parser.add_argument_group('Optional arguments') + optional_group.add_argument("-ood", dest="ood", action="store_true", default=False, help="Open file after decoding from image (default: False)") + optional_group.add_argument("-l", "--log", dest="log", type=str, default=False, metavar="LOG_FILE", help="Log file for the program (default: False)") + optional_group.add_argument("-cli", dest="cli", action="store_true", default=False, help="Run the program in CLI mode, this means there's not gonna be any menu (default: False)") + optional_group.add_argument("-o", "--output", dest="output", type=str, metavar="OUTPUT_DIR", help="Output directory for the modified image or revealed file") + optional_group.add_argument("-v", "--version", action="version", version=f"VanGonography v{__version__}", help="Show the version number and exit") + + # Positional arguments group (only used in CLI mode) + positional_group = parser.add_argument_group('Positional arguments (only used in CLI mode)') + positional_group.add_argument("-s", "--show", dest="show", action="store_true", default=False, help="Show the difference between two images (default: False)") + positional_group.add_argument("-e", "--encode", dest="encode", action="store_true", default=False, help="Encode the file in the image (default: False)") + positional_group.add_argument("-d", "--decode", dest="decode", action="store_true", default=False, help="Decode the file hidden in the image (default: False)") + positional_group.add_argument("-c", "--cover", dest="cover", type=str, metavar="COVER_IMAGE", help="Image to be used for hiding or revealing, positional only when using decoding, encoding or differentiate") + positional_group.add_argument("-f", "--file", dest="file", type=str, metavar="HIDDEN_FILE", help="File to be hidden") + + args = parser.parse_args() + + # Checking for CLI mode + if args.cli: + # Logging setup + if args.log: + # If the user wants to log, we will create the log.log file + if args.log == True: + args.log = "log.log" + + logging.basicConfig( + filename=args.log, + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S" + ) + + logging.info("Logging started") + logging.info(f"Arguments: {args}") - if choice == "1": - try: - # Get the file to hide - root = Tk() - root.withdraw() - file = filedialog.askopenfilename(title="Select file to hide") - - # Check if the user canceled the file selection - if not file: - print("File selection canceled.") - - # Get the cover image - root = Tk() - root.withdraw() - image = filedialog.askopenfilename(title="Select cover image") - - # Check if the user canceled the image selection - if not image: - print("Image selection canceled.") - - # Check if the selected file is an image - if not is_image_file(image): - print("Selected cover image is not a valid image file.") - - # Get the output directory - root = Tk() - root.withdraw() - output_directory = filedialog.askdirectory(title="Select output directory") - - # Check if the user canceled the output directory selection - if not output_directory: - print("Output directory selection canceled.") - - # Check if the selected output directory is valid - if not os.path.isdir(output_directory): - print("Selected output directory is not a valid directory.") - - # Hide the file in the cover image - encode_image(file, image, output_directory) - - except Exception as e: - print(f"An error occurred: {e}") - - elif choice == "2": - try: - # Get the image with the hidden file - root = Tk() - root.withdraw() - image = filedialog.askopenfilename(title="Select image with hidden file") - - # Check if the user canceled the image selection - if not image: - print("Image selection canceled.") - - # Check if the selected file is an image - if not is_image_file(image): - print("Selected image is not a valid image file.") - - # Get the output directory - root = Tk() - root.withdraw() - output_directory = filedialog.askdirectory(title="Select output directory") - - # Check if the user canceled the output directory selection - if not output_directory: - print("Output directory selection canceled.") - - # Check if the selected output directory is valid - if not os.path.isdir(output_directory): - print("Selected output directory is not a valid directory.") - - # Reveal the hidden file - decode_image(image, output_directory) - - except Exception as e: - print(f"An error occurred: {e}") - - elif choice == "3": - try: - # Get the source image - root = Tk() - root.withdraw() - source = filedialog.askopenfilename(title="Select source image") - - # Check if the user canceled the image selection - if not source: - print("Source image selection canceled.") - - # Check if the selected file is an image - if not is_image_file(source): - print("Selected source image is not a valid image file.") - - # Get the cover image - root = Tk() - root.withdraw() - cover = filedialog.askopenfilename(title="Select cover image") - - # Check if the user canceled the image selection - if not cover: - print("Cover image selection canceled.") - - # Check if the selected file is an image - if not is_image_file(cover): - print("Selected cover image is not a valid image file.") - - # Get the output directory - root = Tk() - root.withdraw() - output_directory = filedialog.askdirectory(title="Select output directory") - - # Check if the user canceled the output directory selection - if not output_directory: - print("Output directory selection canceled.") - - # Check if the selected output directory is valid - if not os.path.isdir(output_directory): - print("Selected output directory is not a valid directory.") - - # Show the difference between the source and cover images - differentiate_image(source, cover, output_directory) - - except Exception as e: - print(f"An error occurred: {e}") - - elif choice == "4": - print("Exiting...") + # CLI mode starts here + if args.cover: # Checking if a cover image is given (essential for both decoding and encoding) + # Is the user choosing to encode or decode? + if args.encode: # Encode + # Checking if a file to hide is given + if not args.file: + print("You must insert the file to hide") + logging.error("No file to hide was given") + return + try: + logging.info("Encoding started") # Logging the start + logging.info(f"Encoding {args.file} in {args.cover}") # Logging the file and cover image + + encode_image(args.file, args.cover, args.output) + + print(f"File hidden successfully in {args.cover}.") + logging.info(f"File hidden successfully in {args.cover}.") # Logging the success message, this is also useful for checking the time it took to hide the file + except Exception as e: + print(f"An error occurred: {e}") + logging.error(f"An error occurred: {e}") + + # Checking for decode + elif args.decode: + if args.file: + print("You can't insert the file you must only insert the image with the hidden file and optionally the output directory.") + logging.error("A file to hide was given, but you must only insert the image with the hidden file and optionally the output directory.") + + try: + logging.info("Decoding started") # Logging the start + logging.info(f"Decoding {args.cover}") # Logging the cover image + + decode_image(args.cover, args.output, args.ood) + + print(f"File revealed successfully from {args.cover}.") + logging.info(f"File revealed successfully from {args.cover}.") # Same as above + except Exception as e: + print(f"An error occurred: {e}") + logging.error(f"An error occurred: {e}") + + # Checking for differentiating images + elif args.show: + if args.file: + print("You can't insert the file to hide you must only insert the source and cover images and optionally the output directory.") + logging.error("A file to hide was given, but you must only insert the source and cover images and optionally the output directory.") + try: + logging.info("Differentiating started") # Logging the start + logging.info(f"Differentiating {args.cover} and {args.file}") # Logging the source and cover images + + differentiate_image(args.cover, args.output) + + print(f"Difference image saved successfully as Difference.png.") + logging.info(f"Difference image saved successfully as Difference.png.") # Again, same as above + except Exception as e: + print(f"An error occurred: {e}") + logging.error(f"An error occurred: {e}") + + # Something's wrong + else: + print("Invalid arguments.") + logging.error("Invalid arguments, you must choose a mode to run the program in between encode, decode and show.") + else: + print("You must insert all the required arguments, no cover image was given, use -h for help.") + logging.error("No cover image was given, for checking all the arguments use -h in CLI mode.") + + # Using UI mode if no arguments are given + else: + + # Checking if any arguments are given + if args.show or args.encode or args.decode or args.output or args.cover or args.file: + print("You can't use arguments in UI mode.") return - else: - print("Invalid choice.") + # UI mode + init(autoreset=True) + print( + """ + ,-.-. ,---. .-._ _,---. _,.---._ .-._ _,.---._ _,---. ,---. _ __ ,--.-,,-,--, ,-.-. ,-----.--. _.---.,_ _.---.,_ + ,--.-./=/ ,/.--.' \ /==/ \ .-._ _.='.'-, \ ,-.' , - `. /==/ \ .-._ ,-.' , - `. _.='.'-, \ .-.,.---. .--.' \ .-`.' ,`./==/ /|=| |,--.-. .-,--. ,--.-./=/ ,//` ` - /==/ .' - , `.-, .' - , `.-, + /==/, ||=| -|\==\-/\ \ |==|, \/ /, /==.'- / /==/_, , - \|==|, \/ /, /==/_, , - \ /==.'- / /==/ ` \ \==\-/\ \ /==/, - \==|_ ||=|, /==/- / /=/_ / /==/, ||=| -|`-'-. -|==| / - , ,_\==\ / - , ,_\==\ + \==\, \ / ,|/==/-|_\ | |==|- \| /==/ - .-' |==| .=. |==|- \| |==| .=. /==/ - .-' |==|-, .=., |/==/-|_\ | |==| _ .=. |==| ,|/=| _\==\, \/=/. / \==\, \ / ,| | `|==| | .=. |==| | .=. |==| + \==\ - ' - /\==\, - \ |==| , | -|==|_ /_,-.|==|_ : ;=: - |==| , | -|==|_ : ;=: - |==|_ /_,-.|==| '=' /\==\, - \ |==| , '=',|==|- `-' _ |\==\ \/ -/ \==\ - ' - / | -|==| | - :=; : _|==| | - :=; : _|==| + \==\ , | /==/ - ,| |==| - _ |==| , \_.' )==| , '=' |==| - _ |==| , '=' |==| , \_.' )==|- , .' /==/ - ,| |==|- '..'|==| _ | |==| ,_/ \==\ , | | `|==| | `=` , |==| | `=` , |==| + |==| - ,//==/- /\ - \|==| /\ , \==\- , ( \==\ - ,_ /|==| /\ , |\==\ - ,_ /\==\- , (|==|_ . ,'./==/- /\ - \|==|, | |==| .-. ,\ \==\-, / |==| - ,/ .-','|==| .=.\ _, - /==/.=.\ _, - /==/ + \==\ _ / \==\ _.\=\.-'/==/, | |- |/==/ _ , / '.='. - .' /==/, | |- | '.='. - .' /==/ _ , //==/ /\ , )==\ _.\=\.-'/==/ - | /==/, //=/ | /==/._/ \==\ _ / / \==\:=; :`. - .`=.`:=; :`. - .`=.` + `--`--' `--` `--`./ `--``--`------' `--`--'' `--`./ `--` `--`--'' `--`------' `--`-`--`--' `--` `--`---' `--`-' `-`--` `--`-` `--`--' `-----`---``=` ``--'--' `=` ``--'--' + """ + ) + print() + print(Fore.YELLOW + "Version 1.0.0") + print("Welcome to VanGonography! Please select an option:") + print() + print(Fore.LIGHTRED_EX + "[1] " + Fore.WHITE + "Hide a file in an image") + print(Fore.LIGHTRED_EX + "[2] " + Fore.WHITE + "Reveal a hidden file in an image") + print(Fore.LIGHTRED_EX + "[3] " + Fore.WHITE + "Show the difference between two images") + print(Fore.LIGHTRED_EX + "[4] " + Fore.WHITE + "Exit") + print() + + while True: + choice = input("Enter your choice: ") + + if choice == "1": + try: + # Get the file to hide + root = Tk() + root.withdraw() + file = filedialog.askopenfilename(title="Select file to hide") + + # Check if the user canceled the file selection + if not file: + print("File selection canceled.") + + # Get the cover image + root = Tk() + root.withdraw() + image = filedialog.askopenfilename(title="Select cover image") + + # Check if the user canceled the image selection + if not image: + print("Image selection canceled.") + + # Check if the selected file is an image + if not is_image_file(image): + print("Selected cover image is not a valid image file.") + + # Get the output directory + root = Tk() + root.withdraw() + output_directory = filedialog.askdirectory(title="Select output directory") + + # Check if the user canceled the output directory selection + if not output_directory: + print("Output directory selection canceled.") + + # Check if the selected output directory is valid + if not os.path.isdir(output_directory): + print("Selected output directory is not a valid directory.") + + # Hide the file in the cover image + encode_image(file, image, output_directory) + + except Exception as e: + print(f"An error occurred: {e}") + + elif choice == "2": + try: + # Get the image with the hidden file + root = Tk() + root.withdraw() + image = filedialog.askopenfilename(title="Select image with hidden file") + + # Check if the user canceled the image selection + if not image: + print("Image selection canceled.") + + # Check if the selected file is an image + if not is_image_file(image): + print("Selected image is not a valid image file.") + + # Get the output directory + root = Tk() + root.withdraw() + output_directory = filedialog.askdirectory(title="Select output directory") + + # Check if the user canceled the output directory selection + if not output_directory: + print("Output directory selection canceled.") + + # Check if the selected output directory is valid + if not os.path.isdir(output_directory): + print("Selected output directory is not a valid directory.") + + # Reveal the hidden file + decode_image(image, output_directory) + + except Exception as e: + print(f"An error occurred: {e}") + + elif choice == "3": + try: + # Get the source image + root = Tk() + root.withdraw() + source = filedialog.askopenfilename(title="Select source image") + + # Check if the user canceled the image selection + if not source: + print("Source image selection canceled.") + + # Check if the selected file is an image + if not is_image_file(source): + print("Selected source image is not a valid image file.") + + # Get the cover image + root = Tk() + root.withdraw() + cover = filedialog.askopenfilename(title="Select cover image") + + # Check if the user canceled the image selection + if not cover: + print("Cover image selection canceled.") + + # Check if the selected file is an image + if not is_image_file(cover): + print("Selected cover image is not a valid image file.") + + # Get the output directory + root = Tk() + root.withdraw() + output_directory = filedialog.askdirectory(title="Select output directory") + + # Check if the user canceled the output directory selection + if not output_directory: + print("Output directory selection canceled.") + + # Check if the selected output directory is valid + if not os.path.isdir(output_directory): + print("Selected output directory is not a valid directory.") + + # Show the difference between the source and cover images + differentiate_image(source, cover, output_directory) + + except Exception as e: + print(f"An error occurred: {e}") + + elif choice == "4": + print("Exiting...") + return + + else: + print("Invalid choice.") if __name__ == '__main__': main() \ No newline at end of file