Stéganographie : Cacher une Image dans une Autre

Nous allons voir en théorie où et comment on peut cacher de l’information dans une image, puis mettre tout ceci en pratique en écrivant un programme Java. Cette application de la stéganographie est assez simple à réaliser et les résultats sont plutôt intéressants. Je vous invite tout d’abord à l’article Introduction à la Stéganographie.

Qu’est-ce qu’une image ? – Définitions

Une image est une représentation visuelle (ou mentale) de quelque chose.

En informatique, cette représentation est découpée en éléments carrés appelés pixels
(picture element). Chaque pixel est composé d’une seule couleur.

Votre écran possède un nombre fixe de pixels définit comme étant le produit du nombre de pixels en largeur par le nombre de pixels en hauteur : c’est la définition d’écran. Exemple : 1024 x 768.

On parle également de résolution d’écran mais attention il ne faut pas confondre avec la résolution d’une image ou la résolution d’impression. La « vraie » résolution est une mesure de la densité de pixels, c’est à dire un nombre de pixels par unité de longueur. Exemple : 300 PPI (Pixels Per Inch) ou PPP (Pixels Par Pouce) pour une image

Pour éviter toute confusion, je vais utiliser les définitions suivantes tout au long de l’article :

  • taille d’une image : espace mémoire qu’occupe un fichier image sur le disque dur, exprimée en octets. Exemple : 250 Ko
  • dimensions d’une image : espace visuel qu’occupe une image sur l’écran, représentées par un couple de valeurs longueur x largeur. L’unité utilisée ici sera le pixel. Exemple : 800 x 450

dimensions_image

Pour résumer, une image est composée de plusieurs pixels, chaque pixels représentant une couleur. Mais justement, comment sont définies les couleurs ?

Représentation des couleurs

On définit généralement une couleur par une combinaisons de couleurs primaires.
Il existe plusieurs façons de représenter les couleurs en fonction des couleurs primaires choisies : ce sont les modes colorimétriques.

Nous nous intéresserons ici au mode RGB (Red-Green-Blue) ou RVB (Rouge-Vert-Bleu). Ce mode attribue à chaque pixel une intensité de rouge, vert et bleu. Cette intensité est un nombre compris entre 0 (intensité nulle) et 255 (intensité maximum). Pour les curieux, on peut représenter de cette façon 2563 = 16 777 216 couleurs différentes !

couleurs RGB

Chaque intensité étant une valeur comprise entre 0 et 255, il faut donc 8 bits (1 octet) pour coder une intensité en binaire (28 = 256).

Exemple :

tomato

Où cacher l’information ?

Nous allons cacher notre image secrète à l’intérieur de chaque pixel de notre image originale. Chaque pixel de notre image originale sera légèrement modifié.

Comment réaliser ceci : en modifiant les bits de poids faible de chaque pixel.

pixelsDans cet exemple, les composantes R,G et B de ces deux couleurs diffèrent de +/- 2. Pourtant à l’œil nu on ne voit pas la différence.

Pour cacher un pixel B dans un pixel A, nous allons donc remplacer les bits de poids faible du pixel A par les bits de poids fort du pixel B.

Voici un exemple en utilisant 3 bits :

hide

Pour retrouver un pixel caché, nous allons faire l’opération inverse : on extrait les bits de poids faible du pixel reçu et on complète les bits restants avec des 0.

Exemple avec 3 bits :

reveal

Code Java

On utilisera 2 images de mêmes dimensions : une image de couverture (cover image) et celle que l’on veut cacher (hidden image). Nos images seront des objets BufferedImage. On parcourt tous les pixels et pour chaque pixel on effectue l’opération décrite précédemment.

On récupère la « valeur » RGB d’un pixel avec la méthode getRGB(int x, int y). La valeur retournée par cette méthode est de type int.

Exemple : le pixel situé à la position (50,100) est uniquement Rouge (R = 255, G=0, B=0)

