Steganographic Methods

Laboratory Assignment: Steganographic Methods

Objective

  1. Understand the concept of steganography and its difference from cryptography.
  2. Learn to implement basic steganographic techniques using Python.
  3. Gain hands-on experience with LSB (Least Significant Bit) image steganography.
  4. Explore text-based steganography methods.
  5. Understand the challenges and limitations of steganographic techniques.

Tools Required

  1. Python 3.x
  2. Python libraries: Pillow (PIL), numpy
  3. Text editor or IDE
  4. Sample image files (PNG format recommended)

Part 1: Introduction to Steganography

Task 1: Understanding Steganography

Part 2: LSB Image Steganography

Task 2: Installing Required Libraries

Install the necessary Python libraries:

1pip install pillow numpy

Task 3: Basic LSB Encoding

Create a Python script to hide text in an image using LSB technique:

 1from PIL import Image
 2import numpy as np
 3
 4def text_to_binary(text):
 5    """Convert text to binary string"""
 6    binary = ''.join(format(ord(char), '08b') for char in text)
 7    return binary
 8
 9def binary_to_text(binary):
10    """Convert binary string to text"""
11    text = ''
12    for i in range(0, len(binary), 8):
13        byte = binary[i:i+8]
14        if len(byte) == 8:
15            text += chr(int(byte, 2))
16    return text
17
18def hide_text_in_image(image_path, text, output_path):
19    """Hide text in image using LSB technique"""
20    # Open image
21    img = Image.open(image_path)
22    pixels = np.array(img)
23    
24    # Convert text to binary
25    binary_text = text_to_binary(text)
26    binary_text += '1111111111111110'  # Delimiter
27    
28    # Flatten pixel array
29    flat_pixels = pixels.flatten()
30    
31    # Check if image is large enough
32    if len(binary_text) > len(flat_pixels):
33        raise ValueError("Text too long for this image")
34    
35    # Hide text in LSB
36    for i in range(len(binary_text)):
37        # Modify LSB of pixel value
38        flat_pixels[i] = (flat_pixels[i] & ~1) | int(binary_text[i])
39    
40    # Reshape and save
41    modified_pixels = flat_pixels.reshape(pixels.shape)
42    result_img = Image.fromarray(modified_pixels.astype(np.uint8))
43    result_img.save(output_path)
44    print(f"Text hidden successfully in {output_path}")
45
46def extract_text_from_image(image_path):
47    """Extract hidden text from image"""
48    # Open image
49    img = Image.open(image_path)
50    pixels = np.array(img)
51    
52    # Flatten pixel array
53    flat_pixels = pixels.flatten()
54    
55    # Extract LSB
56    binary_text = ''
57    for pixel in flat_pixels:
58        binary_text += str(pixel & 1)
59        # Check for delimiter
60        if binary_text[-16:] == '1111111111111110':
61            break
62    
63    # Remove delimiter and convert to text
64    binary_text = binary_text[:-16]
65    return binary_to_text(binary_text)
66
67# Example usage
68if __name__ == "__main__":
69    # Hide text
70    secret_message = "Hello, this is a secret message!"
71    hide_text_in_image("sample_image.png", secret_message, "stego_image.png")
72    
73    # Extract text
74    extracted = extract_text_from_image("stego_image.png")
75    print(f"Extracted message: {extracted}")

Task 4: Testing LSB Steganography

  1. Create or download a sample PNG image (name it sample_image.png)
  2. Run the script to hide a secret message
  3. Verify that the extracted message matches the original
  4. Compare the original and stego images visually

Task 5: Advanced LSB with RGB Channels

Enhance the LSB technique to use all RGB channels:

 1def hide_text_rgb(image_path, text, output_path):
 2    """Hide text using all RGB channels"""
 3    img = Image.open(image_path)
 4    pixels = np.array(img)
 5    height, width, channels = pixels.shape
 6    
 7    binary_text = text_to_binary(text)
 8    binary_text += '1111111111111110'  # Delimiter
 9    
