Série 5 – Stéganographie

Introduction

La stéganographie est l'art de cacher un message (au sens large) dans un autre, d'apparence anodine. Par exemple, l'image ci-dessous semble n'être rien d'autre qu'une photographie du kremlin de Pskov. En réalité, elle contient un texte caché, qu'il vous faudra extraire dans le cadre de cette série.

pskov.png

Figure 1 : Kremlin de Pskov

Cette image, comme toute photographie numérique, est composée d'un certain nombre de points, appelés pixels (pour picture elements), organisés sur une grille et dont on connaît la couleur. La couleur de chaque pixel est représentée par trois composantes — la rouge (R), la verte (G) et la bleue (B) —, chacune d'entre elles étant un entier de 8 bits compris entre 0 et 255. Ces composantes sont généralement empaquetées dans les 24 bits de poids faible d'un seul entier de 32 bits, les 8 bits de poids fort contenant la composante rouge, les 8 suivants la verte, et les 8 derniers la bleue.

Par exemple, l'image ci-dessus est composée de 337 lignes de 600 pixels chacune, soit un total de 202'200 pixels. Le pixel en haut à gauche de cette image a la couleur (101, 130, 158), un bleu foncé légèrement verdâtre : :-). Cette couleur peut être représentée par un unique entier de 24 bits, généralement noté en hexadécimal, et valant ici 65829E (car 65 est la représentation hexadécimale de 101, 82 celle de 130 et 9E celle de 158).

Cette encodage des couleurs permet d'en représenter plus de 16 millions (224) différentes. L'œil humain n'est pas capable de distinger chacune d'entre elles, et il est en particulier peu sensible à la composante bleue. Dès lors, il est possible d'appliquer de petites modifications arbitraires à la composante bleue des pixels d'une image sans qu'un observateur ne soit capable de le remarquer. Cette constatation est à la base de la technique de stéganographie utilisée ici.

L'idée est la suivante : un texte est une séquence de caractères, chacun d'entre eux pouvant être représenté par un entier de 16 bits. Pour cacher un texte dans une image, on peut donc stocker le premier bit (celui de poids fort) du premier caractère dans le bit de poids faible de la composante bleue du pixel aux coordonnées (0, 0) de l'image ; ce faisant, on modifie éventuellement cette composante d'une unité, ce qui est invisible à l'œil nu. De la même manière, le second bit du premier caractère peut être stocké dans le bit de poids faible de la composante bleue du second pixel ; et ainsi de suite.

L'image ci-dessus comporte 202'200 pixels, et chacun d'entre eux peut stocker un bit du texte à cacher. Comme chaque caractère occupe 16 bits, un total de 12'637 caractères peuvent être stockés dans cette image, sans que cela ne se voie !

La table ci-dessous illustre les modifications apportées aux couleurs des 16 premiers pixels de l'image plus haut, pour y stocker les 16 bits représentant le caractère F, à savoir 0000000001000110. Chacun de ces bits apparaît, du plus fort au plus faible, dans la première colonne. La seconde colonne donne les entiers 24 bits correspondant aux 16 premiers pixels de l'image originale, tandis que la troisième donne ceux correspondant à l'image modifiée pour que le bit de poids faible de la composante bleue soit celui de la première colonne. La couleur de fond de chacune des cases est celle du pixel, ce qui permet de constater que les modifications apportées sont invisibles à l'œil nu. Finalement, la dernière colonne donne la différence entre les deux couleurs.

Bit Avant Après \(\delta\)
0 65829e 65829e 0
0 65839f 65839e -1
0 65849f 65849e -1
0 65839f 65839e -1
0 64839f 64839e -1
0 64839f 64839e -1
0 64839f 64839e -1
0 64829e 64829e 0
0 64829d 64829c -1
1 63819d 63819d 0
0 63819c 63819c 0
0 63819c 63819c 0
0 62829b 62829a -1
1 62829c 62829d 1
1 62809b 62809b 0
0 617e98 617e98 0

Squelette

Pour démarrer, nous vous fournissons une archive Zip contenant :

  • un squelette de classe Steganographer, dont la méthode extract est à compléter dans le cadre de l'exercice 1, et la méthode insert à compléter dans le cadre de l'exercice 2,
  • une classe principale, Main, qui utilise la méthode extract pour afficher le texte contenu dans l'image ci-dessus, chargée via le réseau.

Exercice 1 : Extraction d'un texte

Complétez la méthode extract de la classe Steganographer, qui extrait le texte stocké dans une image selon la technique décrite plus haut. Le premier bit (de poids fort) du premier caractère se trouve dans le bit de poids faible de la composante bleue du pixel de coordonnées (0,0) ; le second bit de ce même caractère se trouve dans le bit de poids faible de la composante bleue du pixel de coordonnées (1,0) ; et ainsi de suite.

Pour obtenir la couleur (empaquetée) d'un pixel, utilisez la méthode getRGB de la classe BufferedImage.

Vérifiez que votre méthode fonctionne en exécutant le programme principal fourni, qui affiche le message extrait de l'image plus haut. Si le message affiché n'est pas compréhensible, nous vous conseillons d'afficher les 16 premiers bits extraits de l'image, qui correspondent au caractère F et valent 0000000001000110. Si vous les obtenez dans l'ordre inverse, cela signifie que vous n'avez pas tenu compte du fait que le premier bit, stocké dans le pixel (0,0), est le bit de poids fort du premier caractère.

Exercice 2 : Insertion d'un texte

Complétez la méthode insert de la classe Steganographer, qui insère un texte dans une image, selon la technique décrite plus haut. Si le texte est trop long pour être stocké en totalité, il est simplement tronqué ; et s'il est trop court, des caractères nuls (valant 0) sont insérés à la place de ceux manquant.

Une fois votre méthode terminée, testez-la en vérifiant que vous arrivez bien à re-extraire, au moyen de la méthode extract, un texte inséré au moyen de la méthode insert.

Notez que vous pouvez très facilement sauvegarder votre image modifiée si vous le désirez. Par exemple, l'extrait de code suivant enregistre, au format PNG, une image obtenue au moyen de la méthode insert dans un nouveau fichier nommé new_image.png. Une fois créé, ce fichier se trouvera dans le répertoire du projet correpondant à cet exercice.

BufferedImage newImage = Steganographer.insert(image, …);
ImageIO.write(newImage, "png", new File("new_image.png"));

Exercice 3 : améliorations possibles

Quelles améliorations possibles pourriez-vous imaginer faire à cette technique de stéganographie afin de stocker plus de données, ou alors de les stocker de manière plus sûre ?