Steganography is the practice of concealing a message within another message or a physical object. In this blog post we want to hide a photo inside another photo. So when you share the photo, it looks like a single ordinary photo, but it's actually two photos. One of them is hidden inside of the other, and it can only be revealed using an external tool.

Mark sends photo to Julia

Using this method you can hide your signature in your artwork, or store multiple photos instead of one when you're very low on storage in tiny devices. In a similar approach, Pico-8 game cartridges are contained in an small PNG of the game itself1.

Photos as matrices

To be able to manipulate photos and embedding things inside them, we first need to know how they are represented and stored. One way to look at the photos is to see them as a mathematical matrix, where every cell of the matrix represents a pixel, and the value of it is the color of that pixel/cell.

Pm,n=(a1,1a1,2a1,na2,1a2,2a2,nam,1am,2am,n)\begin{equation*} P_{m,n} = \begin{pmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m,1} & a_{m,2} & \cdots & a_{m,n} \end{pmatrix} \end{equation*}

For example for a matrix like this:

P3,3=(1344702552550193470)\begin{equation*} P_{3,3} = \begin{pmatrix} 134 & 47 & 0 \\ 255 & 255 & 0 \\ 193 & 47 & 0 \end{pmatrix} \end{equation*}

We'll get a photo like this:

Mark sends photo to Julia

As you can see in the matrix and the photo, the closer the value is to 255 the whiter the pixel becomes. 0 means black, and anything between 0 and 255 describes a shade of gray.

Photos as matrices of bits

We've already presented our photos as a bunch of matrices. Let's push our nerdiness a little bit further and represent value of each cell using binaries, instead of decimals (I swear it will come in handy later). The same photo we've seen before will be represented using this matrix:

P3,3=(100001100010111100000000111111111111111100000000110000010010111100000000)\begin{equation*} P_{3,3} = \begin{pmatrix} 10000110 & 00101111 & 00000000 \\ 11111111 & 11111111 & 00000000 \\ 11000001 & 00101111 & 00000000 \end{pmatrix} \end{equation*}

Now let's find out what happens to the photo if we change the least significant bit (LSB, aka the rightmost bit). So I'm gonna swap all of the LSBs and see how the new matrix and the photo will look like:

P3,3=(100001110010111000000001111111101111111000000001110000000010111000000001)\begin{equation*} P_{3,3} = \begin{pmatrix} 10000111 & 00101110 & 00000001 \\ 11111110 & 11111110 & 00000001 \\ 11000000 & 00101110 & 00000001 \end{pmatrix} \end{equation*}
Mark sends photo to Julia

Can your eyes detect any changes in the result photo? I assume no, it surely looks like the previous photo (unless you've got supernatural eyes), because we've only changed a single bit with the least significance. With this basic trick we can embed a photo inside another photo without anyone noticing that something is a little bit off (pun intended).

Embed the other photo

For embedding the other photo, we need to replace every LSB of the main photo with every single bit of the hidden photo, which means the hidden photo has to be smaller than the main photo in size.

Mark sends photo to Julia

But how much smaller shall the hidden photo be? For a visible photo of size nmn * m there are nm8\frac{nm}{8} LSBs, which means we can have a hidden photo of the size nm8nm8\sqrt{\frac{nm}{8}} * \sqrt{\frac{nm}{8}} inside the visible photo. As an example, for a visible photo of the size 100010001000 * 1000 we have:

100010008=10000008=125000=353.553...\sqrt{\frac{1000 * 1000}{8}} = \sqrt{\frac{1000000}{8}} = \sqrt{125000} = 353.553...

That means we can hide a photo with the size of 353353353 * 353 inside of a host photo with size of 100010001000 * 1000.

RGB Photos

In colorful photos, every pixel of the photo is represented by three matrices. Red matrix, Green matrix, and the Blue matrix. Instead of 3 different matrices, you might also think of it as a single matrix with a tuple of 3 values, all ranging from 0 to 255.

P3,3=((12,20,133)(15,47,255)(255,255,0)(210,20,230)(21,14,133)(255,255,255)(80,50,90)(96,68,160)(140,0,130))\begin{equation*} P_{3,3} = \begin{pmatrix} (12, 20, 133) & (15, 47, 255) & (255, 255, 0) \\ (210, 20, 230) & (21, 14, 133) & (255, 255, 255) \\ (80, 50, 90) & (96, 68, 160) & (140, 0, 130) \end{pmatrix} \end{equation*}

Our approach to hide the photo still works in this case. You just need to replace all the LSBs for every matrix of every cell.

PNG, JPEG, and compressions

As you might already know, some photo formats (like JPEG) heavily compress the source image using different mathematical approaches (e.g. Haar wavelet). So you might ask what happens to the hidden photo in that case? Well, the hidden photo will most likely be destroyed and you'll see a bunch of noises after trying to reveal your secret. But there are other formats like PNG that preserve your photo as is, and you can try this trick using those formats.

Implementation

I've implemented this method long ago back when I was a bachelor student. You might want to check it out at github.com/avestura/PhotoFiremark. It's a desktop application, and uses Emgu CV to play with photo bits.

Read more

Footnotes

Footnotes

  1. Read Decoding Pico-8 cartridges for more information