10    # Calculate required pixels
11    required_pixels = len(binary_text) // 3 + (1 if len(binary_text) % 3 else 0)
12    
13    if required_pixels > height * width:
14        raise ValueError("Text too long for this image")
15    
16    # Hide text in RGB channels
17    index = 0
18    for i in range(height):
19        for j in range(width):
20            for c in range(channels):
21                if index < len(binary_text):
22                    pixels[i, j, c] = (pixels[i, j, c] & ~1) | int(binary_text[index])
23                    index += 1
24                else:
25                    break
26            if index >= len(binary_text):
27                break
28        if index >= len(binary_text):
29            break
30    
31    result_img = Image.fromarray(pixels.astype(np.uint8))
32    result_img.save(output_path)
33    print(f"Text hidden in RGB channels: {output_path}")
34
35def extract_text_rgb(image_path):
36    """Extract text from RGB channels"""
37    img = Image.open(image_path)
38    pixels = np.array(img)
39    height, width, channels = pixels.shape
40    
41    binary_text = ''
42    for i in range(height):
43        for j in range(width):
44            for c in range(channels):
45                binary_text += str(pixels[i, j, c] & 1)
46                if binary_text[-16:] == '1111111111111110':
47                    break
48            if binary_text[-16:] == '1111111111111110':
49                break
50        if binary_text[-16:] == '1111111111111110':
51            break
52    
53    binary_text = binary_text[:-16]
54    return binary_to_text(binary_text)

Part 3: Text-Based Steganography

Task 6: Whitespace Steganography

Create a script to hide text using whitespace manipulation:

 1def hide_text_whitespace(text, output_file):
 2    """Hide text using whitespace (spaces and tabs)"""
 3    binary_text = text_to_binary(text)
 4    
 5    # Use space for '0' and tab for '1'
 6    whitespace_text = ''
 7    for bit in binary_text:
 8        if bit == '0':
 9            whitespace_text += ' '
10        else:
11            whitespace_text += '\t'
12    
13    # Add some visible text to make it look normal
14    cover_text = "This is a normal looking text file.\n" + whitespace_text + "\nEnd of file."
15    
16    with open(output_file, 'w') as f:
17        f.write(cover_text)
18    
19    print(f"Text hidden in whitespace: {output_file}")
20
21def extract_text_whitespace(input_file):
22    """Extract text from whitespace"""
23    with open(input_file, 'r') as f:
24        content = f.read()
25    
26    # Extract whitespace between lines
27    lines = content.split('\n')
28    if len(lines) >= 3:
29        whitespace_line = lines[1]
30        
31        binary_text = ''
32        for char in whitespace_line:
33            if char == ' ':
34                binary_text += '0'
35            elif char == '\t':
36                binary_text += '1'
37        
38        return binary_to_text(binary_text)
39    
40    return ""
41
42# Example usage
43hide_text_whitespace("Secret message here!", "whitespace_secret.txt")
44extracted = extract_text_whitespace("whitespace_secret.txt")
45print(f"Extracted: {extracted}")

Task 7: Word Length Steganography

Implement word-length based text steganography:

 1def hide_text_word_length(text, cover_text, output_file):
 2    """Hide text using word lengths (even=0, odd=1)"""
 3    binary_text = text_to_binary(text)
 4    
 5    words = cover_text.split()
 6    if len(words) < len(binary_text):
 7        raise ValueError("Cover text too short for message")
 8    
 9    # Modify word lengths
10    modified_words = []
11    for i, word in enumerate(words):
12        if i < len(binary_text):
13            if binary_text[i] == '0':
14                # Ensure even length
15                if len(word) % 2 == 1:
16                    word += 's'  # Add 's' to make even
17            else:
18                # Ensure odd length
19                if len(word) % 2 == 0:
20                    word += 'x'  # Add 'x' to make odd
21        modified_words.append(word)
22    
23    result_text = ' '.join(modified_words)
24    
25    with open(output_file, 'w') as f:
26        f.write(result_text)
27    
28    print(f"Text hidden using word lengths: {output_file}")
29
30def extract_text_word_length(input_file):
31    """Extract text from word lengths"""
32    with open(input_file, 'r') as f:
33        text = f.read()
34    
35    words = text.split()
36    binary_text = ''
37    
38    for word in words:
39        if len(word) % 2 == 0:
40            binary_text += '0'
41        else:
42            binary_text += '1'
43        # Look for delimiter pattern (8 consecutive zeros)
44        if binary_text[-8:] == '00000000':
45            break
46    
47    # Remove delimiter and convert
48    binary_text = binary_text[:-8]
49    return binary_to_text(binary_text)

