
🔐 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 |
|
|
| → |
|
2 |
|
|
| → |
|
3 |
|
|
| → |
|
4 |
|
|
| → |
|
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 3 function 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 20 function 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 3 require_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 12 for ($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 28 imagepng($img, 'encoded.png'); 29 30 echo "Закодировано {$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 3 require_once 'functions.php'; 4 5 $src = 'encoded.png'; 6 $img = imagecreatefrompng($src); 7 8 $bits = ''; 9 10 for ($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 19 echo 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.phprestituisce nella console: "Encoded 40 bits, result in encoded.png".php decrypt.phprestituisce nella console:hello.- Apri
container.jpgeencoded.pngfianco 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.phpsalva 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) esteghide(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) epateon/steganography(PHP 8.5+ con supporto GD e Imagick). Ma non ci sono soluzioni di livello production al livello disteghidein 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 è eseguirephp decrypt.phpe 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) eStegano(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.



