Skip to content

Commit

Permalink
Steganography Tool
Browse files Browse the repository at this point in the history
  • Loading branch information
K11E3R committed Jul 21, 2024
0 parents commit 18be3c7
Show file tree
Hide file tree
Showing 12 changed files with 355 additions and 0 deletions.
207 changes: 207 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Steganography Tool

## Overview

This tool provides functionality to hide and extract images within other images using steganographic techniques. It uses Least Significant Bit (LSB) modification to embed and extract hidden images while maintaining the visual integrity of the carrier image.

## Features

- **Merge Images**: Embed one image into another.
- **Extract Hidden Image**: Retrieve a hidden image from a merged image.

## Installation

To get started with the Steganography Tool, you'll need Python and some libraries. Follow these steps:

1. **Clone the Repository**:

```bash
git clone https://github.com/K11E3R/steganography-tool.git
cd steganography-tool
```

2. **Create a Virtual Environment (optional but recommended)**:

```bash
python -m venv venv
source venv/bin/activate # On Windows use: venv\Scripts\activate
```

3. **Install Dependencies**:

Use the provided `requirements.txt` to install the necessary Python libraries:

```bash
pip install -r requirements.txt
```

## Usage

### Command Line Interface

The tool is designed to be used from the command line. Below are instructions for each command.

#### Merging Images

To merge one image into another, use:

```bash
python steganography.py merge --image1 img/base_image.png --image2 img/image_to_hide.png --output path/to/output_image.png
```

- `--image1`: Path to the base image where the second image will be hidden.
- `--image2`: Path to the image to be hidden.
- `--output`: Path to save the merged image.

#### Extracting Hidden Image

To extract the hidden image from a merged image, use:

```bash
python steganography.py unmerge --image path/to/merged_image.png --output path/to/extracted_image.png
```

- `--image`: Path to the merged image from which the hidden image will be extracted.
- `--output`: Path to save the extracted image.

## Code Explanation

### Loading Spinner

A loading spinner is used to indicate progress during long operations.

- **`loading_spinner()`**: Runs a spinner in a loop to show activity.
- **`show_loading_spinner()`**: Starts the spinner in a separate thread.
- **`stop_loading_spinner(spinner_thread)`**: Stops the spinner and prints "Done!".

### Steganography Class

This class handles the embedding and extraction of images.

- **`_merge_rgb(self, rgb1, rgb2)`**: Combines two RGB color values by merging the lower 4 bits of `rgb2` with the higher 4 bits of `rgb1`.

```python
def _merge_rgb(self, rgb1, rgb2):
return tuple((c1 & 0xF0) | (c2 >> 4) for c1, c2 in zip(rgb1, rgb2))
```

- **`_unmerge_rgb(self, rgb)`**: Extracts the hidden color values from a merged RGB tuple.

```python
def _unmerge_rgb(self, rgb):
return tuple(((c & 0x0F) << 4) | ((c & 0xF0) >> 4) for c in rgb)
```

- **`merge(self, image1, image2, output)`**: Embeds `image2` into `image1` and saves the result.

```python
def merge(self, image1, image2, output):
if image2.size[0] > image1.size[0] or image2.size[1] > image1.size[1]:
raise ValueError('Image 2 must be smaller than or equal to Image 1 in both dimensions!')

if image1.mode != 'RGB' or image2.mode != 'RGB':
raise ValueError('Both images must be in RGB mode!')

print("Merging images...")
spinner_thread = show_loading_spinner()
try:
img1_array = np.array(image1)
img2_array = np.array(image2)
height, width = img2_array.shape[:2]
merged_array = np.copy(img1_array)

merged_array[:height, :width] = np.array([self._merge_rgb(tuple(c1), tuple(c2))
for c1, c2 in zip(img1_array[:height, :width], img2_array)])

new_image = Image.fromarray(merged_array, mode='RGB')
new_image.save(output)
finally:
stop_loading_spinner(spinner_thread)
print(f"Image merged and saved as '{output}'")
```

- **`unmerge(self, image, output)`**: Extracts the hidden image from `image` and saves it.