On remarque que la valeur renvoyée par getRGB contient une composante A supplémentaire. C’est la composante Alpha qui définit l’opacité du pixel. On prendra toujours A = 255 (opaque).

Bon ben y’a plus qu’à ! Je vous donne le prototype de la fonction à coder :

public int hidePixels(int pixelA, int pixelB);

Nous allons utiliser les opérateurs binaires, petit rappel :

  • &    :   ET binaire, exemple : 6 & 5 = 4  (110 & 101 = 100)
  •  |     :   OU binaire, exemple : 6 | 5 = 7  (110 | 101 = 111)
  • >>  :  Décalage à droite, les bits sortants à droite sont perdus, on ajoute des 0 à gauche si le nombre est positif, sinon on rajoute des 1.
    Exemple : 6 >> 2 = 1  (110 >> 2 = 001)

Essayez de coder cette fonction sans regarder la solution, c’est le seul point intéressant de ce programme.On cachera les 3 bits de poids fort du pixel B dans le pixel A.

Solution :

public int hidePixel(int pixelA, int pixelB) {

int a, b, c;

a = pixelA & 0xFFF8F8F8;
b = pixelB & 0x00E0E0E0;
b = b >> 5;
c = a | b;

return c;
}

 

Explications :

On commence par ne conserver que les bits de poids fort du pixel A

a = pixelA & 0xFFF8F8F8;

 

Puis on extrait les 3 premiers bits de poids fort du pixel B et on les décale de 5 rangs vers la droite.


b = pixelB & 0x00E0E0E0;
b = b >> 5;

 

Enfin on fusionne nos deux résultats avec un OU binaire.

c = a | b;

 

Voilà pour la partie dissimulation. Je vous laisse réfléchir maintenant sur la manière de révéler un pixel caché (toujours avec les opérateurs binaires).

Solution :


public int revealPixel(int pixel){

int a;

a = pixel & 0xFF070707;
a = a << 5;
a = a | 0xFF000000;  //Remettre l'opacité à 255

}

Voici le code complet de ma classe Stegano :

import java.awt.image.BufferedImage;

public class Stegano {

private BufferedImage coverImage;
private BufferedImage hiddenImage;
private BufferedImage resultImage;
private int width, height;

//Constructor with 2 images (hiding process)
public Stegano(BufferedImage sourceImg, BufferedImage hiddenImg)
{
coverImage = sourceImg;
hiddenImage = hiddenImg;
width = sourceImg.getWidth();
height = sourceImg.getHeight();
resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
}

//Constructor with 1 image (revealing process)
public Stegano(BufferedImage sourceImg)
{
this(sourceImg, null);
}

// Hide the first 3 MSB of pixelB in pixelA
public int hidePixel(int pixelA, int pixelB)
{
int a, b, c;

a = pixelA & 0xFFF8F8F8;
b = pixelB & 0x00E0E0E0;
b = b >> 5;
c = a | b;

return c;
}

//Extract the last 3 LSB
public int revealPixel(int pixel)
{
int a;

a = pixel & 0xFF070707;
a = a << 5;
a = a | 0xFF000000; //Reset Alpha to 255

return a;
}

//Hide hiddenImage into coverImage
public void hide()
{
int pixelA, pixelB, pixelC;

for(int i=0; i{
for(int j=0; j {
pixelA = coverImage.getRGB(i, j);
pixelB = hiddenImage.getRGB(i, j);
pixelC = hidePixel(pixelA, pixelB);
resultImage.setRGB(i, j, pixelC);
}
}
}

//Reveal hidden image into resultImage
public void reveal()
{
int originalPixel, revealedPixel;

for(int i=0; i{
for(int j=0; j {
originalPixel = coverImage.getRGB(i, j);
revealedPixel = revealPixel(originalPixel);
resultImage.setRGB(i, j, revealedPixel);
}
}
}

//Return resultImage
public BufferedImage getResultImage()
{
return resultImage;
}
}

Sources – En savoir plus

Voir aussi:

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>