Steganographic Methods
Innovative Technologies for Computer Security
Practice
Laboratory Assignment: Steganographic Methods
Objective
- Understand the concept of steganography and its difference from cryptography.
- Learn to implement basic steganographic techniques using Python.
- Gain hands-on experience with LSB (Least Significant Bit) image steganography.
- Explore text-based steganography methods.
- Understand the challenges and limitations of steganographic techniques.
Tools Required
- Python 3.x
- Python libraries: Pillow (PIL), numpy
- Text editor or IDE
- Sample image files (PNG format recommended)
Part 1: Introduction to Steganography
Task 1: Understanding Steganography
- Read the theory: Steganographic methods for information protection
- Understand the difference between steganography and cryptography
- Learn about different types of steganography (image, audio, text)
Part 2: LSB Image Steganography
Task 2: Installing Required Libraries
Install the necessary Python libraries:
Task 3: Basic LSB Encoding
Create a Python script to hide text in an image using LSB technique:
from PIL import Image
import numpy as np
def text_to_binary(text):
"""Convert text to binary string"""
binary = ''.join(format(ord(char), '08b') for char in text)
return binary
def binary_to_text(binary):
"""Convert binary string to text"""
text = ''
for i in range(0, len(binary), 8):
byte = binary[i:i+8]
if len(byte) == 8:
text += chr(int(byte, 2))
return text
def hide_text_in_image(image_path, text, output_path):
"""Hide text in image using LSB technique"""
# Open image
img = Image.open(image_path)
pixels = np.array(img)
# Convert text to binary
binary_text = text_to_binary(text)
binary_text += '1111111111111110' # Delimiter
# Flatten pixel array
flat_pixels = pixels.flatten()
# Check if image is large enough
if len(binary_text) > len(flat_pixels):
raise ValueError("Text too long for this image")
# Hide text in LSB
for i in range(len(binary_text)):
# Modify LSB of pixel value
flat_pixels[i] = (flat_pixels[i] & ~1) | int(binary_text[i])
# Reshape and save
modified_pixels = flat_pixels.reshape(pixels.shape)
result_img = Image.fromarray(modified_pixels.astype(np.uint8))
result_img.save(output_path)
print(f"Text hidden successfully in {output_path}")
def extract_text_from_image(image_path):
"""Extract hidden text from image"""
# Open image
img = Image.open(image_path)
pixels = np.array(img)
# Flatten pixel array
flat_pixels = pixels.flatten()
# Extract LSB
binary_text = ''
for pixel in flat_pixels:
binary_text += str(pixel & 1)
# Check for delimiter
if binary_text[-16:] == '1111111111111110':
break
# Remove delimiter and convert to text
binary_text = binary_text[:-16]
return binary_to_text(binary_text)
# Example usage
if __name__ == "__main__":
# Hide text
secret_message = "Hello, this is a secret message!"
hide_text_in_image("sample_image.png", secret_message, "stego_image.png")
# Extract text
extracted = extract_text_from_image("stego_image.png")
print(f"Extracted message: {extracted}")Task 4: Testing LSB Steganography
- Create or download a sample PNG image (name it
sample_image.png) - Run the script to hide a secret message
- Verify that the extracted message matches the original
- Compare the original and stego images visually
Task 5: Advanced LSB with RGB Channels
Enhance the LSB technique to use all RGB channels:
def hide_text_rgb(image_path, text, output_path):
"""Hide text using all RGB channels"""
img = Image.open(image_path)
pixels = np.array(img)
height, width, channels = pixels.shape
binary_text = text_to_binary(text)
binary_text += '1111111111111110' # Delimiter
# Calculate required pixels
required_pixels = len(binary_text) // 3 + (1 if len(binary_text) % 3 else 0)
if required_pixels > height * width:
raise ValueError("Text too long for this image")
# Hide text in RGB channels
index = 0
for i in range(height):
for j in range(width):
for c in range(channels):
if index < len(binary_text):
pixels[i, j, c] = (pixels[i, j, c] & ~1) | int(binary_text[index])
index += 1
else:
break
if index >= len(binary_text):
break
if index >= len(binary_text):
break
result_img = Image.fromarray(pixels.astype(np.uint8))
result_img.save(output_path)
print(f"Text hidden in RGB channels: {output_path}")
def extract_text_rgb(image_path):
"""Extract text from RGB channels"""
img = Image.open(image_path)
pixels = np.array(img)
height, width, channels = pixels.shape
binary_text = ''
for i in range(height):
for j in range(width):
for c in range(channels):
binary_text += str(pixels[i, j, c] & 1)
if binary_text[-16:] == '1111111111111110':
break
if binary_text[-16:] == '1111111111111110':
break
if binary_text[-16:] == '1111111111111110':
break
binary_text = binary_text[:-16]
return binary_to_text(binary_text)Part 3: Text-Based Steganography
Task 6: Whitespace Steganography
Create a script to hide text using whitespace manipulation:
def hide_text_whitespace(text, output_file):
"""Hide text using whitespace (spaces and tabs)"""
binary_text = text_to_binary(text)
# Use space for '0' and tab for '1'
whitespace_text = ''
for bit in binary_text:
if bit == '0':
whitespace_text += ' '
else:
whitespace_text += '\t'
# Add some visible text to make it look normal
cover_text = "This is a normal looking text file.\n" + whitespace_text + "\nEnd of file."
with open(output_file, 'w') as f:
f.write(cover_text)
print(f"Text hidden in whitespace: {output_file}")
def extract_text_whitespace(input_file):
"""Extract text from whitespace"""
with open(input_file, 'r') as f:
content = f.read()
# Extract whitespace between lines
lines = content.split('\n')
if len(lines) >= 3:
whitespace_line = lines[1]
binary_text = ''
for char in whitespace_line:
if char == ' ':
binary_text += '0'
elif char == '\t':
binary_text += '1'
return binary_to_text(binary_text)
return ""
# Example usage
hide_text_whitespace("Secret message here!", "whitespace_secret.txt")
extracted = extract_text_whitespace("whitespace_secret.txt")
print(f"Extracted: {extracted}")Task 7: Word Length Steganography
Implement word-length based text steganography:
def hide_text_word_length(text, cover_text, output_file):
"""Hide text using word lengths (even=0, odd=1)"""
binary_text = text_to_binary(text)
words = cover_text.split()
if len(words) < len(binary_text):
raise ValueError("Cover text too short for message")
# Modify word lengths
modified_words = []
for i, word in enumerate(words):
if i < len(binary_text):
if binary_text[i] == '0':
# Ensure even length
if len(word) % 2 == 1:
word += 's' # Add 's' to make even
else:
# Ensure odd length
if len(word) % 2 == 0:
word += 'x' # Add 'x' to make odd
modified_words.append(word)
result_text = ' '.join(modified_words)
with open(output_file, 'w') as f:
f.write(result_text)
print(f"Text hidden using word lengths: {output_file}")
def extract_text_word_length(input_file):
"""Extract text from word lengths"""
with open(input_file, 'r') as f:
text = f.read()
words = text.split()
binary_text = ''
for word in words:
if len(word) % 2 == 0:
binary_text += '0'
else:
binary_text += '1'
# Look for delimiter pattern (8 consecutive zeros)
if binary_text[-8:] == '00000000':
break
# Remove delimiter and convert
binary_text = binary_text[:-8]
return binary_to_text(binary_text)Part 4: Steganalysis and Detection
Task 8: Basic Steganalysis
Create a simple steganalysis tool to detect LSB steganography:
import statistics
def analyze_lsb_distribution(image_path):
"""Analyze LSB distribution to detect hidden data"""
img = Image.open(image_path)
pixels = np.array(img)
flat_pixels = pixels.flatten()
# Extract LSBs
lsb_values = [pixel & 1 for pixel in flat_pixels]
# Calculate statistics
zeros = lsb_values.count(0)
ones = lsb_values.count(1)
total = len(lsb_values)
print(f"LSB Analysis for {image_path}:")
print(f"Total pixels: {total}")
print(f"Zeros: {zeros} ({zeros/total*100:.2f}%)")
print(f"Ones: {ones} ({ones/total*100:.2f}%)")
# Simple detection: if distribution is too close to 50-50, it might be suspicious
ratio = min(zeros, ones) / max(zeros, ones)
if ratio > 0.9:
print("⚠️ Suspicious: LSB distribution is very balanced (possible steganography)")
elif ratio > 0.7:
print("⚠️ Moderately suspicious: LSB distribution is quite balanced")
else:
print("✓ Normal: LSB distribution shows natural variation")
def analyze_image_quality(original_path, stego_path):
"""Compare original and stego images"""
original = Image.open(original_path)
stego = Image.open(stego_path)
# Convert to same mode if needed
if original.mode != stego.mode:
stego = stego.convert(original.mode)
# Calculate MSE (Mean Squared Error)
orig_pixels = np.array(original)
stego_pixels = np.array(stego)
mse = np.mean((orig_pixels - stego_pixels) ** 2)
print(f"Mean Squared Error: {mse}")
if mse < 1:
print("✓ Very low visual distortion")
elif mse < 10:
print("✓ Low visual distortion")
else:
print("⚠️ Significant visual distortion detected")Part 5: Advanced Techniques
Task 9: Error-Correction and Encryption
Enhance steganography with error correction and encryption:
import hashlib
from cryptography.fernet import Fernet
def generate_key(password):
"""Generate encryption key from password"""
return hashlib.sha256(password.encode()).digest()
def encrypt_message(message, password):
"""Encrypt message using password"""
key = generate_key(password)
fernet = Fernet(Fernet.generate_key())
return fernet.encrypt(message.encode())
def decrypt_message(encrypted_message, password):
"""Decrypt message using password"""
key = generate_key(password)
fernet = Fernet(key)
return fernet.decrypt(encrypted_message).decode()
def hide_encrypted_text(image_path, text, password, output_path):
"""Hide encrypted text in image"""
# Encrypt the message
encrypted = encrypt_message(text, password)
# Convert to binary and hide
binary_encrypted = ''.join(format(byte, '08b') for byte in encrypted)
# Use the LSB function to hide encrypted data
img = Image.open(image_path)
pixels = np.array(img)
flat_pixels = pixels.flatten()
if len(binary_encrypted) + 16 > len(flat_pixels): # +16 for delimiter
raise ValueError("Encrypted text too long for this image")
binary_encrypted += '1111111111111110' # Delimiter
for i in range(len(binary_encrypted)):
flat_pixels[i] = (flat_pixels[i] & ~1) | int(binary_encrypted[i])
modified_pixels = flat_pixels.reshape(pixels.shape)
result_img = Image.fromarray(modified_pixels.astype(np.uint8))
result_img.save(output_path)
print(f"Encrypted text hidden: {output_path}")Part 6: Practical Exercises
Task 10: Complete Steganography System
Create a comprehensive steganography tool with command-line interface:
import argparse
import os
def main():
parser = argparse.ArgumentParser(description='Steganography Tool')
parser.add_argument('action', choices=['hide', 'extract', 'analyze'],
help='Action to perform')
parser.add_argument('-i', '--input', required=True,
help='Input image file')
parser.add_argument('-o', '--output',
help='Output file')
parser.add_argument('-t', '--text',
help='Text to hide')
parser.add_argument('-f', '--file',
help='File containing text to hide')
parser.add_argument('-p', '--password',
help='Password for encryption')
parser.add_argument('-m', '--method', choices=['lsb', 'rgb', 'text'],
default='lsb', help='Steganography method')
args = parser.parse_args()
try:
if args.action == 'hide':
if not args.output:
print("Error: Output file required for hiding")
return
# Get text to hide
if args.file:
with open(args.file, 'r') as f:
text = f.read()
elif args.text:
text = args.text
else:
print("Error: Text or file required for hiding")
return
# Hide text
if args.method == 'lsb':
hide_text_in_image(args.input, text, args.output)
elif args.method == 'rgb':
hide_text_rgb(args.input, text, args.output)
elif args.method == 'text':
hide_text_whitespace(text, args.output)
print(f"Text hidden successfully in {args.output}")
elif args.action == 'extract':
# Extract text
if args.method == 'lsb':
text = extract_text_from_image(args.input)
elif args.method == 'rgb':
text = extract_text_rgb(args.input)
elif args.method == 'text':
text = extract_text_whitespace(args.input)
if args.output:
with open(args.output, 'w') as f:
f.write(text)
print(f"Text extracted to {args.output}")
else:
print(f"Extracted text: {text}")
elif args.action == 'analyze':
analyze_lsb_distribution(args.input)
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()Part 7: Testing and Validation
Task 11: Create Test Suite
Create a comprehensive test to validate your steganography implementation:
import unittest
import os
import tempfile
class TestSteganography(unittest.TestCase):
def setUp(self):
"""Create temporary files for testing"""
self.temp_dir = tempfile.mkdtemp()
self.test_image = os.path.join(self.temp_dir, "test.png")
self.stego_image = os.path.join(self.temp_dir, "stego.png")
self.test_text = os.path.join(self.temp_dir, "secret.txt")
# Create a simple test image
test_img = Image.new('RGB', (100, 100), color='red')
test_img.save(self.test_image)
# Create test text
with open(self.test_text, 'w') as f:
f.write("This is a secret test message!")
def tearDown(self):
"""Clean up temporary files"""
import shutil
shutil.rmtree(self.temp_dir)
def test_text_to_binary_conversion(self):
"""Test text to binary conversion"""
text = "ABC"
binary = text_to_binary(text)
self.assertEqual(binary, "010000010100001001000011")
# Test reverse conversion
converted_back = binary_to_text(binary)
self.assertEqual(converted_back, text)
def test_lsb_hide_extract(self):
"""Test LSB hiding and extraction"""
secret_message = "Hello, Steganography!"
# Hide message
hide_text_in_image(self.test_image, secret_message, self.stego_image)
# Extract message
extracted = extract_text_from_image(self.stego_image)
self.assertEqual(extracted, secret_message)
def test_rgb_hide_extract(self):
"""Test RGB channel hiding and extraction"""
secret_message = "RGB steganography test"
# Hide message
hide_text_rgb(self.test_image, secret_message, self.stego_image)
# Extract message
extracted = extract_text_rgb(self.stego_image)
self.assertEqual(extracted, secret_message)
def test_whitespace_hide_extract(self):
"""Test whitespace steganography"""
secret_message = "Whitespace test"
# Hide message
output_file = os.path.join(self.temp_dir, "whitespace.txt")
hide_text_whitespace(secret_message, output_file)
# Extract message
extracted = extract_text_whitespace(output_file)
self.assertEqual(extracted, secret_message)
# Run tests
if __name__ == '__main__':
unittest.main()Part 8: Real-World Applications
Task 12: Create a Digital Watermarking System
Implement a simple digital watermarking system:
def create_watermark(original_image, watermark_text, output_image):
"""Create a simple digital watermark"""
# Hide watermark in multiple locations for robustness
img = Image.open(original_image)
pixels = np.array(img)
# Convert watermark to binary
binary_watermark = text_to_binary(watermark_text)
# Embed watermark in multiple locations (corners and center)
locations = [
(0, 0), # Top-left
(pixels.shape[1]-50, 0), # Top-right
(0, pixels.shape[0]-50), # Bottom-left
(pixels.shape[1]//2, pixels.shape[0]//2) # Center
]
for start_x, start_y in locations:
index = 0
for y in range(start_y, min(start_y + 50, pixels.shape[0])):
for x in range(start_x, min(start_x + 50, pixels.shape[1])):
if index < len(binary_watermark):
for c in range(3): # RGB channels
if index < len(binary_watermark):
pixels[y, x, c] = (pixels[y, x, c] & ~1) | int(binary_watermark[index])
index += 1
result_img = Image.fromarray(pixels.astype(np.uint8))
result_img.save(output_image)
print(f"Watermark embedded: {output_image}")
def verify_watermark(watermarked_image, original_watermark):
"""Verify if watermark exists in image"""
img = Image.open(watermarked_image)
pixels = np.array(img)
# Try to extract watermark from different locations
locations = [
(0, 0), # Top-left
(pixels.shape[1]-50, 0), # Top-right
(0, pixels.shape[0]-50), # Bottom-left
(pixels.shape[1]//2, pixels.shape[0]//2) # Center
]
binary_watermark = text_to_binary(original_watermark)
for start_x, start_y in locations:
extracted_binary = ''
index = 0
for y in range(start_y, min(start_y + 50, pixels.shape[0])):
for x in range(start_x, min(start_x + 50, pixels.shape[1])):
if index < len(binary_watermark):
for c in range(3): # RGB channels
if index < len(binary_watermark):
extracted_binary += str(pixels[y, x, c] & 1)
index += 1
# Check if extracted matches original
if extracted_binary == binary_watermark:
return True, f"Watermark found at location ({start_x}, {start_y})"
return False, "Watermark not found"Submission Guidelines
- Submit a report that includes:
- Detailed explanation of each steganographic technique implemented
- Screenshots of original and modified images
- Code snippets for each implementation
- Analysis of capacity vs. detectability trade-offs
- Discussion of ethical considerations and legitimate use cases
- Reflection on the challenges of steganalysis and detection
- Include all Python scripts and test results
- Demonstrate successful hiding and extraction of messages using different techniques
- Discuss the robustness of each method against various attacks (compression, filtering, etc.)
Additional Challenges (Optional)
- Audio Steganography: Implement LSB steganography for WAV files
- Adaptive Steganography: Create a system that adapts embedding based on image characteristics
- Multi-layer Security: Combine steganography with strong encryption
- Steganalysis Tools: Develop more sophisticated detection algorithms
- Performance Analysis: Compare different techniques for capacity, speed, and detectability