English (UK)
Français (France)
Brezhoneg (Breizh)

Stéganographie

September 27 2010

La stéganographie est le nom que porte la technique qui consiste à dissimuler des informations dans une autre information.

Bien que l'on puisse cacher diverses informations dans des supports multiples, nous verrons ici comment il est possible de cacher une image, dans une autre image.

Tout d'abord, il faut prendre en considération qu'une image peut être perçue comme un assemblage de pixels ayant tous une couleur codée en RVB. (Nous omettrons ici le canal alpha de certains formats)
À chaque composante est associé un nombre de bits. Plus ce nombre de bits est important, plus l'on augmente le nombre de nuances d'une couleur. Dans le cas contraire, moins il y a de bits et plus la couleur est approximative.
Ainsi, tronquer le nombre de bits décrivant une couleur, revient donc à l'approximer, on perd en information et en qualité, mais l'essentiel de l'information est conservé.

Prenons le cas d'une couleur codée sur 8 bits.
Les 4 bits de poids fort donnent la majeure partie de l'information, les 4 bits de poids faible, apportent une nuance.

Si la quantité de rouge d'un pixel est codée par :
[xxxx xxxx] (x, représentant des 0 ou des 1)
On peut l'approximer par :
[xxxx 0000]

La quantité de rouge est proche de l'ancienne, l'essentiel de l'information est conservé, la nuance est perdue.

On peut éffectuer la même opération pour chaque composante RVB, et obtenir un pixel semblable au pixel initial.
R : [xxxx 0000]
V : [xxxx 0000]
B : [xxxx 0000]

Prenons maintenant un pixel d'une autre image, et procédons à l'identique. On a :
R : [yyyy 0000]
V : [yyyy 0000]
B : [yyyy 0000]

On ne peut pas dans l'état superposer l'information des 2 pixels, sans perdre l'information fournie par le pixel ecrasé.
Réécrivons donc les composantes de notre second pixel ainsi :
R : [0000 yyyy]
V : [0000 yyyy]
B : [0000 yyyy]

(Cela se réalise par un simple décalage des bits)

Composons les 2 pixels, on obtient :
R : [xxxx yyyy]
V : [xxxx yyyy]
B : [xxxx yyyy]

L'essentiel de l'information est apportée par les 4 bits de poids fort (x). Les bits de poids faible (y) apportent une nuance, et ne participent que très peu dans la couleur finale du pixel.
(Il s'agit d'un OU binaire entre les composantes des 2 pixels)

Si l'on réalise cette opération sur l'ensemble des pixels de chacune des deux images, on cache l'image aux pixels y, derrière l'image au pixels x.

Si le choix des 2 images est bien fait, l'image résultante peut ne pas laisser présumer de son contenu.

Attention : ce principe est mis à mal si l'image résultante subit une compression.

Ci-dessous une proposition de code PHP permettant de cacher une image dans ue autre, puis de la révéler à nouveau
Ce fichier est disponible ICI
Ou en HTML.

Code : PHP
"
<?php

/** ************************************************** **/
/*** Proposition d'un scrit PHP sur la stéganographie ***/
/** ************************************************** **/

/* On suppose que l'image qui cache, et l'image cachée ont les mêmes dimensions */

/** Cacher une image dans une autre **/

/* Récupérer le type mime d'une image */
function getMIMEType($picPath) {
if(function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME);
$mime = finfo_file($finfo, $picPath);
finfo_close($finfo);
return $mime;
}
elseif ( function_exists('mime_content_type')) {
return mime_content_type($picPath);
}
else{
/* Sinon, on utilise une fonction système */
$picPath = str_replace(' ', ' ', $picPath);
return system("file -i $picPath");
}
}

/* Récupérer la taille de l'image */
function picHeight($res) {
return imagesy($res);
}

function picWidth($res) {
return imagesx($res);
}

/* Créer une resource à partir d'une image */
function fromPicture($path) {

$mime = getMIMEType($path);

if( strpos($mime, 'image/jpeg') !== false) {
return fromJPEG($path);
}
elseif( strpos($mime, 'image/png') !== false ) {
return fromPNG($path);
}
elseif( strpos($mime, 'image/gif') !== false ) {
return fromGIF($path);
}

return false;
}

function fromPNG($path) {
return @imagecreatefrompng($path);
}

function fromJPG($path) {
return @imagecreatefromjpeg($path);
}

function fromJPEG($path) {
return @imagecreatefromjpeg($path);
}

function fromGIF($path) {
return @imagecreatefromgif($path);
}

/* Créer une resource "true color" */
function trueColor($width, $height) {
return imagecreatetruecolor($width, $height);
}

/* Cacher l'image dans une autre */
function steganoHide($picFinal, $picToDisplay, $picToHide) {
/* On parcoure chaque pixel avec 2 boucles imbriquées */
for($x=0; $x< picWidth($picFinal); $x++) {
for($y=0; $y< picHeight($picFinal); $y++) {

/* Extraction des bits "visibles" */
$dispColor = deleteColorLowBits(getPixelColor($picToDisplay, $x, $y));

/* Extraction des bits "cachés" */
$hideColor = deleteColorHighBits(getPixelColor($picToHide, $x, $y));

/* Superposition des bits */
$color = joinColorBits($dispColor, $hideColor);

/* On ajoute le pixel à la nouvelle image */
$color = imagecolorallocate($picFinal, (($color >> 16) & 0xFF), (($color >> 8 ) & 0xFF), ($color & 0xFF));
imagesetpixel($picFinal, $x, $y, $color);

}
}

return $picFinal;
}

