Skip to content

Tutto per WordPress, lo sviluppo web — e non solo

🔐 Steganografia di immagini in PHP: nascondere testo nei pixel con LSB

🔐 Steganografia di immagini in PHP: nascondere testo nei pixel con LSB

Click, e il tuo encoded.png appare identico a container.jpg. Nessuno sospetterà che contenga un messaggio nascosto. La steganografia non rende i dati illeggibili come fa la crittografia; nasconde il fatto stesso che sia avvenuta una trasmissione. Per uno sviluppatore PHP, questa è una di quelle tecniche che vale la pena comprendere a livello di bit e pixel.

La steganografia LSB in PHP con il modulo GD non è uno strumento di produzione ma un terreno di addestramento. Un terreno di addestramento che insegna a lavorare con la rappresentazione binaria, le maschere di bit e i formati lossless meglio di una dozzina di attività CRUD. Passerai da "com'è strutturata un'immagine al suo interno" alla riga php decrypt.php che estrae hello dai pixel, e capirai perché JPEG non è adatto a questo scopo.

💡 Panoramica rapida:

  • Analizziamo la teoria: come il bit meno significativo del canale blu nasconde un messaggio e perché l'occhio non lo vede
  • Scriviamo tre file PHP: functions.php (utilità), encrypt.php (codifica), decrypt.php (estrazione)
  • Lo eseguiamo su un vero contenitore JPEG e otteniamo un risultato PNG visivamente indistinguibile
  • Esaminiamo i limiti: compressione, rilevabilità, capacità e la combinazione con la crittografia AES
  • Diamo un'occhiata alle librerie PHP già pronte e decidiamo quando GD è sufficiente e quando è il momento di usare Python

Come funziona la steganografia LSB

LSB (Least Significant Bit) è un metodo in cui il messaggio segreto viene scritto nei bit meno significativi dei pixel. Il nome è letterale: prendiamo il bit meno significativo di un canale di colore e lo sostituiamo con un bit del messaggio.

Un'immagine digitale è una griglia di pixel. Ogni pixel è codificato con tre byte: rosso (R), verde (G) e blu (B). La combinazione dei tre valori da 0 a 255 determina il colore finale. Cambiare il bit meno significativo sposta il valore del canale al massimo di 1; per esempio, RGB(100, 50, 200) diventa RGB(100, 50, 201). L'occhio umano non riesce a distinguere la differenza.

Prendiamo un pixel arancione:

  • Rosso: 11001001 (201)
  • Verde: 11111000 (248)
  • Blu: 00000011 (3)

Il bit meno significativo del canale blu è uno. Sostituiscilo con un bit del messaggio segreto e il colore rimarrà visivamente lo stesso. Elaborando i pixel in sequenza, scriviamo l'intero messaggio bit per bit.

Supponiamo di dover nascondere la sequenza 1101. Prendiamo 4 pixel e cambiamo il bit meno significativo del canale blu:

Pixel

R

G

B originale

LSB sostituito

Nuovo B

1

11001100

10010001

00101011

1

00101011

2

00011000

11110000

11111110

1

11111111

3

11100010

00100101

01010101

0

01010100

4

11111101

00001010

01000011

1

01000011

La decodifica è il processo inverso: raccogliamo i bit meno significativi del canale blu dagli stessi quattro pixel e otteniamo 1101. Nessuna magia, solo operazioni bit a bit.

In pratica, non sei limitato al solo canale blu: puoi usare tutti e tre (R, G, B), triplicando il volume di dati nascosti. In questo tutorial, lavoriamo solo con il blu per semplicità, così è più facile vedere cosa succede a ogni bit.

Implementazione passo passo in PHP

Avrai bisogno di PHP con il modulo GD, che è preinstallato su praticamente tutti i provider di hosting. Il codice è scritto per PHP 8.0+ e testato fino alla 8.4. Crea tre file in una cartella.

Passo 1: Funzioni helper, functions.php

Due utilità: convertire una stringa in rappresentazione binaria e viceversa.

1<?php
2
3function toBin(string $str): string
4{
5 $result = '';
6 $len = strlen($str);
7
8 while ($len--) {
9 $result = str_pad(
10 decbin(ord($str[$len])),
11 8,
12 '0',
13 STR_PAD_LEFT
14 ) . $result;
15 }
16
17 return $result;
18}
19
20function toString(string $binary): string
21{
22 $result = '';
23 foreach (str_split($binary, 8) as $byte) {
24 $result .= chr((int) base_convert($byte, 2, 10));
25 }
26
27 return $result;
28}

toBin() converte ogni carattere della stringa in una rappresentazione a 8 bit tramite decbin(ord(...)) e riempie con zeri a sinistra per formare un byte completo. toString() fa l'opposto: suddivide la stringa binaria in blocchi da 8 bit, converte ogni blocco in un numero decimale e ottiene il carattere tramite chr().

