Skip to content

Commit d2262b7

Browse files
committed
content(blogpost): steganography
1 parent 5830a27 commit d2262b7

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

public/posts/posts.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
[
2+
{
3+
"slug": "steganography-lsb-deep-dive",
4+
"title": "Steganography: Hiding Secrets in Plain Sight with LSB",
5+
"date": "2026-01-12",
6+
"updated": "2026-01-12",
7+
"description": "A technical deep dive into Least Significant Bit (LSB) steganography and how we implemented it for the Fezcodex Steganography Tool.",
8+
"tags": ["steganography", "lsb", "security", "image-processing", "canvas", "javascript", "dev"],
9+
"category": "dev",
10+
"filename": "steganography-lsb-deep-dive.txt",
11+
"authors": ["fezcode"],
12+
"image": "/images/defaults/sina-salehian-HqmTUJD73mM-unsplash.jpg"
13+
},
214
{
315
"slug": "pixel-art-resources-guide",
416
"title": "The Ultimate Pixel Art Resources Guide",
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
Steganography is the art and science of hiding information within other non-secret data. Unlike cryptography, which scrambles a message so it can't be read, steganography hides the very existence of the message.
2+
3+
In this deep dive, we'll explore the implementation of the **Steganography Tool** added to Fezcodex, focusing on the **Least Significant Bit (LSB)** technique.
4+
5+
## The Core Concept: Least Significant Bit (LSB)
6+
7+
Digital images are made up of pixels. In a standard 24-bit RGB image, each pixel has three color channels: Red, Green, and Blue. Each channel is represented by 8 bits (a value from 0 to 255).
8+
9+
Example of a pixel's color:
10+
- **Red:** 1011010**1** (181)
11+
- **Green:** 0110011**0** (102)
12+
- **Blue:** 1100101**1** (203)
13+
14+
The **Least Significant Bit** is the rightmost bit in these binary strings. If we change this single bit, the decimal value of the color channel only changes by 1. For example, changing the Red channel from `10110101` (181) to `10110100` (180) is a change so subtle that the human eye cannot detect it in a complex image.
15+
16+
By replacing the LSB of each color channel with a bit from our secret message, we can embed data directly into the image.
17+
18+
## The Protocol: FEZ Steganography
19+
20+
To make the extraction process reliable, we've implemented a simple protocol:
21+
22+
1. **Magic Header (`FEZ`):** The first 24 bits (3 bytes) of the hidden data always spell "FEZ". This allows the decoder to verify if an image actually contains a hidden message from our tool.
23+
2. **Length (32-bit):** The next 32 bits represent the length of the message in bytes. This tells the decoder exactly when to stop reading.
24+
3. **The Message:** The remaining bits are the actual UTF-8 encoded message.
25+
26+
### Tracing the Magic: Encoding "FEZ"
27+
28+
Let's look at how the magic header `FEZ` is scattered across the first few pixels.
29+
30+
**Step 1: Convert characters to binary**
31+
* **F** (70): `0 1 0 0 0 1 1 0`
32+
* **E** (69): `0 1 0 0 0 1 0 1`
33+
* **Z** (90): `0 1 0 1 1 0 1 0`
34+
35+
Combined Bitstream: `01000110` + `01000101` + `01011010` (24 bits total)
36+
37+
**Step 2: Embed into pixels**
38+
Since each pixel has 3 channels (R, G, B), we need 8 pixels to hide these 24 bits.
39+
40+
| Pixel | Channel | Original Byte | Bit to Hide | Modified Byte |
41+
| :--- | :--- | :--- | :--- | :--- |
42+
| **Pixel 1** | Red | `1011010`**`1`** | **0** (from F) | `1011010`**`0`** |
43+
| | Green | `0110011`**`0`** | **1** (from F) | `0110011`**`1`** |
44+
| | Blue | `1100101`**`1`** | **0** (from F) | `1100101`**`0`** |
45+
| **Pixel 2** | Red | `0101010`**`0`** | **0** (from F) | `0101010`**`0`** |
46+
| | Green | `1111001`**`1`** | **0** (from F) | `1111001`**`0`** |
47+
| | Blue | `0011001`**`1`** | **1** (from F) | `0011001`**`1`** |
48+
| **Pixel 3** | Red | `1010101`**`0`** | **1** (from F) | `1010101`**`1`** |
49+
| | Green | `1100110`**`1`** | **0** (from F) | `1100110`**`0`** |
50+
| | Blue | `0001111`**`0`** | **0** (from E) | `0001111`**`0`** |
51+
| **Pixel 4** | Red | `1011001`**`0`** | **1** (from E) | `1011001`**`1`** |
52+
| | Green | `0110110`**`1`** | **0** (from E) | `0110110`**`0`** |
53+
| | Blue | `1110001`**`1`** | **0** (from E) | `1110001`**`0`** |
54+
| **Pixel 5** | Red | `0101010`**`1`** | **0** (from E) | `0101010`**`0`** |
55+
| | Green | `1111001`**`0`** | **1** (from E) | `1111001`**`1`** |
56+
| | Blue | `0011001`**`1`** | **0** (from E) | `0011001`**`0`** |
57+
| **Pixel 6** | Red | `1010101`**`0`** | **1** (from E) | `1010101`**`1`** |
58+
| | Green | `1100110`**`1`** | **0** (from Z) | `1100110`**`0`** |
59+
| | Blue | `0101111`**`0`** | **1** (from Z) | `0101111`**`1`** |
60+
| **Pixel 7** | Red | `1011001`**`1`** | **0** (from Z) | `1011001`**`0`** |
61+
| | Green | `0110110`**`0`** | **1** (from Z) | `0110110`**`1`** |
62+
| | Blue | `1110001`**`1`** | **1** (from Z) | `1110001`**`1`** |
63+
| **Pixel 8** | Red | `0101010`**`1`** | **0** (from Z) | `0101010`**`0`** |
64+
| | Green | `1111001`**`0`** | **1** (from Z) | `1111001`**`1`** |
65+
| | Blue | `0011001`**`1`** | **0** (from Z) | `0011001`**`0`** |
66+
67+
By the time we reach Pixel 8, all 24 bits of "FEZ" are woven into the image. If you open this in a hex editor, you might see that the color `181` became `180`, but the text "FEZ" is nowhere to be found in the raw bytes!
68+
69+
### Why PNG and not JPEG?
70+
71+
Our tool works best with **PNG** files. Why?
72+
- **PNG (Portable Network Graphics)** is a *lossless* format. It preserves every single bit exactly as it was saved.
73+
- **JPEG (Joint Photographic Experts Group)** is a *lossy* format. It uses compression algorithms that slightly alter pixel values to reduce file size. These tiny changes are fine for human viewing, but they destroy the data we've hidden in the LSBs.
74+
75+
## The Implementation (JavaScript/Canvas)
76+
77+
We use the HTML5 `<canvas>` API to access and manipulate image data at the pixel level.
78+
79+
### Encoding Logic
80+
81+
```javascript
82+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
83+
const data = imageData.data; // Uint8ClampedArray [R, G, B, A, R, G, B, A, ...]
84+
85+
// ... transform message to bits ...
86+
87+
let bitIndex = 0;
88+
for (let i = 0; i < data.length && bitIndex < allBits.length; i += 4) {
89+
for (let j = 0; j < 3 && bitIndex < allBits.length; j++) {
90+
// Replace LSB of R, G, or B
91+
// (data[i + j] & 0xfe) clears the last bit
92+
// | allBits[bitIndex++] sets it to our secret bit
93+
data[i + j] = (data[i + j] & 0xfe) | allBits[bitIndex++];
94+
}
95+
}
96+
ctx.putImageData(imageData, 0, 0);
97+
```
98+
99+
### Decoding Logic
100+
101+
Decoding is the reverse process. We iterate through the pixels, extract the LSB of each R, G, and B channel, and rebuild the bitstream until we've parsed the header, the length, and finally the message content.
102+
103+
## Challenges and Limitations
104+
105+
- **Capacity:** The amount of data you can hide depends on the image resolution. Each pixel can hold 3 bits (1 for each RGB channel). A 1080p image (1920x1080) can theoretically hold about 777 KB of hidden data.
106+
- **Robustness:** LSB steganography is very fragile. Resizing, cropping, or re-saving the image as a JPEG will likely corrupt the hidden message.
107+
- **Security:** Pure LSB is "security through obscurity." Anyone who knows the technique can extract the message. For true security, you should encrypt the message *before* hiding it in the image.
108+
109+
## Try it out!
110+
111+
Check out the **Steganography Tool** in the Applications section and start sending your own cryptic signals through the digital aether.

0 commit comments

Comments
 (0)