A picture is worth a thousand bits

A picture is worth a thousand bits

Encoding invisible unicode messages into PNGs with RGB value flipping

While bored in class, I began wondering how one might be able to encode the contents of a string into an image as a secret message of sorts, undetectable to the naked eye. The string might be decipherable by someone who, given the image, knew how and where to look.

The Idea

All images are made up of pixels, each of which is little more than a color. And, as we know from computer screens, image colors can be represented using the RGBA scheme where a byte of data (8 bits, 256 possible values) is used to represent the individual values of R (red), G (green), B (blue), and A (alpha/opacity).

For example, a nice teal might be represented as such:

rgba(41, 215, 177, 0.8)

Encoding a message, then, will require systematically adjusting the RGB colors of an image in a near-unnoticeable pattern. We can accomplish this by encoding each pixel with 1/2 of a single UTF-8 character. How does this work?

  • UTF-8 is an encoding standard that represents characters using a single byte, or 8 bits.

  • RGBA color representations contain four independent numerical values.

  • We can map each RGBA value to a bitwise value (0 or 1) depending on whether it's even (0) or odd (1).

    • 41 % 2 = 1 (1 bit)

    • 216 % 2 = 0 (0 bit)

  • By stringing two pixels together, we can represent a single UTF-8 character:

    • RGBA(40, 215, 178, 0.8) % 2 → 0100

    • RGBA(42, 216, 178, 0.81) % 2 → 0001

    • 0100 0001 = A in UTF-8

Encoding

Using this train of thought, we can encode a message into a PNG by iterating through its pixels and intentionally flipping their individual RGBA values by +-1 to represent a bit value. This adjustment is minimal enough that it would be invisible to the naked eye, yet large enough for it to be usable.

Decoding

A message might be decoded in the same way; the RGBA values for an image can easily be transcribed into their binary encoding. Start/stop sequences can be used to signal the beginning/end of the encoded message. From there, it is as easy as parsing the binary into UTF-8.

Code Sample

To be added...

Usage and Implications

This is a simple idea that has undoubtedly been documented and reproduced before. However, it's still fun! Use it for passing hidden messages, or combine it with an encryption algorithm for even more secrecy.

It's worth noting that conversion from a PNG to a JPEG instantly wipes all encoded data because of the JPEG's compression algorithm fuzzily rewriting RGBA values (however minimally). I like the idea of using this to instantly wipe your secrets by simply pressing "compress."