L'elaborazione a blocchi in toString() non è un capriccio. L'approccio diffuso nei tutorial che usa pack('H*', base_convert($binary, 2, 16)) si rompe su messaggi più lunghi di 31 bit: la funzione base_convert opera su interi e, quando supera PHP_INT_MAX, restituisce spazzatura. La suddivisione in byte risolve il problema per messaggi di qualsiasi lunghezza.

Passo 2: Codifica, encrypt.php

Legge un contenitore JPEG, incorpora il messaggio nei bit meno significativi del canale blu e salva il risultato come PNG. Perché non JPEG? Un formato lossless è obbligatorio: la compressione JPEG distruggerà i bit nascosti e il messaggio si trasformerà in rumore.

1<?php
2
3require_once 'functions.php';
4
5$message = 'hello';
6$binary = toBin($message);
7$msgLen = strlen($binary);
8
9$src = 'container.jpg';
10$img = imagecreatefromjpeg($src);
11
12for ($x = 0; $x < $msgLen; $x++) {
13 $y = $x;
14 $rgb = imagecolorat($img, $x, $y);
15
16 $r = ($rgb >> 16) & 0xFF;
17 $g = ($rgb >> 8) & 0xFF;
18 $b = $rgb & 0xFF;
19
20 $blueBits = str_pad(decbin($b), 8, '0', STR_PAD_LEFT);
21 $blueBits[7] = $binary[$x];
22 $newB = bindec($blueBits);
23
24 $newColor = imagecolorallocate($img, $r, $g, $newB);
25 imagesetpixel($img, $x, $y, $newColor);
26}
27
28imagepng($img, 'encoded.png');
29
30echo "Закодировано {$msgLen} бит — результат в encoded.png\n";

imagecolorat() ottiene il colore del pixel alle coordinate (x, y). Gli shift di bit >>16 e >>8 estraggono R, G, B. Il canale blu viene espanso in una stringa a 8 bit, il bit meno significativo (indice 7) viene sostituito con il bit successivo del messaggio e il nuovo colore viene scritto tramite imagesetpixel().

Il messaggio è codificato in diagonale (y = x), il che rende più facile seguire la logica in un piccolo esempio. In un progetto reale, itera attraverso tutti i pixel riga per riga e aggiungi un'intestazione a 32 bit con la lunghezza del messaggio: senza di essa, il decodificatore non sa dove fermarsi.

Passo 3: Decodifica, decrypt.php

Estrae il messaggio nascosto: percorre gli stessi pixel e raccoglie i bit meno significativi del canale blu.

1<?php
2
3require_once 'functions.php';
4
5$src = 'encoded.png';
6$img = imagecreatefrompng($src);
7
8$bits = '';
9
10for ($x = 0; $x < 40; $x++) {
11 $y = $x;
12 $rgb = imagecolorat($img, $x, $y);
13
14 $b = $rgb & 0xFF;
15 $blueBits = str_pad(decbin($b), 8, '0', STR_PAD_LEFT);
16 $bits .= $blueBits[7];
17}
18
19echo toString($bits) . "\n";

Qui 40 è un numero di bit cablato, esattamente 5 byte per la parola hello. In una versione di produzione, sostituisci questo numero con la lunghezza dall'intestazione del messaggio che hai scritto nei primi 32 bit durante la codifica.

Passo 4: Esecuzione e verifica

Posiziona container.jpg nella cartella con gli script. La procedura:

  • php encrypt.php restituisce nella console: "Encoded 40 bits, result in encoded.png".
  • php decrypt.php restituisce nella console: hello.
  • Apri container.jpg e encoded.png fianco a fianco; non c'è differenza. Sono visivamente indistinguibili.

Puoi confrontare byte per byte usando ImageMagick: magick compare container.jpg encoded.png diff.png evidenzierà le differenze nei bit meno significativi, praticamente invisibili all'occhio. Ma il modo più affidabile per verificare è eseguire decrypt.php e leggere il messaggio.

Limiti del metodo

