Steganography is the practice of hiding information within other data in a way that is not easily detectable. For this assignment, you’ll implement LSB (Least Significant Bit) steganography to hide text messages in PNG images.
Original pixel (red channel): 11001011 (203)To encode a 0 bit: 11001010 (202) - changed from 203 to 202To encode a 1 bit: 11001011 (203) - no change neededThe difference is visually undetectable.
Directly modifying palette indices can cause visible artifacts. Instead, use identical-color pairs.
The clever solution:
Find or create pairs of identical colors in the palette
Map each pair: lower index = 0 bit, higher index = 1 bit
Swap pixel indices between pairs to encode bits
Result is visually identical (same RGB values)
Example palette:
Index 0: RGB(100, 150, 200)Index 1: RGB(50, 75, 100)Index 2: RGB(100, 150, 200) ← Identical to index 0!
Pairs: (both are RGB(100, 150, 200))To encode a 0: Use index 0To encode a 1: Use index 2Image looks identical!If no pairs exist:Duplicate existing palette entries (up to 256 max):
// Original palette has 100 colors// Duplicate each color:for (int i = 0; i < original_count && total < 256; i++) { palette[original_count + i] = palette[i]; // Copy RGB pair_map[i] = original_count + i; // Record pair}
// For each bit in the message:for (size_t i = 0; i < total_bits; i++) { // Calculate pixel position size_t y = i / ihdr.width; size_t x = i % ihdr.width; // Calculate scanline offset (skip filter byte!) size_t scanline_offset = y * (1 + ihdr.width * bytes_per_pixel); size_t pixel_offset = scanline_offset + 1 + x * bytes_per_pixel; // Get bit to encode int bit; get_bit(message, i, &bit); // Modify LSB of first channel if (bit) { pixel_data[pixel_offset] |= 0x01; // Set LSB to 1 } else { pixel_data[pixel_offset] &= 0xFE; // Set LSB to 0 }}
Critical: Never modify the filter byte at the start of each scanline!Scanline structure: [filter_byte][pixel_0][pixel_1]...[pixel_width-1]
// Build pair mapping firstint pair_map[256]; // pair_map[i] = index of pair for index i// ... build pair_map ...// For each bit in the message:for (size_t i = 0; i < total_bits; i++) { size_t y = i / ihdr.width; size_t x = i % ihdr.width; size_t scanline_offset = y * (1 + ihdr.width); size_t pixel_offset = scanline_offset + 1 + x; uint8_t current_index = pixel_data[pixel_offset]; // Skip if this index has no pair if (pair_map[current_index] == -1) { continue; // or return error } int bit; get_bit(message, i, &bit); // Get the pair indices uint8_t idx0 = current_index; uint8_t idx1 = pair_map[current_index]; // Ensure idx0 < idx1 if (idx0 > idx1) { uint8_t temp = idx0; idx0 = idx1; idx1 = temp; } // 0 bit = lower index, 1 bit = higher index pixel_data[pixel_offset] = bit ? idx1 : idx0;}
# The images should look identical$ open tests/data/image.png$ open encoded.png# But have different file sizes (slightly)$ ls -lh tests/data/image.png encoded.png