Part 4: Steganalysis and Detection

Task 8: Basic Steganalysis

Create a simple steganalysis tool to detect LSB steganography:

 1import statistics
 2
 3def analyze_lsb_distribution(image_path):
 4    """Analyze LSB distribution to detect hidden data"""
 5    img = Image.open(image_path)
 6    pixels = np.array(img)
 7    flat_pixels = pixels.flatten()
 8    
 9    # Extract LSBs
10    lsb_values = [pixel & 1 for pixel in flat_pixels]
11    
12    # Calculate statistics
13    zeros = lsb_values.count(0)
14    ones = lsb_values.count(1)
15    total = len(lsb_values)
16    
17    print(f"LSB Analysis for {image_path}:")
18    print(f"Total pixels: {total}")
19    print(f"Zeros: {zeros} ({zeros/total*100:.2f}%)")
20    print(f"Ones: {ones} ({ones/total*100:.2f}%)")
21    
22    # Simple detection: if distribution is too close to 50-50, it might be suspicious
23    ratio = min(zeros, ones) / max(zeros, ones)
24    if ratio > 0.9:
25        print("⚠️  Suspicious: LSB distribution is very balanced (possible steganography)")
26    elif ratio > 0.7:
27        print("⚠️  Moderately suspicious: LSB distribution is quite balanced")
28    else:
29        print("✓ Normal: LSB distribution shows natural variation")
30
31def analyze_image_quality(original_path, stego_path):
32    """Compare original and stego images"""
33    original = Image.open(original_path)
34    stego = Image.open(stego_path)
35    
36    # Convert to same mode if needed
37    if original.mode != stego.mode:
38        stego = stego.convert(original.mode)
39    
40    # Calculate MSE (Mean Squared Error)
41    orig_pixels = np.array(original)
42    stego_pixels = np.array(stego)
43    
44    mse = np.mean((orig_pixels - stego_pixels) ** 2)
45    print(f"Mean Squared Error: {mse}")
46    
47    if mse < 1:
48        print("✓ Very low visual distortion")
49    elif mse < 10:
50        print("✓ Low visual distortion")
51    else:
52        print("⚠️  Significant visual distortion detected")

Part 5: Advanced Techniques

Task 9: Error-Correction and Encryption

Enhance steganography with error correction and encryption:

 1import hashlib
 2from cryptography.fernet import Fernet
 3
 4def generate_key(password):
 5    """Generate encryption key from password"""
 6    return hashlib.sha256(password.encode()).digest()
 7
 8def encrypt_message(message, password):
 9    """Encrypt message using password"""
10    key = generate_key(password)
11    fernet = Fernet(Fernet.generate_key())
12    return fernet.encrypt(message.encode())
13
14def decrypt_message(encrypted_message, password):
15    """Decrypt message using password"""
16    key = generate_key(password)
17    fernet = Fernet(key)
18    return fernet.decrypt(encrypted_message).decode()
19
20def hide_encrypted_text(image_path, text, password, output_path):
21    """Hide encrypted text in image"""
22    # Encrypt the message
23    encrypted = encrypt_message(text, password)
24    
25    # Convert to binary and hide
26    binary_encrypted = ''.join(format(byte, '08b') for byte in encrypted)
27    
28    # Use the LSB function to hide encrypted data
29    img = Image.open(image_path)
30    pixels = np.array(img)
31    flat_pixels = pixels.flatten()
32    
33    if len(binary_encrypted) + 16 > len(flat_pixels):  # +16 for delimiter
34        raise ValueError("Encrypted text too long for this image")
35    
36    binary_encrypted += '1111111111111110'  # Delimiter
37    
38    for i in range(len(binary_encrypted)):
39        flat_pixels[i] = (flat_pixels[i] & ~1) | int(binary_encrypted[i])
40    
41    modified_pixels = flat_pixels.reshape(pixels.shape)
42    result_img = Image.fromarray(modified_pixels.astype(np.uint8))
43    result_img.save(output_path)
44    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:

 1import argparse
 2import os
 3
 4def main():
 5    parser = argparse.ArgumentParser(description='Steganography Tool')
 6    parser.add_argument('action', choices=['hide', 'extract', 'analyze'], 
 7                       help='Action to perform')
 8    parser.add_argument('-i', '--input', required=True, 
 9                       help='Input image file')