La steganografia LSB è uno strumento didattico, non una protezione crittografica. Ecco cosa devi tenere a mente prima di usare questo approccio in un progetto reale:

  • La compressione distrugge i dati. JPEG ricalcola blocchi di pixel 8×8 con quantizzazione e i bit meno significativi non sopravvivono. Ecco perché encrypt.php salva il risultato rigorosamente come PNG (lossless). Un solo passaggio attraverso la compressione JPEG e i bit nascosti si trasformeranno in rumore. Formati alternativi: BMP, TIFF senza compressione.

  • Rilevabilità. Il riempimento LSB lascia artefatti statistici: l'istogramma del canale blu cambia in modo prevedibile. L'analisi RS e il test χ² rilevano un messaggio nascosto in pochi secondi. Senza crittografia, LSB è occultamento, non protezione: la presenza di un messaggio può essere rilevata anche se il contenuto non può essere letto.

  • Volume di dati. Ogni pixel cede un bit nel canale blu. Un'immagine di 1000×1000 pixel può contenere fino a 125 KB di testo nascosto; questo è il limite matematico derivante dalla definizione del metodo LSB. In pratica, dopo la compressione PNG, il volume utile è inferiore, più alcuni bit vanno all'intestazione con la lunghezza.

  • Non è crittografia. La steganografia non sostituisce la crittografia; la integra. La pipeline corretta: cifra il messaggio (AES-256), poi nascondi il testo cifrato nell'immagine. Così anche rilevare il canale nascosto non rivelerà il contenuto.

  • PHP GD è uno strumento di base. GD fornisce accesso a basso livello ai pixel e, per l'apprendimento, questo è più che sufficiente. Ma l'ecosistema Python per la steganografia è notevolmente più ricco: la libreria stegano (LSB + crittografia), Stegano (un insieme di metodi) e steghide (C++, ma ci sono binding Python). In PHP puro, esistono diverse librerie: kzykhys/Steganography (un'implementazione LSB minima per PNG), seba/steganography (una libreria moderna con manipolazione LSB) e pateon/steganography (PHP 8.5+ con supporto GD e Imagick). Ma non ci sono soluzioni di livello production al livello di steghide in PHP al 2026.

⁉️🤔 Domande frequenti

Si può nascondere testo direttamente in JPEG?

No. La compressione JPEG divide l'immagine in blocchi 8×8 e applica la quantizzazione; i bit meno significativi non sopravvivono. Per nascondere dati in JPEG, si usa un approccio diverso: i dati vengono nascosti nei coefficienti della trasformata discreta del coseno (DCT), non nei pixel. Algoritmi: F5, JSteg, OutGuess. Non esistono praticamente librerie PHP già pronte per questo; è più facile usare Python.

In cosa differisce LSB dalla steganografia nei metadati?

LSB modifica i dati dei pixel; il messaggio è nascosto "dentro" l'immagine. I metadati (EXIF, XMP, chunk tEXt del PNG) si trovano negli header del file e possono essere letti da qualsiasi visualizzatore di proprietà. I metadati sono più facili da estrarre, ma sopravvivono alla compressione JPEG, mentre LSB no.

Come posso verificare che encoded.png contenga effettivamente un messaggio nascosto?

Visivamente, non puoi. Confronta i file byte per byte: magick compare container.jpg encoded.png diff.png (ImageMagick) mostrerà le differenze nei bit meno significativi. Sono praticamente invisibili all'occhio, ma l'utility evidenzierà i pixel modificati. Il modo più affidabile è eseguire php decrypt.php e leggere il messaggio.

Quanto è sicuro tutto questo nel complesso?

Senza crittografia, non è sicuro. LSB senza previa cifratura AES è occultamento, non protezione. Il fatto della trasmissione di dati nascosti viene rilevato da test statistici in pochi secondi. La combinazione "crittografia AES + occultamento LSB" fornisce un livello accettabile per progetti personali, ma non per dati sensibili.

Esistono librerie PHP già pronte per la steganografia?

Sì, ma sono tutte didattiche o leggere. kzykhys/Steganography è un'implementazione LSB minima che funziona solo con PNG. seba/steganography è più recente, con un'API pulita per codificare/decodificare testo nelle immagini. pateon/steganography è per PHP 8.5+ e supporta sia GD che Imagick. Per compiti seri, guarda verso Python: stegano (LSB + crittografia) e Stegano (un insieme di metodi, incluso un generatore di stego-immagini).

Steganografia in PHP: una base di apprendimento o uno strumento già pronto?

La steganografia LSB in PHP con GD è un eccellente terreno di addestramento per capire come i dati vengono nascosti a livello di pixel. Hai scritto un ciclo di codifica/decodifica funzionante, hai compreso le operazioni bit a bit e hai visto in pratica perché una modifica "invisibile" lascia tracce nelle statistiche dell'immagine.

Per una dimostrazione didattica, un progetto personale o una sfida CTF, questo toolkit è più che sufficiente. Per una reale protezione dei dati, aggiungi la crittografia prima dell'occultamento e guarda alle librerie Python, che sono una spanna sopra le controparti PHP per capacità e resistenza al rilevamento.

Se vuoi approfondire, implementa la codifica in tutti e tre i canali di colore contemporaneamente e aggiungi un'intestazione a 32 bit con la lunghezza del messaggio. Otterrai un mini-strumento che è 3 volte più efficiente della versione base e si qualifica già come progetto per un corso. Pubblica il codice sorgente su GitHub così che altri possano imparare dal tuo esempio.