```python
def unmerge(self, image, output):
if image.mode != 'RGB':
raise ValueError('The image must be in RGB mode!')

print("Extracting hidden image...")
spinner_thread = show_loading_spinner()
try:
img_array = np.array(image)
unmerged_array = np.array([self._unmerge_rgb(tuple(c))
for c in img_array])

new_image = Image.fromarray(unmerged_array, mode='RGB')
new_image.save(output)
finally:
stop_loading_spinner(spinner_thread)
print(f"Hidden image extracted and saved as '{output}'")
```

### Main Function

The `main()` function handles command-line arguments and calls the appropriate steganography methods.

```python
def main():
parser = argparse.ArgumentParser(description='Steganography Tool')
subparser = parser.add_subparsers(dest='command')

merge = subparser.add_parser('merge', help='Merge one image into another')
merge.add_argument('--image1', required=True, help='Path to the base image')
merge.add_argument('--image2', required=True, help='Path to the image to be hidden')
merge.add_argument('--output', required=True, help='Path to save the merged image')

unmerge = subparser.add_parser('unmerge', help='Extract hidden image from a merged image')
unmerge.add_argument('--image', required=True, help='Path to the merged image')
unmerge.add_argument('--output', required=True, help='Path to save the extracted image')

args = parser.parse_args()

try:
stego = Steganography()
if args.command == 'merge':
with Image.open(args.image1) as image1, Image.open(args.image2) as image2:
stego.merge(image1, image2, args.output)
elif args.command == 'unmerge':
with Image.open(args.image) as image:
stego.unmerge(image, args.output)
else:
parser.print_help()
except FileNotFoundError as e:
print(f"File not found: {e.filename}")
except ValueError as e:
print(f"Value error: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
```

## Technical Details

### Steganographic Technique

- **Merging Logic**: The tool hides an image (`image2`) within another image (`image1`) by combining their RGB values. Specifically, the lower 4 bits of `image2` are merged with the higher 4 bits of `image1`.

- **Unmerging Logic**: Extracts the hidden image by reversing the merging process, recovering the lower 4 bits from the combined RGB values.

### Capacity and Limits

- **Capacity**: The amount of data you can hide depends on the carrier image’s resolution and bit depth. For a 24-bit RGB image, each pixel can theoretically store up to 3 bits of hidden data per color channel.

- **Quality Considerations**: Hiding more data can reduce the quality of the carrier image. The hidden image's size and the amount of data being hidden affect the visual integrity of the carrier image.

## Contributing

If you would like to contribute to this project, please follow these steps:

1. **Fork the Repository**: Create a copy of the repository under your GitHub account.
2. **Create a Feature Branch**: Create a new branch for your feature or bug fix.
3. **Make Your Changes**: Implement your changes and test thoroughly.
4. **Submit a Pull Request**: Push your changes to your fork and submit a pull request for review.

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
Binary file added advanced_test/base_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added advanced_test/hidden_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/base_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/hidden_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions img_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from PIL import Image, ImageDraw

def generate_image(width, height, color, file_path):
"""Solid color image Generator """
img = Image.new('RGB', (width, height), color)
img.save(file_path)

def generate_test_images():
"""Image Generator """
base_image_width, base_image_height = 200, 200
hidden_image_width, hidden_image_height = 50, 50

generate_image(base_image_width, base_image_height, 'blue', 'img/base_image.png')

generate_image(hidden_image_width, hidden_image_height, 'red', 'img/hidden_image.png')
hidden_img = Image.open('img/hidden_image.png')
draw = ImageDraw.Draw(hidden_img)
draw.rectangle([(10, 10), (40, 40)], outline='black', width=3)
hidden_img.save('img/hidden_image.png')

print("Test images generating...\nbase_image.png ✅\nhidden_image.png ✅\nboth generated in the 'img' directory")

if __name__ == '__main__':
generate_test_images()
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
numpy==1.24.0
Pillow==9.5.0
Binary file added result_advanced_test/extracted_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added result_advanced_test/merged_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added results/extracted_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added results/merged_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
122 changes: 122 additions & 0 deletions steganography.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import argparse
import numpy as np
from PIL import Image
import itertools
import threading
import time
import sys

