On May 5, 2026, I and the other folks at NOLA Devs were messing around with steganography. While the others played with the excellent and far more comprehensive ST3GG suite, I decided to experiment on a more conceptual level and see how much of a toy implementation I could write right then and there. I didn’t complete this implementation at that meeting, but I got pretty far, and here I present the finished product for idle curiosity.
This program accepts an input image and a secret message, and embeds
the secret message in the lower bits of the red, green, and blue
channel values of each pixel in the image. The resulting image is then
written to an output file. Input and output image file names are
specified on the command line, and then the secret message is read
from standard in. When run with the -d flag, the secret message is
extracted from an input image and written to standard out.
The first four pixels of the image encode a 32-bit length for the secret message. If the secret message won’t fit in the image with this encoding, an error will result.
- The encoder splits a single byte across the red, green, and blue channels of a pixel in the image: the top 3 bits of the byte are embedded in the lower 3 bits of the red channel; the next 3 bits are embedded in the lower 3 bits of the green channel; and the bottom 2 bits are embedded in the lower 2 bits of the blue channel. While this approach works, it affects too many bits per channel, leading to visible artifacts in the output image. If you really wanted your message to be super-secret, it would be best to embed 1 bit per channel, as a variance of 1 bit would be too hard to detect by the naked eye of a casual observer.
- Works only on PPM images, which is an uncommon format in 2026.
- Does not understand PPM images with comments.
- Guile 3.0 or later (probably called
guilein your distro’s package manager) - (optional) An image conversion program to convert images in common formats (JPEG, PNG, etc.) into PPM images. Netpbm, ImageMagick, and GIMP will all work (but you may have to strip comments out for some of these).
To encode:
guile -s stegembed.scm [infile] [outfile]
The standard input will be read and used as a secret message to embed into the PPM image given by infile. The resulting image will be written as a PPM to outfile.
To decode:
guile -s stegembed.scm -d [infile]
The secret message will be read from infile and written to standard output. If you get garbage, there probably wasn’t a secret message actually embedded (or it might’ve been encrypted!).