10    parser.add_argument('-o', '--output', 
11                       help='Output file')
12    parser.add_argument('-t', '--text', 
13                       help='Text to hide')
14    parser.add_argument('-f', '--file', 
15                       help='File containing text to hide')
16    parser.add_argument('-p', '--password', 
17                       help='Password for encryption')
18    parser.add_argument('-m', '--method', choices=['lsb', 'rgb', 'text'], 
19                       default='lsb', help='Steganography method')
20    
21    args = parser.parse_args()
22    
23    try:
24        if args.action == 'hide':
25            if not args.output:
26                print("Error: Output file required for hiding")
27                return
28            
29            # Get text to hide
30            if args.file:
31                with open(args.file, 'r') as f:
32                    text = f.read()
33            elif args.text:
34                text = args.text
35            else:
36                print("Error: Text or file required for hiding")
37                return
38            
39            # Hide text
40            if args.method == 'lsb':
41                hide_text_in_image(args.input, text, args.output)
42            elif args.method == 'rgb':
43                hide_text_rgb(args.input, text, args.output)
44            elif args.method == 'text':
45                hide_text_whitespace(text, args.output)
46            
47            print(f"Text hidden successfully in {args.output}")
48            
49        elif args.action == 'extract':
50            # Extract text
51            if args.method == 'lsb':
52                text = extract_text_from_image(args.input)
53            elif args.method == 'rgb':
54                text = extract_text_rgb(args.input)
55            elif args.method == 'text':
56                text = extract_text_whitespace(args.input)
57            
58            if args.output:
59                with open(args.output, 'w') as f:
60                    f.write(text)
61                print(f"Text extracted to {args.output}")
62            else:
63                print(f"Extracted text: {text}")
64                
65        elif args.action == 'analyze':
66            analyze_lsb_distribution(args.input)
67            
68    except Exception as e:
69        print(f"Error: {e}")
70
71if __name__ == "__main__":
72    main()

Part 7: Testing and Validation

Task 11: Create Test Suite

Create a comprehensive test to validate your steganography implementation:

 1import unittest
 2import os
 3import tempfile
 4
 5class TestSteganography(unittest.TestCase):
 6    
 7    def setUp(self):
 8        """Create temporary files for testing"""
 9        self.temp_dir = tempfile.mkdtemp()
10        self.test_image = os.path.join(self.temp_dir, "test.png")
11        self.stego_image = os.path.join(self.temp_dir, "stego.png")
12        self.test_text = os.path.join(self.temp_dir, "secret.txt")
13        
14        # Create a simple test image
15        test_img = Image.new('RGB', (100, 100), color='red')
16        test_img.save(self.test_image)
17        
18        # Create test text
19        with open(self.test_text, 'w') as f:
20            f.write("This is a secret test message!")
21    
22    def tearDown(self):
23        """Clean up temporary files"""
24        import shutil
25        shutil.rmtree(self.temp_dir)
26    
27    def test_text_to_binary_conversion(self):
28        """Test text to binary conversion"""
29        text = "ABC"
30        binary = text_to_binary(text)
31        self.assertEqual(binary, "010000010100001001000011")
32        
33        # Test reverse conversion
34        converted_back = binary_to_text(binary)
35        self.assertEqual(converted_back, text)
36    
37    def test_lsb_hide_extract(self):
38        """Test LSB hiding and extraction"""
39        secret_message = "Hello, Steganography!"
40        
41        # Hide message
42        hide_text_in_image(self.test_image, secret_message, self.stego_image)
43        
44        # Extract message
45        extracted = extract_text_from_image(self.stego_image)
46        
47        self.assertEqual(extracted, secret_message)
48    
49    def test_rgb_hide_extract(self):
50        """Test RGB channel hiding and extraction"""
51        secret_message = "RGB steganography test"
52        
53        # Hide message
54        hide_text_rgb(self.test_image, secret_message, self.stego_image)
55        
56        # Extract message
57        extracted = extract_text_rgb(self.stego_image)
58        
59        self.assertEqual(extracted, secret_message)
60    
61    def test_whitespace_hide_extract(self):
62        """Test whitespace steganography"""
63        secret_message = "Whitespace test"
64        
65        # Hide message
66        output_file = os.path.join(self.temp_dir, "whitespace.txt")
67        hide_text_whitespace(secret_message, output_file)
68        
69        # Extract message
70        extracted = extract_text_whitespace(output_file)
71        
72        self.assertEqual(extracted, secret_message)
73
74# Run tests
75if __name__ == '__main__':
76    unittest.main()

