[justify]The current articles on the website have talked about how to hide data inside images via either appending bytes after an image and by manipulating pixels. In this article, we’re going to go a bit deeper in the second technique by explaining a few algorithms used to hide data.[/justify]
Knowledge that will help you to read this article:
We’ll consider the following:
Brief explanation of image architecture
[justify]Here is what you should keep in mind: an image file is not more than a huge list of pixels that should be displayed on your screen.
In a 24bpp BMP file, we can distinguish two main parts; a header that’s describing the file and a body that’s including all the pixel information.
A pixel is a composition of 3 colors, red, green and blue that are encoded in 1 byte per color (so 3 for a pixel). A byte can take value from 0 to 255 and it’s those byte we will alter to hide our data.[/justify]
Methods of hiding data
Method 1: Encode raw bytes
[justify]This method consists of encoding each value as they are inside our image.
Let’s say we want to hide the word “Hidden” inside an image. That’s 6 characters to hide (so 6 bytes). Since we can hide 3 bytes per pixels, that’s means we can hide our data in 2 pixels.
Let’s create an image with a width of 2 and height of 1.
We want to put our data so we can hide “Hid” in the first pixel and “den” in the second one:
Let’s transform our data into its hexadecimal representation.
We’re now able to affect value to our pixels:
Pixel 1: Red = 48 | Green = 69 | Blue = 64
Pixel 2: Red = 64 | Green = 65 | Blue = 6E
Congrats we’ve hidden our first data in an image!
[justify]Because of the cons we need to find a more reliable way to hide data without telling to everyone “Hey! Look at my pixelated image, there is something here”. If we cannot hide our data directly, why not just hide the minimum of information per pixels? As we said before, a pixel is 3 bytes that does take the value from 0 to 255. As a human, we’re not able to make the difference between a red with the value of 255 and the value of 254. That’s what we’re going to exploit to hide our data.[/justify]
Method 2: Least significant bit encoding
We’re going to hide our data by using only the least significant bits of a pixel. That means we can use up to 3 bits per pixels. Let’s take the word “Hidden” again and hide it with this method. We still have 6 bytes to hides but since one byte contains 8 bits and we cannot store more than 3 bits per pixels we’ll need a bigger image if we want be able to hide this data.
Let’s calculate the space we’ll need this time:
6bytes * 8bits = 48 bits to hide.
48 bits / 3bits per pixel = 16 pixels.
That’s 800% more pixels than the first method!
Let’s then make an image with a width of 4 and an height of 4 and add a white background. Now, let’s transit our data into 8 bits padded binary:
We need to encode this data into each pixel we have. Since we have a white image, all the pixels have the hex value of FF FF FF that’s 11111111 11111111 11111111 in binary. To hide the letter ‘H’, we’ll need approximately 3 pixels. So let’s change the pixels composite by the following:
Repeat this for each byte in our data and we’re now having a data hidden that is invisible to human eyes!
[justify]Now we’ve find a way to make our change invisible, the truth is the human eye is even less perfect than that and we’re going to optimized our method with this.
It has been proved that the human eyes can only percept about 10 millions of colors.
Thus said, we can calculate how many bits we are able to change to keep our changes invisible.
We’ll be quick and admit we can modify at most 5 bits. Since our eyes are more sensitive to contrast change on the green channel, we’ll only modify 1bit from here and 2 from the others.
Here come the third method[/justify]
Method 3: least significant bit encoding (optimized)
[justify]So far, this method is the same as the second one, but instead of storing only 3 bit per pixel, we’ll store 5. That’s ensuring ~ 1.7 pixels per character.
In our case and the hiding of the word “Hidden” it will be still +480% spaces occupied than the first method but much compressed than the second one.
Let’s see for example what will looks like the letter ‘H’ inside our white image.
H = 01001000 =>
Process the same for each character to hide and you’re done.
We got out way to hide data but now a few problems persist.
Here is some and how we can possibly fix them:
How to know the length of the data?
Let’s say we have a recalcitrant mailbox that’s filter any exe we’re sending even inside archive and it pissed us off so much that we’ve decided to hide it into a photo of our cat (because there is not enough over the net).
So far, for a text based data that’s fine, you’ll be able to see by yourself where it starts and where it’s end.
But for the case of an executable, that’s another story !
We must know how many data we need to extract or we’ll never get our exe to work.
Example: Write an Header
We can write an header with the seize of the data to recover. Let’s take a 32 bits integer so we can reserve little less than 7 pixels to write the length of our data.
If we want apply this to our word “Hidden” with the method 3, this will be:
Length(“Hidden”) = 6 = 110 = 00000000 00000000 00000000 00000110
Let’s just prefix this value to our image and we’ll have the length of the data to extract.
How to prevent an attacker from extracting our data?
Hard time here, we spend so much time encoding our data but they’re still so easy to read. No doubt someone suspicious fill find out what’s going on if he intercept our images. The first one will probably be to add offset to our data so we can hide them in a specific area of our image. This is not an acceptable solution as a global analysis will just break it. Instead, we’re going to generate a key that will contain all the pixel indexes where we can find our data. Let’s say we want to hide the word “Hide” into a 5w * 5h image. We need to generate a key of the position of 7 pixels.
=> 14, 8, 15, 1, 3, 9, 5
We’ll be hiding the 5 first bit of ‘h’ in the pixel n14 and the 3 other in the pixel n8 then the first two bits of ‘I’ in the pixel 8, 5 other in pixel n15 and the last one in n1 etc..
For better results, we’ll use a cryptographic random to generate the key. We even can improve this by setting the number of bits we’re hiding from 1 to 5 and make the output even less predictable but we need make sure our image can support the amount of data we’re going to store with the specified key.
Please note this is not a perfect solution to hide data and it shouldn’t be used as it is for real critical information.