3 min read

Image Steganography Tool

This utility allows you to hide secret text messages inside an image file (Steganography) and retrieve them later. It uses the Least Significant Bit (LSB) technique, which modifies the last bit of the pixel color values. These changes are so subtle that they are invisible to the human eye.

Modules Used:

  • Pillow: To manipulate image pixels.
  • argparse: To handle command-line arguments.

Installation

pip install Pillow

The Code

Save this as stego.py.

from PIL import Image
import argparse
import os
import sys

DELIMITER = "#####"

def text_to_bin(text):
    """Convert text to a binary string."""
    return ''.join(format(ord(char), '08b') for char in text)

def encode(image_path, secret_text, output_path):
    print(f"Encoding message into '{image_path}'...")
    img = Image.open(image_path)
    img = img.convert("RGB") # Ensure 3 channels
    pixels = img.load()

    # Add delimiter so we know when to stop decoding
    full_text = secret_text + DELIMITER
    binary_secret = text_to_bin(full_text)
    data_len = len(binary_secret)

    width, height = img.size

    # Check if image is large enough
    if data_len > width * height * 3:
        raise ValueError(f"Text is too long for this image. Max bits: {width*height*3}")

    data_index = 0

    for y in range(height):
        for x in range(width):
            r, g, b = pixels[x, y]

            # Modify LSB of Red, Green, and Blue channels
            # (val & ~1) clears the last bit, | int(bit) sets it
            if data_index < data_len:
                r = (r & ~1) | int(binary_secret[data_index])
                data_index += 1
            if data_index < data_len:
                g = (g & ~1) | int(binary_secret[data_index])
                data_index += 1
            if data_index < data_len:
                b = (b & ~1) | int(binary_secret[data_index])
                data_index += 1

            pixels[x, y] = (r, g, b)

            if data_index >= data_len:
                break
        if data_index >= data_len:
            break

    img.save(output_path)
    print(f"Success! Encoded image saved to '{output_path}'")

def decode(image_path):
    print(f"Decoding message from '{image_path}'...")
    img = Image.open(image_path)
    img = img.convert("RGB")
    pixels = img.load()

    binary_data = ""
    width, height = img.size

    for y in range(height):
        for x in range(width):
            r, g, b = pixels[x, y]

            # Extract LSB
            binary_data += str(r & 1)
            binary_data += str(g & 1)
            binary_data += str(b & 1)

    # Convert binary to text and look for delimiter
    decoded_text = ""
    for i in range(0, len(binary_data), 8):
        byte = binary_data[i:i+8]
        if len(byte) < 8: break

        decoded_text += chr(int(byte, 2))

        if decoded_text.endswith(DELIMITER):
            print("\n--- Hidden Message ---")
            print(decoded_text[:-len(DELIMITER)])
            print("----------------------")
            return

    print("No hidden message found (delimiter missing).")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Image Steganography Tool")
    subparsers = parser.add_subparsers(dest="command", required=True)

    # Encode Command
    enc_parser = subparsers.add_parser("encode", help="Hide text in an image")
    enc_parser.add_argument("image", help="Input image path")
    enc_parser.add_argument("text", help="Secret text to hide")
    enc_parser.add_argument("-o", "--output", default="secret.png", help="Output image path (must be PNG)")

    # Decode Command
    dec_parser = subparsers.add_parser("decode", help="Reveal text from an image")
    dec_parser.add_argument("image", help="Image with hidden text")

    args = parser.parse_args()

    if args.command == "encode":
        # PNG is required because JPEG compression destroys LSB data
        if not args.output.lower().endswith(".png"):
            print("Error: Output file must be a .png to prevent data loss.")
        else:
            encode(args.image, args.text, args.output)
    elif args.command == "decode":
        decode(args.image)

Usage

# Hide a message
python stego.py encode landscape.jpg "The treasure is buried under the palm tree." -o secret_image.png

# Reveal the message
python stego.py decode secret_image.png

programming/python/python