Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

stough 202-

Preview: Bit-slicing

A quick bit-slicing demo to show that the low-order bits often show little relevant information, while the high-order bits show more relevant information: most of what you perceive in the image is encoded in the high-order bits. We’ll demonstrate this with an example of steganography, where we hide a secret message within a public message. If you know where to look you can tease out the hidden message from the public one.

<Figure size 800x300 with 2 Axes>
((1080, 1920), dtype('uint8'), np.uint8(0), np.uint8(255))
((1080, 1920), dtype('uint8'), np.uint8(0), np.uint8(1))

Our image I is 8-bit grayscale, and the message Im is binary: just 0s and 1s, the same size as I.

 

Bit Planes

Each 8-bit pixel value is a sum of powers of two:

r=b727+b626+⋯+b121+b020,bk∈{0,1}r = b_7 2^7 + b_6 2^6 + \cdots + b_1 2^1 + b_0 2^0, \qquad b_k \in \{0,1\}

The image made of just the kkth bit of every pixel is called the kkth bit plane. The most significant bit b7b_7 decides whether a pixel is in the top or bottom half of the intensity range, a swing of 128 levels, while the least significant bit b0b_0 moves the value by just 1. We can pull out any plane with a bitwise and against a mask that has only that bit set: I & 0b00001000 (that is, I & 0x08) keeps only b3b_3. Let’s look at all eight.

<Figure size 1000x350 with 8 Axes>

The top couple of planes look like crude but recognizable versions of the scene: the high-order bits hold most of what we perceive. By bit 4 the structure is mostly gone, and from bit 3 down the planes look like random noise. Natural images have noise and fine texture at the level of a few gray levels, so their low-order bits are essentially random. (The exceptions are the darkest regions, like the road and the tree trunk at left, which stay solid black through several planes.)

That makes the low-order bits a great place to hide something. Changing b0b_0 changes a pixel by at most 1 out of 255, far too little to see, and the plane we’d be overwriting looks like noise anyway. The next cell shows plane 3 by itself, as one example.

<Figure size 600x300 with 1 Axes>

 

Hiding a Message

A bitwise or with the message, I | Im, sets bit 0 to 1 wherever the message is 1 and leaves the pixel alone wherever the message is 0. Since Im only has values 0 and 1, it can only ever touch bit 0.

<Figure size 600x300 with 1 Axes>
<Figure size 600x300 with 1 Axes>

Pulling out bit 0 of J reveals the message. But look closely: the white parts of the face are clean, while the black strokes (the eyes, mouth and outline) are filled with the same noise as the rest of bit 0. That’s because or can set a bit to 1 but never clear it to 0. Wherever the message is 0, bit 0 still holds the image’s original, random low bit.

To embed the message exactly, first clear bit 0 everywhere with I & 0xFE (binary 11111110), then or in the message:

Largest change to any pixel: 1
<Figure size 900x250 with 2 Axes>

Now bit 0 is the message, and no pixel of the image changed by more than one gray level. You can view the two side by side below and convince yourself you can’t tell which one carries the secret. This is the simplest form of least-significant-bit steganography. It’s fragile, though: any processing that perturbs the low bits, such as lossy JPEG compression, slight brightening, or resizing, destroys the message.

<Figure size 800x300 with 2 Axes>