Stéganographie
CS-108 — Série 3
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 de la forteresse de Pskov. En réalité, elle contient un texte caché, qu'il vous faudra extraire dans le cadre de cette série.
![pskov.png](i/pskov.png)
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).
Cet 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 (en Java). 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 | δ |
---|---|---|---|
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 :
- l'image ci-dessus, dans le fichier nommé
pskov.png
, - un squelette de classe
Steganographer
, dont la méthodeextract
est à compléter dans le cadre de l'exercice 1, et la méthodeinsert
à compléter dans le cadre de l'exercice 2, - une classe principale,
Main
, qui utilise la méthodeextract
pour afficher le texte contenu dans l'image.
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 ?