Settings

There are three places to hide something in a picture, and they are not equally likely. Most of what is actually found is an archive stuck on after the image's end marker, which needs no cleverness at all and takes a second to spot. Next is the container's own fields — comment blocks, text chunks, EXIF. Last, and rarest, is the one everybody means by the word: data written into the low bit of every pixel. STEGOSCAN looks at all three in your browser, and the arithmetic for the third is the part worth understanding, because a clean result there is a much narrower statement than it sounds.

Start at the end of the file

Every image format has an end marker. A PNG ends with IEND, a JPEG with FFD9. Bytes after it are not part of the picture, every viewer ignores them, and a picture with a zip stuck on the end opens perfectly and is also a zip.

This is what most "hidden message in an image" turns out to be, because it requires nothing but a command that joins two files. It is also the easiest thing in the world to find: the file is longer than the format accounts for, and the first bytes of the extra usually name themselves. A test picture here came back as 148 bytes sit after the end of the image, starting at offset 98204. By its first bytes it looks like a ZIP archive.

Before any statistics, check three things: is the file longer than the format says, are there blocks the specification does not define, and is there readable text in a comment field. Two of the three are usually enough.

The low bit of every pixel, and why the obvious test misses

The real technique replaces the least significant bit of each colour value. Shifting a colour by one step in 256 is invisible, and every pixel carries a bit, so a modest photograph holds a few hundred kilobytes.

The classical detection is Westfeld and Pfitzmann's: writing random bits into the low bit drives the counts of each pair of values — 2i and 2i+1 — towards each other. Measure how far apart they still are, and a picture carrying a payload is measurably more balanced than one that is not.

Applied to the whole picture, that test fails on the commonest case, and the failure is quiet. Sequential embedding starts at the first pixel and stops when the message runs out. A picture of forty-eight thousand pixels carrying two kilobytes has been altered in its first eighth and is untouched after that, so a whole-image histogram is one part signal to seven parts noise. Here is the same picture measured both ways:

PictureWhole image saysGrowing prefix says
CleanAn ordinary pictureNo opening stretch reads as balanced
12% of capacity usedAn ordinary pictureBalanced to 17%, then collapse — message ends between 8% and 17%
35% of capacity usedOne channel of three, which means nothingBalanced to 42%, then collapse — ends between 33% and 42%
Every pixel usedBalanced in all three channelsStill balanced at full length — filling, not a message that stopped

The second row is the one that matters. Measured the usual way, a picture carrying a real payload is described in the same words as a clean one.

Scanning a growing prefix is what Westfeld and Pfitzmann actually described, and it gives you something better than a yes: the point where the statistic collapses is the point where the message stops, so you get its length. The answer is a range rather than a figure, because what the scan knows is which pair of checkpoints the end falls between, and inventing a single number is how an estimate turns into something somebody quotes in a report.

Four things it cannot see, and one of them is funny

  • A payload that is not random. The whole test rests on the hidden bits looking random, which they do when the payload has been encrypted or compressed — as any competent tool does it. Write plain ASCII straight into the low bits and the pair counts do not balance at all, and this reports an ordinary picture. It is better at catching careful tools than careless ones. The bit planes are what catch the careless ones: plain text in the low bits is often legible as shapes.
  • Embedding scattered from a key rather than filled from the first pixel. There is no opening run to find, and the payload is spread so thinly that no prefix stands out.
  • Anything in a JPEG. JPEG is lossy: a bit written into a pixel does not survive being saved. Hiding in a JPEG is done in the compression coefficients, which never touch a pixel value, and needs a different test entirely.
  • A payload under roughly a sixth of the capacity, which is below what two checkpoints can separate from chance on an ordinary picture. Lowering that threshold buys sensitivity by manufacturing false positives, and a confident "the first eight per cent carries a message" about a clean photograph is the worse error.

Look at the bit planes anyway

Drawing the low bit of each channel as a black-and-white image should give you television static. Anything that is not static is worth a second look: flat blocks where a payload ended, edges that follow the picture's own shapes, a patch of noise that is visibly different from the noise around it, or — more often than you would think — readable text.

This is the check that costs nothing and needs no arithmetic, and it catches the cases the statistics are blind to.

Why the free decoders want your picture

Search this and the working tools on the first page all begin the same way: upload the file. Which is a strange thing to be asked when the whole reason you are there is that you do not trust the picture, or do not own it, or were sent it by somebody you are trying to understand.

None of this needs a server. The end-of-file check is arithmetic on a length. The container walk is reading a structure. The statistics are a histogram and a chi-square, and the bit planes are a canvas. STEGOSCAN does all of it in the tab, and the page works with the network switched off once it has loaded, which is a thing you can check rather than a thing you are told.

What a result is worth

A finding is a reason to look further, not a conclusion. Values that sit in a narrow, evenly spread band read as balanced with nothing hidden in them at all, which is why three channels have to agree before the page says anything. And a clean result means the four specific things above were not found — not that the picture is carrying nothing.

If it matters, hash the file before you touch it: CHECKSUM takes a SHA-256 in the browser, and the first question asked of any examination is whether the copy examined is the copy received. Alongside: X-RAY reads what any file declares about itself, and PHANTOM is the other direction — hiding something, encrypted first, which is also what makes it detectable by the test above.

And when the answer matters more than a summary of it: HEX opens the file as bytes, so you can read the trailing data yourself rather than take a description of it, and IMAGEDIFF compares a suspect picture against an original if you have one — a map of every pixel that moved settles in a second what statistics can only make likely.

The same question for text, which has a better answer

Text is the easier case and almost nobody checks it. Characters exist that occupy no width and print as nothing: zero-width spaces, non-joiners, direction overrides. A string of them is a payload, and it survives copy and paste, which is what makes text watermarking work at all. Letters from other alphabets that look identical to Latin ones are the other half — a Cyrillic а in a domain name or a filename is invisible to a reader and a different string to a computer.

UNSEEN lists every character in a piece of text that you cannot see, which is a definite answer rather than a statistical one: either they are there or they are not. Worth running on anything you are about to republish, quote or forward, and on a filename or an address that looks right but behaves oddly. GLYPH is the other direction, tucking a short note into ordinary text, and PALIMPSEST does the same into an image's pixels — both are what the checks above are looking for.

One more measurement worth knowing about, for a file rather than a picture: ENTROPY plots how random each part of a file is rather than giving one figure for the whole. Encrypted or compressed data is flat and near-maximal, and a flat stretch sitting inside a file that should not contain one is a region worth opening — which is the same reasoning as the prefix scan above, applied to bytes instead of pixels.

Related tools