Bitmap Encoding#
Steganography: Hiding Messages in Images#
Steganography involves concealing messages within another medium, such as images, audio, or text. Unlike encryption, which disguises the content of a message, steganography hides its very existence. Modern digital steganography often leverages multimedia files, particularly images, due to their high data redundancy. This makes minor modifications, such as changing pixel values, imperceptible to the human eye.
Least Significant Bit (LSB) Steganography#
The most common technique in image steganography is Least Significant Bit (LSB) embedding, where the least significant bit of a pixel’s color value is replaced with a bit from the secret message. For example:
Original: 11110010
Modified: 11110011
The change encodes a message bit while remaining visually undetectable. LSB steganography requires an agreed encoding scheme to identify where and how the data is hidden.
Applications and Security Enhancements#
Steganography is used for watermarking, secure communication, and metadata embedding. For added security, encryption methods like the Caesar Cipher can be applied to the message before embedding. Encoding the message in formats like Base64 ensures compatibility and reduces corruption risks.
Why Images?#
Images are ideal for steganography due to their pixel-level redundancy, enabling small changes without noticeable visual impact. Combining steganography with encryption provides an additional layer of security, ensuring even if the message is found, it remains indecipherable.
Encoding a message in an image#
How To hide a message#
Choose an Image: Any bitmap image can be used, but high-resolution images provide more space for hiding data. For this guide, we’ll use a bitmap image of a cat.
Prepare the Message: The message will be encoded in Base64 before embedding to ensure compatibility and readability.
Embed the Message: Using LSB steganography, replace the least significant bits of the image’s pixel data with bits from the encoded message.
Note
You may need to update the path
variable to point to the image you want to use.
Show code cell source
from PIL import Image
from IPython.display import display
img = Image.open("cat.bmp")
display(img)

import os
path = os.getcwd()
def base64_encode(input_string):
BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
encoded_string = ""
value = 0
bits = -6
base64_mask = 0x3F
for character in input_string.encode():
value = (value << 8) + character
bits += 8
while bits >= 0:
encoded_string += BASE64_CHARS[(value >> bits) & base64_mask]
bits -= 6
if bits > -6:
encoded_string += BASE64_CHARS[((value << 8) >> (bits + 8)) & base64_mask]
while len(encoded_string) % 4:
encoded_string += "="
return encoded_string
# This will allow us to highlight the differences in the binary data
def highlight_changes(original, modified):
highlighted_original = []
highlighted_modified = []
for o, m in zip(original, modified):
if o != m:
highlighted_original.append(f"[{o}]")
highlighted_modified.append(f"[{m}]")
else:
highlighted_original.append(o)
highlighted_modified.append(m)
return ''.join(highlighted_original), ''.join(highlighted_modified)
def embed_message(data, message, offset):
# Encode the message using Base64
base64_message = base64_encode(message)
print(f"Base64-encoded message: {base64_message}")
print(f"Base64 length (in characters): {len(base64_message)}")
binary_message = ''.join(format(ord(char), '08b') for char in base64_message)
print(f"Binary message: {binary_message}")
# Embed the length of the Base64 message (in bytes) in the first 32 bits
length_bits = format(len(base64_message), '032b')
print(f"Length bits: {length_bits}")
data = bytearray(data)
# Extract original binary at embedding positions
original_binary = "".join(format(data[offset + i], '08b') for i in range(len(length_bits + binary_message)))
modified_data = bytearray(data)
# Embed the message into the least significant bits
for i, bit in enumerate(length_bits + binary_message):
modified_data[offset + i] = (modified_data[offset + i] & 0xFE) | int(bit)
# Extract modified binary at embedding positions
modified_binary = "".join(format(modified_data[offset + i], '08b') for i in range(len(length_bits + binary_message)))
# Highlight changes
highlighted_original, highlighted_modified = highlight_changes(original_binary, modified_binary)
# Truncate for readability
def truncate_binary(binary_str, show_bits=64):
if len(binary_str) > show_bits * 2:
return binary_str[:show_bits] + "..." + binary_str[-show_bits:]
return binary_str
print("\nOriginal binary data at embedding positions (truncated):")
print(truncate_binary(highlighted_original))
print("\nModified binary data at embedding positions (truncated):")
print(truncate_binary(highlighted_modified))
return modified_data
input_file_path = os.path.join(path, "cat.bmp")
output_file_path = os.path.join(path, "hiddencat.bmp")
with open(input_file_path, "rb") as f:
data = f.read()
print("Enter the message to embed: ")
message_to_embed = input()
print(f"{message_to_embed}")
# Specify the offset where the pixel data starts (e.g., 54 for standard BMP)
pixel_data_offset = int.from_bytes(data[10:14], byteorder='little')
encoded_data = embed_message(data, message_to_embed, pixel_data_offset)
with open(output_file_path, "wb") as f:
f.write(encoded_data)
print(f"Message embedded successfully in Base64! Encoded image saved as {output_file_path}.")
Enter the message to embed:
Hi! I'm a cool cat!
Base64-encoded message: SGkhIEknbSBhIGNvb2wgY2F0IQ==
Base64 length (in characters): 28
Binary message: 01010011010001110110101101101000010010010100010101101011011011100110001001010011010000100110100001001001010001110100111001110110011000100011001001110111011001110101100100110010010001100011000001001001010100010011110100111101
Length bits: 00000000000000000000000000011100
Original binary data at embedding positions (truncated):
0110011[1]10001110010000100110011[1]10001110010000100110011[1]10...0110011[1]1000111[0]0100001[0]011001111000111[0]0100001001100111
Modified binary data at embedding positions (truncated):
0110011[0]10001110010000100110011[0]10001110010000100110011[0]10...0110011[0]1000111[1]0100001[1]011001111000111[1]0100001001100111
Message embedded successfully in Base64! Encoded image saved as /run/media/breezy/b1de4ffb-fd5e-4613-986d-b119a4c02043/GitHub/Jupyter-Books/cryptography-guide/steganography/hiddencat.bmp.
The comparison between the original and modified binary data highlights the subtle but precise changes made during the embedding process. Notice how the least significant bits (LSBs) of the original binary data are modified to encode the length and content of the hidden message. These alterations are so minor that they do not visibly affect the image, yet they securely store the message within its pixel data. This clever manipulation of binary values demonstrates the power of steganography, where seemingly insignificant details carry meaningful information—hidden in plain sight. By preserving the image’s integrity while embedding a secret, this technique showcases how technology can be used creatively to protect and communicate information.