Part 8: Real-World Applications

Task 12: Create a Digital Watermarking System

Implement a simple digital watermarking system:

 1def create_watermark(original_image, watermark_text, output_image):
 2    """Create a simple digital watermark"""
 3    # Hide watermark in multiple locations for robustness
 4    img = Image.open(original_image)
 5    pixels = np.array(img)
 6    
 7    # Convert watermark to binary
 8    binary_watermark = text_to_binary(watermark_text)
 9    
10    # Embed watermark in multiple locations (corners and center)
11    locations = [
12        (0, 0),  # Top-left
13        (pixels.shape[1]-50, 0),  # Top-right
14        (0, pixels.shape[0]-50),  # Bottom-left
15        (pixels.shape[1]//2, pixels.shape[0]//2)  # Center
16    ]
17    
18    for start_x, start_y in locations:
19        index = 0
20        for y in range(start_y, min(start_y + 50, pixels.shape[0])):
21            for x in range(start_x, min(start_x + 50, pixels.shape[1])):
22                if index < len(binary_watermark):
23                    for c in range(3):  # RGB channels
24                        if index < len(binary_watermark):
25                            pixels[y, x, c] = (pixels[y, x, c] & ~1) | int(binary_watermark[index])
26                            index += 1
27    
28    result_img = Image.fromarray(pixels.astype(np.uint8))
29    result_img.save(output_image)
30    print(f"Watermark embedded: {output_image}")
31
32def verify_watermark(watermarked_image, original_watermark):
33    """Verify if watermark exists in image"""
34    img = Image.open(watermarked_image)
35    pixels = np.array(img)
36    
37    # Try to extract watermark from different locations
38    locations = [
39        (0, 0),  # Top-left
40        (pixels.shape[1]-50, 0),  # Top-right
41        (0, pixels.shape[0]-50),  # Bottom-left
42        (pixels.shape[1]//2, pixels.shape[0]//2)  # Center
43    ]
44    
45    binary_watermark = text_to_binary(original_watermark)
46    
47    for start_x, start_y in locations:
48        extracted_binary = ''
49        index = 0
50        for y in range(start_y, min(start_y + 50, pixels.shape[0])):
51            for x in range(start_x, min(start_x + 50, pixels.shape[1])):
52                if index < len(binary_watermark):
53                    for c in range(3):  # RGB channels
54                        if index < len(binary_watermark):
55                            extracted_binary += str(pixels[y, x, c] & 1)
56                            index += 1
57        
58        # Check if extracted matches original
59        if extracted_binary == binary_watermark:
60            return True, f"Watermark found at location ({start_x}, {start_y})"
61    
62    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)

  1. Audio Steganography: Implement LSB steganography for WAV files
  2. Adaptive Steganography: Create a system that adapts embedding based on image characteristics
  3. Multi-layer Security: Combine steganography with strong encryption
  4. Steganalysis Tools: Develop more sophisticated detection algorithms
  5. Performance Analysis: Compare different techniques for capacity, speed, and detectability
Basics of Symmetric Encryption Algorithms →