/* Récupérer l'image cachée */
function steganoDisplay($resFinal, $resHiddenPic) {
for($x=0; $x< picWidth($resFinal); $x++) {
for($y=0; $y< picHeight($resFinal); $y++) {
/* On supprime les bits de poids fort, c'est une autre manière de réaliser la fonction deleteColorHighBits */
$color = (getPixelColor($resHiddenPic, $x, $y) << 4);
$color = imagecolorallocate($resFinal, (($color >> 16) & 0xFF), (($color >> 8 ) & 0xFF), ($color & 0xFF));
imagesetpixel($resFinal, $x, $y, $color);
}
}

return $resFinal;
}

/* Enregistrement de l'image (vérifiez les droits d'accès...) au format PNG
0 <--- qualité --- compression ---> 9 */
function recordPNGPic($picRes, $path, $quality = 0, $filters = PNG_NO_FILTER ) {
imagepng($picRes, $path, $quality, $filters);
}

/* On récupère la couleur d'un pixel */
function getPixelColor($res, $x, $y) {
return imagecolorat($res, $x, $y);
}

/* On applique un filtre pour supprimer les bits de poids faible */
function deleteColorLowBits($intColor) {
return ($intColor & 0xF0F0F0);
}

/* On applique un filtre pour supprimer les bits de poids fort */
function deleteColorHighBits($intColor) {
return ($intColor & 0x0F0F0F);
}

/* On superpose les 2 couleurs */
function joinColorBits($intColor1, $intColor2) {
return ($intColor1 | $intColor2);
}

/* Créer une image PNG cachant une autre image et l'enregistrer sur le disque */
function generatePNGSteganoPicture($imToHide, $imToDisp, $pathToRecord) {
$resPicToHide = fromPicture($imToHide);
$resPicToDisplay = fromPicture($imToDisp);
$resPicFinal = trueColor(picWidth($resPicToDisplay), picHeight($resPicToDisplay));

recordPNGPic( steganoHide($resPicFinal, $resPicToDisplay, $resPicToHide) , $pathToRecord);
}

/* Créer une image PNG semblable à celle qui a été cachée, et l'enregistrer sur le disque */
function generatePNGSteganoPictureReverse($steganoPicture, $pathToRecord) {
$resSteganoPicture = fromPicture($steganoPicture);
$resPicFinal = trueColor(picWidth($resSteganoPicture), picHeight($resSteganoPicture));

recordPNGPic( steganoDisplay($resSteganoPicture, $resSteganoPicture) , $pathToRecord);
}

/** Cacher une image **/
/* L'image à cacher */
$imToHide = 'imageACacher.png';

/* L'image qui cachera l'image à cacher */
$imToDisp = 'iMageQuiCachera.jpg';

/* Le nom de l'image finale */
$steganoRecordPath ='stegano.png';

/** ************************************************** **/
/** ****** On lance donc la création de l'image ****** **/
generatePNGSteganoPicture($imToHide, $imToDisp, $steganoRecordPath);
/** ************************************************** **/
/** ************************************************** **/


/** Récupérer une image cachée **/
/* L'image contenant une autre image */
$steganoPicture = 'stegano.png';

/* L'image qui a été cachée et qui est maintenant découverte */
$imUnhide = 'steganoReverse.png';

/** ************************************************** **/
/** ****** On lance donc la création de l'image ****** **/
generatePNGSteganoPictureReverse($steganoPicture, $imUnhide);
/** ************************************************** **/
/** ************************************************** **/

?>
"

avatar

Hiryu


Hiryu

2010-09-27 02:36:03

Cette portion de code est une adaptation simplifiée du contenu d'une de mes classes PHP.

Il est ici proposé un cas général aisément modifiable.

Idée d'amélioration :
Ne modifier que le dernier bit, voire, que le dernier bit d'une seule des composantes RVB. Cela préserverait une grande qualité de l'image tout en permettant l'insertion d'une image bichromatique (ex : texte ou signature).

Hiryu

2010-10-04 14:42:48

Toute image peut cacher du texte ou une autre image par cette méthode. Le choix de l'image ou des images est déterminant, car le contenu caché peut apparaître, si la concordance des couleurs n'est pas idéale.

Agrandir l'image est souvent une manière de déceler si elle cache quelque chose.

Hiryu

2010-12-09 20:37:32

La coloration syntaxique semble ne pas être totalement au point encore. Je vous invite donc à utiliser le fichier mis en lien, ou directement l'implémentation initiale en vous rendant sur le module une fois connecté.

Hiryu

2010-12-20 00:12:07

Coloration syntaxique prise à nouveau en compte.

Hiryu

2010-12-20 00:19:12

Bien que l'on m'ait fait part du contraire, (le script ci-dessus, qui peut-être directement téléchargé via un lien dans l'actualité) est fonctionnel (testé à nouveau à l'instant).

Si vous rencontrez des problèmes dans son utilisation, n'hésitez pas à le signaler.