
🔐 Stéganographie d'image en PHP : cacher du texte dans les pixels avec LSB
Cliquez, et votre encoded.png est visuellement identique à container.jpg. Personne ne soupçonnera qu’un message caché s’y trouve. La stéganographie ne rend pas les données illisibles comme le fait le chiffrement; elle dissimule le fait même de la transmission. Pour un développeur PHP, c’est une de ces techniques qu’il vaut la peine de comprendre au niveau des bits et des pixels.
La stéganographie LSB en PHP avec le module GD n’est pas un outil de production, mais un terrain d’apprentissage. Un terrain d’apprentissage qui vous apprend à manipuler la représentation binaire, les masques de bits et les formats sans perte mieux qu’une douzaine de tâches CRUD. Vous passerez de «comment une image est structurée en interne» à la ligne php decrypt.php qui extrait hello des pixels, et vous comprendrez pourquoi le JPEG n’est pas adapté à cet usage.
💡 Aperçu rapide:
- Nous décomposons la théorie: comment le bit de poids faible du canal bleu dissimule un message et pourquoi l’œil ne le voit pas
- Nous écrivons trois fichiers PHP:
functions.php(utilitaires),encrypt.php(encodage),decrypt.php(extraction) - Nous l’exécutons sur un vrai conteneur JPEG et obtenons un résultat PNG visuellement indiscernable
- Nous passons en revue les limites: compression, détectabilité, capacité et combinaison avec le chiffrement AES
- Nous examinons les bibliothèques PHP prêtes à l’emploi et décidons quand GD suffit et quand il est temps d’utiliser Python
Comment fonctionne la stéganographie LSB
LSB (Least Significant Bit, bit de poids faible) est une méthode où le message secret est écrit dans les bits de poids faible des pixels. Le nom est littéral: nous prenons le bit de poids faible d’un canal de couleur et le remplaçons par un bit du message.
Une image numérique est une grille de pixels. Chaque pixel est codé sur trois octets: rouge (R), vert (G) et bleu (B). La combinaison des trois valeurs de 0 à 255 détermine la couleur finale. Modifier le bit de poids faible fait varier la valeur du canal d’au plus 1; par exemple, RGB(100, 50, 200) devient RGB(100, 50, 201). L’œil humain ne peut pas distinguer la différence.
Prenons un pixel orange:
- Rouge:
11001001(201) - Vert:
11111000(248) - Bleu:
00000011(3)
Le bit de poids faible du canal bleu est un. Remplacez-le par un bit du message secret, et la couleur restera visuellement la même. En traitant les pixels séquentiellement, nous écrivons l’intégralité du message bit par bit.
Supposons que nous devions cacher la séquence 1101. Nous prenons 4 pixels et modifions le bit de poids faible du canal bleu:
Pixel | R | G | B d’origine | LSB remplacé | Nouveau B |
|---|---|---|---|---|---|
1 |
|
|
| → |
|
2 |
|
|
| → |
|
3 |
|
|
| → |
|
4 |
|
|
| → |
|
Le décodage est le processus inverse: nous collectons les bits de poids faible du canal bleu des quatre mêmes pixels et obtenons 1101. Pas de magie, juste des opérations bit à bit.
En pratique, vous n’êtes pas limité au seul canal bleu: vous pouvez utiliser les trois (R, G, B), ce qui triple le volume de données cachées. Dans ce tutoriel, nous travaillons uniquement avec le bleu par simplicité, afin de voir plus facilement ce qui se passe avec chaque bit.
Implémentation pas à pas en PHP
Vous aurez besoin de PHP avec le module GD, qui est préinstallé sur pratiquement tous les hébergeurs. Le code est écrit pour PHP 8.0+ et testé jusqu’à la version 8.4. Créez trois fichiers dans un même dossier.
Étape 1: Fonctions utilitaires, functions.php
Deux utilitaires: convertir une chaîne en représentation binaire et inversement.
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() convertit chaque caractère de la chaîne en une représentation sur 8 bits via decbin(ord(...)) et complète avec des zéros à gauche pour obtenir un octet complet. toString() fait l’inverse: elle découpe la chaîne binaire en blocs de 8 bits, convertit chaque bloc en nombre décimal et récupère le caractère via chr().
Le traitement bloc par bloc dans toString() n’est pas un caprice. L’approche répandue dans les tutoriels utilisant pack('H*', base_convert($binary, 2, 16)) échoue sur les messages de plus de 31 bits: la fonction base_convert opère sur des entiers, et lorsqu’elle dépasse PHP_INT_MAX, elle retourne n’importe quoi. Le découpage par octets résout le problème pour des messages de toute longueur.
Étape 2: Encodage, encrypt.php
Lit un conteneur JPEG, intègre le message dans les bits de poids faible du canal bleu et enregistre le résultat en PNG. Pourquoi pas en JPEG? Un format sans perte est obligatoire: la compression JPEG détruira les bits cachés, et le message se transformera en bruit.
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() obtient la couleur du pixel aux coordonnées (x, y). Les décalages de bits >>16 et >>8 extraient R, G, B. Le canal bleu est développé en une chaîne de 8 bits, le bit de poids faible (indice 7) est remplacé par le bit suivant du message, et la nouvelle couleur est écrite via imagesetpixel().
Le message est encodé en diagonale (y = x), ce qui facilite le suivi de la logique sur un petit exemple. Dans un projet réel, parcourez tous les pixels ligne par ligne et ajoutez un en-tête de 32 bits avec la longueur du message: sans cela, le décodeur ne sait pas où s’arrêter.
Étape 3: Décodage, decrypt.php
Extrait le message caché: parcourt les mêmes pixels et collecte les bits de poids faible du canal bleu.
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";
Ici, 40 est un nombre de bits codé en dur, exactement 5 octets pour le mot hello. Dans une version de production, remplacez ce nombre par la longueur issue de l’en-tête du message que vous avez écrite dans les 32 premiers bits lors de l’encodage.
Étape 4: Exécution et vérification
Placez container.jpg dans le dossier contenant les scripts. La procédure:
php encrypt.phpaffiche dans la console: «Encoded 40 bits, result in encoded.png».php decrypt.phpaffiche dans la console:hello.- Ouvrez
container.jpgetencoded.pngcôte à côte; il n’y a aucune différence. Ils sont visuellement indiscernables.
Vous pouvez comparer octet par octet avec ImageMagick: magick compare container.jpg encoded.png diff.png mettra en évidence les différences dans les bits de poids faible, pratiquement invisibles à l’œil. Mais le moyen le plus fiable de vérifier est d’exécuter decrypt.php et de lire le message.
Limites de la méthode
La stéganographie LSB est un outil d’apprentissage, pas une protection cryptographique. Voici ce que vous devez garder à l’esprit avant d’utiliser cette approche dans un projet réel:
La compression détruit les données. Le JPEG recalcule des blocs de 8×8 pixels avec quantification, et les bits de poids faible ne survivent pas. C’est pourquoi
encrypt.phpenregistre le résultat strictement en PNG (sans perte). Un seul passage par la compression JPEG, et les bits cachés se transforment en bruit. Formats alternatifs: BMP, TIFF sans compression.Détectabilité. Le remplissage LSB laisse des artefacts statistiques: l’histogramme du canal bleu change de manière prévisible. L’analyse RS et le test du χ² détectent un message caché en quelques secondes. Sans chiffrement, le LSB est de la dissimulation, pas de la protection: la présence d’un message peut être détectée même si le contenu ne peut pas être lu.
Volume de données. Chaque pixel fournit un bit dans le canal bleu. Une image de 1000×1000 pixels peut contenir jusqu’à 125 Ko de texte caché; c’est la limite mathématique découlant de la définition de la méthode LSB. En pratique, après compression PNG, le volume utile est plus faible, et une partie des bits est utilisée pour l’en-tête avec la longueur.
Ce n’est pas de la cryptographie. La stéganographie ne remplace pas le chiffrement; elle le complète. Le pipeline correct: chiffrez le message (AES-256), puis cachez le texte chiffré dans l’image. Ainsi, même la détection du canal caché ne révélera pas le contenu.
PHP GD est un outil basique. GD fournit un accès bas niveau aux pixels, et pour l’apprentissage, c’est plus que suffisant. Mais l’écosystème Python pour la stéganographie est nettement plus riche: la bibliothèque
stegano(LSB + chiffrement),Stegano(un ensemble de méthodes) etsteghide(C++, mais il existe des bindings Python). En PHP pur, il existe plusieurs bibliothèques:kzykhys/Steganography(une implémentation LSB minimale pour PNG),seba/steganography(une bibliothèque moderne avec manipulation LSB) etpateon/steganography(PHP 8.5+ avec support GD et Imagick). Mais il n’existe pas de solutions de niveau production équivalentes àsteghideen PHP à la date de 2026.
⁉️🤔 Questions fréquentes
Peut-on cacher du texte directement dans un JPEG?
Non. La compression JPEG divise l’image en blocs de 8×8 et applique une quantification; les bits de poids faible ne survivent pas. Pour cacher des données dans un JPEG, une approche différente est utilisée: les données sont cachées dans les coefficients de la transformée en cosinus discrète (DCT), pas dans les pixels. Algorithmes: F5, JSteg, OutGuess. Il n’existe pratiquement pas de bibliothèques PHP prêtes à l’emploi pour cela; il est plus simple d’utiliser Python.
Quelle est la différence entre le LSB et la stéganographie dans les métadonnées?
Le LSB modifie les données des pixels; le message est caché «à l’intérieur» de l’image. Les métadonnées (EXIF, XMP, chunks PNG tEXt) se trouvent dans les en-têtes de fichier et peuvent être lues par n’importe quel visualiseur de propriétés. Les métadonnées sont plus faciles à extraire, mais elles survivent à la compression JPEG, contrairement au LSB.
Comment puis-je vérifier que encoded.png contient effectivement un message caché?
Visuellement, vous ne le pouvez pas. Comparez les fichiers octet par octet:
magick compare container.jpg encoded.png diff.png(ImageMagick) montrera les différences dans les bits de poids faible. Elles sont pratiquement invisibles à l’œil, mais l’utilitaire mettra en évidence les pixels modifiés. Le moyen le plus fiable est d’exécuterphp decrypt.phpet de lire le message.
Quel est le niveau de sécurité global?
Sans chiffrement, ce n’est pas sécurisé. Le LSB sans chiffrement AES préalable est de la dissimulation, pas de la protection. Le fait de la transmission de données cachées est détecté par des tests statistiques en quelques secondes. La combinaison «chiffrement AES + dissimulation LSB» offre un niveau acceptable pour des projets personnels, mais pas pour des données sensibles.
Existe-t-il des bibliothèques PHP prêtes à l’emploi pour la stéganographie?
Oui, mais elles sont toutes éducatives ou légères.
kzykhys/Steganographyest une implémentation LSB minimale qui fonctionne uniquement avec le PNG.seba/steganographyest plus récente, avec une API propre pour encoder/décoder du texte dans des images.pateon/steganographynécessite PHP 8.5+ et prend en charge à la fois GD et Imagick. Pour des tâches sérieuses, tournez-vous vers Python:stegano(LSB + chiffrement) etStegano(un ensemble de méthodes, incluant un générateur de stégo-images).
La stéganographie en PHP: une base d’apprentissage ou un outil prêt à l’emploi?
La stéganographie LSB en PHP avec GD est un excellent terrain d’apprentissage pour comprendre comment les données sont cachées au niveau des pixels. Vous avez écrit un cycle d’encodage/décodage fonctionnel, compris les opérations bit à bit et constaté en pratique pourquoi une modification «invisible» laisse des traces dans les statistiques de l’image.
Pour une démonstration pédagogique, un projet personnel ou un défi CTF, cette boîte à outils est plus que suffisante. Pour une protection réelle des données, ajoutez un chiffrement avant la dissimulation et intéressez-vous aux bibliothèques Python, qui surpassent largement leurs homologues PHP en capacités et en résistance à la détection.
Si vous souhaitez approfondir, implémentez l’encodage dans les trois canaux de couleur simultanément et ajoutez un en-tête de 32 bits avec la longueur du message. Vous obtiendrez un mini-outil 3 fois plus efficace que la version de base et qui peut déjà valoir comme projet de cours. Publiez le code source sur GitHub pour que d’autres puissent apprendre de votre exemple.



