diff --git a/Encoding and Decoding an Image/Messi.png b/Encoding and Decoding an Image/Messi.png new file mode 100644 index 0000000..d4dfeb3 Binary files /dev/null and b/Encoding and Decoding an Image/Messi.png differ diff --git a/Encoding and Decoding an Image/README.md b/Encoding and Decoding an Image/README.md new file mode 100644 index 0000000..fa9b65b --- /dev/null +++ b/Encoding and Decoding an Image/README.md @@ -0,0 +1,82 @@ +# **Image Text Encoder/Decoder** + +This Python script provides a simple way to encode text messages into image files and decode them back. +It uses the OpenCV library to work with images and allows users to hide text within an image's pixel data. + +## **How To Use:** + +Run the script and choose the mode: encode or decode. +If encoding, provide the path to the image file and the text message you want to hide. +If decoding, provide the path to the encoded image. +The script will perform the selected operation and display the result. + +## **Algorithm Justification:** + +The programme encodes text messages with XOR and stores them in images using Least Significant Bit (LSB) steganography. XOR encryption was picked because it is simple and removable, which is appropriate for the helpful and lightweight nature of this tool. LSB steganography is used because it can conceal information inside the image's noise level, rendering changes unnoticeable to the human eye and keeping the image's original look while securely encoding the data. + + +## **How The Tool Works:** + +### ImageLoader: + +Loads the image and checks that it is a valid PNG file. + +### Encryptor: + +Encrypts or decrypts the given text using the XOR method. It's a static class that may be readily customised for various encryption methods. + +### TextEncoder: + +The encrypted text is encoded into the picture using LSB steganography. It examines the text length to ensure that it is within the image's capacity. + +### TextDecoder: + +Extracts and decrypts text from images by reversing the encoding process. + +### ImageEncoder: + +Controls the loading, encoding, and saving of the picture with embedded text. + + +## **Test Cases:** +The unittest framework is used to guarantee that the Image Text Encoder/Decoder tool is both robust and reliable. Below is an overview of the test cases used to validate each component of the system: + +### TestImageLoader: +**Valid Image File** +Confirms that the ImageLoader correctly identifies valid PNG files. A temporary image is created and verified to be recognized as a valid image file. + +### Load Image: +Ensures that an image is correctly loaded from the filesystem, checking both the presence of the loaded image and its dimensions to match the expected values. + +### TestTextEncoder: +**Encode Text** +Verifies that text can be successfully encoded into an image. This test uses a blank image to encode a simple message, ensuring that the encoding process results in a modified image. + +### TestImageEncoder: +**Save Encoded Image** +Tests the ability of the ImageEncoder to save an encoded image to the filesystem. It confirms that the saved file is correctly named to indicate its encoded status and then cleans up by removing the file. + +### TestEncryptor: +**XOR Encrypt/Decrypt** +Validates the Encryptor's ability to encrypt a message with a key and then decrypt it back to its original form. It checks that the decrypted message matches the original and that the encrypted form is different. + +### TestTextDecoder: +**Decode Text** +Assesses the TextDecoder's capability to accurately extract and decrypt text from an encoded image. The test ensures that the decoded text matches the original message encoded in the image. + +### TestErrorHandling: +**Invalid Image File** +Tests the system's response to an attempt at loading a non-existent image file, expecting a ValueError to be raised. +### Text Too Long: +Verifies that encoding text which exceeds the capacity of the image results in a ValueError, preventing the operation. + + +## Notes +This tool displays basic steganography and encryption techniques. It is intended for educational uses and is not suitable for secure communications until modified to use higher-level encryption methods. + +## Requirements: +1. Python 3.x +2. OpenCV (cv2) library + +## Supported Files +The script currently supports PNG image files only due to their lossless nature, which is crucial for accurately encoding and decoding text within an image. diff --git a/Encoding and Decoding an Image/main.py b/Encoding and Decoding an Image/main.py new file mode 100644 index 0000000..ff8116a --- /dev/null +++ b/Encoding and Decoding an Image/main.py @@ -0,0 +1,164 @@ +import cv2 +import os +# OpenCV is needed to work with images and os to work with files and directories +class ImageLoader: + """Loads and checks image files.""" + def __init__(self, image_filename): + """ + Initializes with an image filename. + + :param image_filename: Name of the image file. + """ + self.image_filename = image_filename + self.image = None + + def is_image_file_valid(self): + """ + Checks if the image file is a valid PNG. + + :return: True if valid, False otherwise. + """ + # This method checks if the file name provided is actually an image file that the user can work with. + valid_extensions = ['.png'] + return os.path.isfile(self.image_filename) and os.path.splitext(self.image_filename)[1] in valid_extensions + + def load_image_file(self): + """ + Loads the image from the file. + + :return: Loaded image. + :raises ValueError: If file is not a valid image. + """ + if self.is_image_file_valid(): + self.image = cv2.imread(self.image_filename) + else: + raise ValueError("Invalid image file.") + return self.image + +class Encryptor: + """Handles text encryption and decryption.""" + @staticmethod + def xor_encrypt_decrypt(input_text, key=123): # (OpenAI, 2022) + """ + Encrypts/decrypts text using XOR. + + :param input_text: Text to encrypt/decrypt. + :param key: Encryption key. + :return: Encrypted/decrypted text. + """ + # This method uses XOR encryption/decryption on the input text. + output = ''.join( + # It changes each letter into a number, does the XOR with the key and then changes it back to a letter. + chr(ord(char) ^ key) for char in input_text + ) + return output # Returns the encrypted or decrypted text. + # (OpenAI, 2022) + +class TextIntoImageEncoder: + """Encodes text into an image.""" + @staticmethod + def encode_text_into_image(image, text): + """ + Encodes text into an image. + + :param image: Image to encode text into. + :param text: Text to encode. + :return: Image with encoded text. + :raises ValueError: If image not loaded or text too long. + """ + # This method takes some text and hides it inside the image. + if image is None: + raise ValueError("Image not loaded.") + + # (OpenAI, 2022) + encrypted_text = Encryptor.xor_encrypt_decrypt(text) + + binary_text = ''.join([format(ord(char), '08b') for char in encrypted_text]) + '00000000' + + + if len(binary_text) > image.shape[0] * image.shape[1] * 3: + raise ValueError("Text too long to encode in the image.") + + data_index = 0 + for i in range(image.shape[0]): + for j in range(image.shape[1]): + pixel = list(image[i, j]) + for k in range(3): + if data_index < len(binary_text): + pixel[k] = (pixel[k] & 254) | int(binary_text[data_index]) + data_index += 1 + image[i, j] = tuple(pixel) + return image + # (OpenAI, 2022) + +class TextFromImageDecoder: + """Decodes text from an image.""" + @staticmethod + def decode_text_from_image(image): + """ + Extracts text from an image. + + :param image: Image to extract text from. + :return: Decoded text. + :raises ValueError: If image not loaded. + """ + # (OpenAI, 2022) + if image is None: + raise ValueError("Image not loaded.") + binary_text = '' + for i in range(image.shape[0]): + for j in range(image.shape[1]): + pixel = image[i, j] + for k in range(3): + binary_text += str(pixel[k] & 1) + all_bytes = [binary_text[i: i+8] for i in range(0, len(binary_text), 8)] + encrypted_text = '' + for byte in all_bytes: + if byte == '00000000': + break + encrypted_text += chr(int(byte, 2)) + + decrypted_text = Encryptor.xor_encrypt_decrypt(encrypted_text) # Uses the Encryptor class to decrypt the text. + return decrypted_text # Return the decrypted text so it can be displayed. + # (OpenAI, 2022) + +class ImageTextEncoder: + """Manages encoding of text into images.""" + def __init__(self, image_filename): + """ + Initializes with the image filename. + + :param image_filename: Image file to process. + """ + self.loader = ImageLoader(image_filename) + def save_encoded_image(self, image): + """ + Saves the encoded image to a file. + + :param image: Image with encoded text. + :return: Filename of the saved image. + """ + encoded_image_filename = os.path.splitext(self.loader.image_filename)[0] + "_encoded" + os.path.splitext(self.loader.image_filename)[1] + cv2.imwrite(encoded_image_filename, image) + return encoded_image_filename + +# This is the menu the user is presented with when first running the Tool. +if __name__ == "__main__": + mode = input("Choose mode (encode/decode): ") + image_filename = input("Image Filename (with extension): ") + encoder = ImageTextEncoder(image_filename) + + try: + image = encoder.loader.load_image_file() + if mode.lower() == "encode": + text = input("Encoded Text: ") + encoded_image = TextIntoImageEncoder.encode_text_into_image(image, text) + encoded_image_filename = encoder.save_encoded_image(encoded_image) + print(f"Text successfully encoded. Encoded image saved as: {encoded_image_filename}") + elif mode.lower() == "decode": + extracted_text = TextFromImageDecoder.decode_text_from_image(image) + print(f"Extracted Text: {extracted_text}") + else: + print("Invalid mode. Please choose 'encode' or 'decode'.") + except Exception as e: + print(f"Error: {e}") diff --git a/Encoding and Decoding an Image/requirements.txt b/Encoding and Decoding an Image/requirements.txt new file mode 100644 index 0000000..1d69c3e --- /dev/null +++ b/Encoding and Decoding an Image/requirements.txt @@ -0,0 +1,3 @@ +Requirements: +opencv-python~=4.9.0.80 +numpy~=1.24.2 \ No newline at end of file diff --git a/Encoding and Decoding an Image/testcase.py b/Encoding and Decoding an Image/testcase.py new file mode 100644 index 0000000..96b7d67 --- /dev/null +++ b/Encoding and Decoding an Image/testcase.py @@ -0,0 +1,74 @@ +import unittest +import os +import cv2 +import numpy as np +from main import ImageLoader, TextIntoImageEncoder, TextFromImageDecoder, ImageTextEncoder, Encryptor + +class TestImageLoader(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.test_image_path = "test_image.png" + cls.test_image = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8) + cv2.imwrite(cls.test_image_path, cls.test_image) + + @classmethod + def tearDownClass(cls): + os.remove(cls.test_image_path) + + def test_is_valid_image_file(self): + loader = ImageLoader(self.test_image_path) + self.assertTrue(loader.is_image_file_valid()) + + def test_load_image_file(self): + loader = ImageLoader(self.test_image_path) + image = loader.load_image_file() + self.assertIsNotNone(image) + self.assertEqual(image.shape, (100, 100, 3)) + +class TestTextIntoImageEncoder(unittest.TestCase): + def test_encode_text_into_image(self): + image = np.zeros((100, 100, 3), dtype=np.uint8) + encoded_image = TextIntoImageEncoder.encode_text_into_image(image, "Test") + self.assertIsNotNone(encoded_image) + +class TestImageTextEncoder(unittest.TestCase): + def test_save_encoded_image(self): + encoder = ImageTextEncoder("test_image.png") + image = np.zeros((100, 100, 3), dtype=np.uint8) + encoded_image_filename = encoder.save_encoded_image(image) + self.assertTrue(encoded_image_filename.endswith("_encoded.png")) + os.remove(encoded_image_filename) + +class TestEncryptor(unittest.TestCase): + def test_xor_encrypt_decrypt(self): + original_text = "Hello" + key = 123 + encrypted = Encryptor.xor_encrypt_decrypt(original_text, key) + decrypted = Encryptor.xor_encrypt_decrypt(encrypted, key) + self.assertEqual(original_text, decrypted) + self.assertNotEqual(original_text, encrypted) + +class TestTextFromImageDecoder(unittest.TestCase): + def setUp(self): + self.image = np.zeros((100, 100, 3), dtype=np.uint8) + self.test_text = "Hello" + self.encoded_image = TextIntoImageEncoder.encode_text_into_image(self.image, self.test_text) + + def test_decode_text_from_image(self): + decoded_text = TextFromImageDecoder.decode_text_from_image(self.encoded_image) + self.assertEqual(decoded_text, self.test_text) + +class TestErrorHandling(unittest.TestCase): + def test_invalid_image_file(self): + loader = ImageLoader("nonexistent_file.png") + with self.assertRaises(ValueError): + loader.load_image_file() + + def test_text_too_long(self): + image = np.zeros((10, 10, 3), dtype=np.uint8) + long_text = "a" * 1000 + with self.assertRaises(ValueError): + TextIntoImageEncoder.encode_text_into_image(image, long_text) + +if __name__ == "__main__": + unittest.main()