stop_spinner = False

def loading_spinner():
"""Display a loading spinner."""
spinner = itertools.cycle(['-', '\\', '|', '/'])
while not stop_spinner:
sys.stdout.write(next(spinner))
sys.stdout.flush()
sys.stdout.write('\b')
time.sleep(0.1)

def show_loading_spinner():
"""Start the loading spinner in a separate thread."""
global stop_spinner
stop_spinner = False
spinner_thread = threading.Thread(target=loading_spinner, daemon=True)
spinner_thread.start()
return spinner_thread

def stop_loading_spinner(spinner_thread):
"""Stop the loading spinner."""
global stop_spinner
stop_spinner = True
spinner_thread.join()
sys.stdout.write('Done! ✅\n')
sys.stdout.flush()

class Steganography:
def _merge_rgb(self, rgb1, rgb2):
"""Merge two RGB tuples by combining their lower 4 bits."""
return tuple((c1 & 0xF0) | (c2 >> 4) for c1, c2 in zip(rgb1, rgb2))

def _unmerge_rgb(self, rgb):
"""Extract the hidden RGB value from a merged RGB tuple."""
return tuple(((c & 0x0F) << 4) | ((c & 0xF0) >> 4) for c in rgb)

def merge(self, image1, image2, output):
"""Merge image2 into image1 and save to output path."""
if image2.size[0] > image1.size[0] or image2.size[1] > image1.size[1]:
raise ValueError('Image 2 must be smaller than or equal to Image 1 in both dimensions!')

if image1.mode != 'RGB' or image2.mode != 'RGB':
raise ValueError('Both images must be in RGB mode!')

print("Merging images...")
spinner_thread = show_loading_spinner()
try:
img1_array = np.array(image1)
img2_array = np.array(image2)
height, width = img2_array.shape[:2]
merged_array = np.copy(img1_array)

merged_array[:height, :width] = np.array([self._merge_rgb(tuple(c1), tuple(c2))
for c1, c2 in zip(img1_array[:height, :width], img2_array)])

new_image = Image.fromarray(merged_array, mode='RGB')
new_image.save(output)
finally:
stop_loading_spinner(spinner_thread)
print(f"Image merged and saved as '{output}'")

def unmerge(self, image, output):
"""Unmerge an image to extract the hidden image and save to output path."""
if image.mode != 'RGB':
raise ValueError('The image must be in RGB mode!')

print("Extracting hidden image...")
spinner_thread = show_loading_spinner()
try:
img_array = np.array(image)
unmerged_array = np.array([self._unmerge_rgb(tuple(c))
for c in img_array])

new_image = Image.fromarray(unmerged_array, mode='RGB')
new_image.save(output)
finally:
stop_loading_spinner(spinner_thread)
print(f"Hidden image extracted and saved as '{output}'")

def main():
"""Main function to handle command-line arguments and execute steganography tasks."""
parser = argparse.ArgumentParser(description='Steganography Tool')
subparser = parser.add_subparsers(dest='command')

merge = subparser.add_parser('merge', help='Merge one image into another')
merge.add_argument('--image1', required=True, help='Path to the base image')
merge.add_argument('--image2', required=True, help='Path to the image to be hidden')
merge.add_argument('--output', required=True, help='Path to save the merged image')

unmerge = subparser.add_parser('unmerge', help='Extract hidden image from a merged image')
unmerge.add_argument('--image', required=True, help='Path to the merged image')
unmerge.add_argument('--output', required=True, help='Path to save the extracted image')

args = parser.parse_args()

try:
stego = Steganography()
if args.command == 'merge':
with Image.open(args.image1) as image1, Image.open(args.image2) as image2:
stego.merge(image1, image2, args.output)
elif args.command == 'unmerge':
with Image.open(args.image) as image:
stego.unmerge(image, args.output)
else:
parser.print_help()
except FileNotFoundError as e:
print(f"File not found: {e.filename}")
except ValueError as e:
print(f"Value error: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")

if __name__ == '__main__':
main()

0 comments on commit 18be3c7

Please sign in to comment.