Source Code: https://github.com/rohanphanse/steganography
Live Demo: https://replit.com/@Roar123/steganography
Steganography is a form of cryptography where a hidden message is concealed inside another message. For images, this process works by encrypting the most significant bits of a hidden image into the least significant bits of a main image, and then displaying the hidden image bits to reveal the image. The beauty of the steganography is that the hidden bits fly under the human vision radar, yet the information is readily available for the computer to decrypt it.
I wrote this program in Rust and gained experience with using the image
crate from this amazing tutorial by freeCodeCamp (link).
I want to feature 2 pieces of code, because they were the most rewarding and challenging things to get right.
This was super fun for me to figure out and I learned how to use the bitwise operators AND &
, OR |
, and << BITSHIFT >>
.
// src/main.rs, line 129
encrypted.push(
(main_pixel[i] & 0b_1111_1000) + // Replace last 3 bits of main pixel
((hidden_pixel[i] & 0b_1110_0000) >> 5) // with first 3 bits of hidden pixel
);
The code above creates an encrypted pixel where the three least significant bits of the main image are replaced with the three most significant bits of the hidden pixel. The result is that the encrypted image resembles the main image to ~97% accuracy, but contains enough information to reassemble the hidden image to ~88% accuracy. This is how steganography slips hidden images under the human radar while allowing computers to recover them.
Images are stored in buffers (1d arrays), so I had to figure out how to traverse the buffer while keeping track of the 2d location of the pixels. After a lot of mindless debugging, I realized that image_height * image_width * 4 == buffer_length
and having reached enlightenment, I blissfully wrote the code below in a state of nirvana.
// src/main.rs, lines 97-107
for h in 0..main_height {
for w in 0..main_width {
// Convert 2d pixel location (w, h) to 1d index in image buffer
i = (h * main_width + w) * 4;
if h < hidden_height && w < hidden_width {
encrypted.splice(i..=i + 3, encrypt_bits(&main_vec, i, &hidden_vec, (h * hidden_width + w) * 4));
} else {
encrypted.splice(i..=i + 3, encode_transparent_pixel(&main_vec, i));
}
}
}
My steganographer passes my tests with flying colors on .png
files and fails on .jpg
(I suspect that jpeg does some compression which conflicts with my bit manipulations).
Credit to pngimg.com for Mario and Stack Overflow post for Tux.
Credit to Wikipedia for Paysage by Camille Pissarro and Mona Lisa