Nano projet : Stéganographie - Enseignement de l`informatique à

Transcription

Nano projet : Stéganographie - Enseignement de l`informatique à
TD de programmation structurée : 1ère année
2012-2013
Nano projet : Stéganographie
Buts : Allocation dynamique, tableau multidimensionnel dynamique, entrées/sorties fichier,
Maitrise de l’environnement de travail
Durée : 2 séances et demi + travail personnel
Ce mini projet clôture le premier semestre. Il sera réalisé en binôme sur deux séances et demi. Il
nécessite un peu de travail personnel hors séance.
Introduction
La stéganographie [1] consiste à cacher un message secret dans un autre message support. Le
message secret doit être indétectable aux observateurs du support, mais doit pouvoir être retrouvé
par ceux qui connaissent son existence et savent décoder cette information. Ce TD s’intéresse à la
stéganographie, dite LSB (ou Least Significant Bit), qui permet de cacher un message secret (du
texte, une autre image, un programme exécutable, un fichier mp3, etc.) dans une image numérique.
Exemple : la photo 1 (Lena.pgm) est l’image d’origine, le message à cacher est un texte et la photo
2 (Tatoue.pgm) est l’image dans laquelle le message est caché. Visuellement, il n’y a pas de
différences entre les photos 1 et 2. Le message est invisible. Mais son existence est-elle
indétectable ?
Phelma est une école merveilleuse
Phelma est une école merveilleuse
Phelma est une école merveilleuse
Phelma est une école merveilleuse
Phelma est une école merveilleuse
Phelma est une école merveilleuse
Phelma est une école merveilleuse
Photo 1 : Lena.pgm
Message secret : ici, du texte
Photo 2 : Tatoue.pgm
Deux programmes complets seront réalisés, l’un capable d’encoder un message quelconque dans
une image en niveaux de gris, l’autre capable de décoder ce message.
Image numérique en niveaux de gris
Une image numérique en niveaux de gris est une fonction I(x,y) à support
fini (ses dimensions). Chaque élément du tableau est appelé pixel. Pour ce
TD, chaque pixel sera un scalaire sur 8 bits : le noir correspond à une valeur
nulle, le blanc à 255.
Pour stocker une image en mémoire, on utilise un tableau d’octets non signés
à 2 dimensions. Vous avez deux tableaux représentant les valeurs de l’image du
tangram sur un voisinage de 6x6 pixels dans le fond et sur un sommet de triangle.
Principe de la technique de stéganographie LSB
L’œil est peu sensible aux variations minimes de teintes d’une image
numérique. Toute image captée par un appareil photo comporte un aléa dû au
bruit d’acquisition qui n’est pas perceptible à l’œil s’il est suffisamment
faible. Si dans une image numérique on modifie arbitrairement la valeur d’un
ou plusieurs pixels par une valeur « suffisamment proche », par exemple en
passant d’une valeur n à (n+1) ou (n-1) ou (n+3) ou (n-3), on
obtient une nouvelle image qui est visuellement identique à la première. On dit que les bits de poids
faibles (least significant bits) des pixels contiennent peu d’information perceptible par l’homme.
La technique de stéganographique Least Significant Bit repose sur cette propriété : pour cacher un
message secret dans une image, on va modifier certains pixels en modifiant les n bits de poids
faible (n=1, 2, ou 4 bits par pixel pour simplifier).
Si on décide d’utiliser n=2 bits de poids faible par pixels pour cacher le message, alors pour cacher
un octet (8 bits), il faut le « disperser » sur 4 pixels de l’image.
Exemple. Supposons qu’on veuille cacher dans une image l’octet de valeur 203, soit 0xCB en
hexadécimal et 1100 1011 en binaire, en utilisant 2 bits par pixel. Il faut altérer 4 pixels dans
l’image, de telle sorte que les bits de poids faible de ces pixels soient remplacés respectivement en
11, 00, 10 et 11. On aura alors:
Octet à cacher 11001011, éclaté par paquet de 2 pixels
4 pixels de l’image initiale (binaire)
4 pixels de l’image initiale (décimal)
4 pixels de l’image modifiée (binaire)
4 pixels de l’image modifiée (décimal)
11
00
10
11
11001010 00100111 10111010 01001010
202
39
186
74
11001011 00100100 10111010 01001011
203
36
186
75
Pour décoder le message, il suffira d’extraire les deux bits de poids faible de chaque pixel puis de
les regrouper dans l’ordre sur un octet.
Structure des informations cachées
Pour décoder un message caché dans une image, il est nécessaire que quelques informations soient
cachées en plus du message lui même. Dans ce TD, pour cacher le contenu d’un fichier, on décide
de cacher les informations suivantes :
• Un entier T (4 octets, 32 bits à cacher) : le nombre d’octets du fichier à cacher.
• Le contenu du fichier à cacher, octet par octet (nombre d’octets spécifiés dans l’entier
précédent).
Pour cacher ces informations les unes après les autres dans les bits de poids faible des pixels de
l’image, on commence par modifier le pixel en haut à gauche, puis on procède de gauche à droite et
de haut en bas. Tous les pixels de l’image ne seront pas modifiés : on utilisera uniquement le
nombre de pixels suffisant pour la quantité d’information à cacher. Mais il faut aussi que le nombre
de pixels disponibles soit suffisant.
Exemple d’encodage par stéganographie LSB
Supposons qu’on veuille encoder le fichier secret message.txt, qui contient la chaine de caractère
ASCII « bonjour », dans une image initiale Lena.pgm, en utilisant n=2 bits de poids faible pour
cacher l’information.. On va cacher séquentiellement les informations suivantes:
• le nombre d’octets du fichier message.txt (nombre de caractères de "bonjour") : T=7
• Le contenu du fichier à cacher lui-même : "bonjour" (7 octets à cacher).
Au total, il s’agit donc de cacher 4 (T)+7=11 octets, dans l’image.
Puisque n=2 bits de poids faible sont utilisés par pixel, il faut altérer au total 11*4=44 pixels de
l’image pour cacher toute l’information.
Considérons le processus permettant de cacher la taille (int) du fichier « message.txt », soit 7 en
décimal, 0x7 en hexadécimal et 0000 0000 0000 0000 0000 0000 0000 0111 en binaire
sur 32 bits. Pour cacher ces 32 bits en utilisant n=2 bits de poids faible par pixel, on va utiliser
32/2=16 pixels. Il faut éclater la valeur binaire par paquet de deux bits (comme dans la 4ème ligne du
tableau suivant) et modifier les bits de poids faible de Lena.pgm.
Numéro de pixel
Valeur des pixels de l’image initiale
Lena.pgm (décimal)
Valeur des pixels de l’image initiale
Lena.pgm (hexadécimal)
Message à cacher (valeur voulue pour
les 2 bits de poids faible)
Valeur des pixels de l’image tatouée
Tatoue.pgm (hexadécimal)
Valeur des pixels de l’image tatouée
Tatoue.pgm (décimal)
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
162 162 162 161 162 156 163 160 164 160 161 159 155 162 159 154
A2 A2 A2 A1 A2 9C A3 A0 A4 A0 A1 9F 9B A2 9F 9A
00
00
00
00
00
00
00
00
00
00
00
00
00
00
01
11
A0 A0 A0 A0 A0 9C A0 A0 A4 A0 A0 9C 98 A0 9D 9B
160 160 160 160 160 156 160 160 164 160 160 156 152 160 157 155
Dans les octets suivants, on va cacher le contenu du fichier, soit « bonjour », 7 octets, sur 4*7=28
pixels. Considérons les 3 premiers caractères à cacher, ‘b’, ‘o’ et ‘n’ :
Caractère
‘b’
‘o’
‘n’
Valeur ASCII (décimal)
98
111
110
Valeur ASCII (hexa)
0x62
0x6F
0x6E
Valeur ASCII (binaire)
01 10 00 10
01 10 11 11
01 10 11 10
On obtiendra donc pour ces trois caractères (les 12 pixels numéro 16 à 27):
Numéro de pixel
16 17
Valeur des pixels de l’image initiale
157 155
Lena.pgm (décimal)
Valeur des pixels de l’image initiale
9D 9B
Lena.pgm (hexadécimal)
Message à cacher (valeur voulue
01 10
pour les 2 bits de poids faible)
Valeur des pixels de l’image tatouée
9D 9A
Tatoue.pgm (hexadécimal)
Valeur des pixels de l’image tatouée
157 154
Tatoue.pgm (décimal)
18
19
20
21
22
23
24
25
26
27
161 160 153 156 154 157 154 157 155 151
A1
A0
99
9C 9A
9D
9A
9D
9B
00
10
01
10
A0
A2
99
9E
97
11
11
01
10
11
10
9B
9F
99
9E
9B
96
160 162 153 158 155 159 153 158 155 150
Notez que, pour cacher le contenu du fichier, il suffit de lire son contenu en binaire octet par octet,
et de cacher ces octets au fur et à mesure.
Exemple de décodage d’un message stéganographique LSB
Le décodage d’un message caché dans une image avec la technique LSB est simple si on connait le
nombre de bits de poids faible utilisé ainsi que la structure des informations cachées. Dans notre
exemple, il suffit d’extraire de chaque pixel utilisé les 2 bits de poids faible, puis de les grouper par
octets (un octet étant réparti sur 4 pixels de l’image). On procèdera pas à pas en analysant les pixels
de gauche à droite et de haut en bas :
• Reconstitution de l’entier T encodant la taille du contenu caché (réparti sur 16 pixels)
• Reconstitution du contenu du fichier (réparti sur T*4 pixels), en décodant les T octets un à
un et en les réécrivant pas à pas dans un nouveau fichier, ouvert en binaire.
Estimation du nombre de plans de bits
Dans une image ne contenant pas d’information cachée, la différence entre les bits de poids faible
de 2 pixels voisins suit en général une répartition statistique uniforme. En « modifiant » le bit de
poids faible pour cacher un message, on rompt cette répartition statistique et on peut donc détecter
que le bit de poids faible a été utilisé pour cacher un message. Cette stratégie peut s’étendre au
2ieme bit, puis au 3ieme, etc… Mieux, on peut estimer la taille du message caché. Ainsi, ce qui est
imperceptible à l'Homme n'est pas nécessairement indétectable par un algorithme.
La méthode proposée par [2] peut être résumée de la manière suivante :
Soit u la valeur du pixel de coordonnées i,j et v celle du pixel voisin i, j+1. On utilise 4 compteurs
X, W, V et Z :
• Z est le nombre de couples u,v tels que u=v
• X est le nombre de couples u,v tels que (v est pair et u < v) ou (v est impair et u>v)
• Y est le nombre de couples u,v tels que (v est pair et u > v) ou (v est impair et u<v)
• W est le nombre de couples u,v de Y tels que |u-v|=1
• V = Y-W.
• P est le nombre total de couples et P = X+W+V+Z = X+Y+Z
Si un message est caché, sa longueur relative (proportion de bits modifiés) est donnée par la plus
petite racine de l’équation du second degré : (W+Z)/2 x2 + (2*X-P) x – X =0.
Pour le bit de poids faible, la longueur du message est donc racine*nbre_pixel_image/8 octets.
NB. Si le discriminant est nul, l'image ne suit pas les hypothèses statistiques que nous avons faites :
il ne s'agit pas d'une scène naturelle mais, par exemple, d'une image calculée par ordinateur). Si le
discriminant est négatif, l'image est alors réputée ne pas contenir d'information cachée.
Pour connaître le nombre de plans de bits qui a été utilisé, on applique la même procédure pour
chaque plan de bits. Pour tester le plan de bits p, on décale la valeur des pixels u et v vers la droite
de p bits et on applique la même méthode.
On dispose alors de 8 racines r(p) (1 pour chaque plan de bits). Les valeurs des racines sont très
proches de 0 quand il n’y a pas de message caché, et sensiblement identiques et non nulles sinon, au
moins pour les bits 0 à 4 (typiquement des racines de l’ordre de 0,01 à 0,04).
Pour détecter automatiquement le nombre p de bits utilisés, on peut choisir la valeur de p pour
laquelle la différence entre les 2 racines r(p) et r(p+1) est la plus forte, ou mieux choisir p tel que :
⎛
⎞
1 p −1

p = argmax⎜ r( p) − * ∑ r(k)⎟ avec r(k) : racine estimée .
p k =0
p =0..7 ⎝
⎠
Code et documents fournis (sur le site TD Info)
1/ Différents
€ fichiers images au format PGM, dans lesquels vous pourrez encoder un message caché
et différents fichiers images au format PGM dans lesquels un message est caché.
2/ Le module image (fichiers image.h et image.c) définit un type structuré IMAGE_T permettant de
stocker une image en mémoire :
typedef struct {
int nbl ; // nombre de ligne de l’image
int nbc ; // nombre de colonnes de l’image
unsigned char ** data ; // tableau bidim des pixels de l’image
} IMAGE_T ;
Le champ data de cette structure est un tableau d’octets à deux dimensions, alloué
dynamiquement. La zone de données sera contiguë en mémoire, de taille nbl*nbc.
3/ Le
module
pgm_file
(fichier
pgm_file.h
et
pgm_file.c)
contient
une
fonction
write_pgm_file()qui écrit une image dans un fichier au format PGM :
/* Ecrit l’image passée en paramètre dans un fichier au format PGM P5.
* PARAMETRES :
*
fileName : nom du fichier PGM à créer
*
p_img : pointeur vers l’image à sauver dans le fichier
*
comment : commentaire, optionnel. Ignoré si comment == NULL.
*
Sinon, sur la seconde ligne ‘#’ est écrit, suivi de "comment".
* RETOUR : 0 si tout se passe bien ; autre chose en cas d’erreur.
*/
int write_pgm_file(char *fileName, IMAGE_T * p_img, char * comment) ;
Les modules image et pgm_file devront être complétés et inclus dans votre projet.
4/ Le programme principal test_write.c (qu’il est inutile d’inclure dans votre projet) donne un
exemple d’utilisation de cette fonction (crée un fichier PGM contenant une image grise de 11x100
pixels). Remarque : ce programme ne peut pas être compilé tant que vous n’avez pas écrit les
fonctions d’allocation et de libération d’IMAGE_T (voir ci dessous).
5/ Pour tester le bon fonctionnement de vos programmes, nous vous fournissons enfin deux
programmes exécutables encode_stegano et decode_stegano équivalents à ceux que vous
devez réaliser dans le répertoire /users/prog1A/C/librairie/projet2
Travail à réaliser
Programmes à réaliser
Il vous est demandé de réaliser deux programmes.
• encode permet de cacher le contenu d’un fichier quelconque dans une image.
• decode permet d’extraire le message caché dans une image et recrée à l’identique le fichier
qui avait été caché.
Méthode de travail
Vous réaliserez ce travail par groupe de deux. Il vous est demandé d’apporter une attention
particulière à votre environnement de travail et à la propreté du code :
• Vous écrirez votre fichier Makefile. Ce Makefile sera doté a minima des cibles suivantes :
all (compilation de tous les programmes), clean (nettoyage du projet).
• Le code sera réparti dans plusieurs modules (fichiers .c et .h) [3]. Une répartition en modules
est proposée ci dessous.
• Vous réfléchirez au découpage en fonctions. Un découpage est proposé ci dessous ; vous
pourrez le compléter en ajoutant des fonctions dont vous avez besoin.
• Vous prendrez garde à gérer convenablement les cas d’erreur.
• Vous définirez et utiliserez des conventions de codage simples. S’inspirer de [4] [5].
• Le code sera proprement indenté et commenté [4] [5].
• Vous prendrez garde à compiler au fur et à mesure et vous testerez une à une vos fonctions
au fur et à mesure de leur écriture (pas tout à la fin).
Découpage en modules et fonctions On propose le découpage en modules suivant :
• Module image (fichiers image.h et image.c) : définition du type IMAGE_T, allocation et
libération mémoire d’une IMAGE_T.
• Module pgm_file (fichiers pgm_file.c et pgm_file.h) : lecture/écriture des fichiers image
PGM.
• Module encodage (fichiers encodage.h et encodage.c) : fonctions pour cacher un message
dans une image.
• Fichier encode_stegano.c : programme principal d’encodage.
• Module decodage (fichiers decodag.h et decodag.c) : fonctions pour extraire un message
caché dans une image.
• Fichier decode_stegano.c : programme principal de décodage.
• Module estimation (fichiers estimation.c et estimation.h) : estimation du nombre de bits
utilisés dans une image comprenant un message caché.
On précise ci-dessous le prototype de chacune des fonctions à écrire. Chacune des fonctions est
courte, typiquement 10 à 40 lignes de code.
Travail à réaliser en commun : modules image et pgm_file 1) Module image : Fonction allouant une image en mémoire /**
* Alloue une structure IMAGE_T pour une image de nbl*nbc pixels.
* La zone de donnée de l’image, dans le champ img, est contiguë en mémoire.
* La zone de donnée est initialisée à 0.
* PARAMETRES :
*
nbl : nombre de lignes de l’image à allouer
*
nbc : nombre de colonnes de l’image à allouer
* RETOUR : pointeur vers l’IMAGE_T allouée, ou NULL en cas d’erreur
*/
IMAGE_T * alloc_image(int nbl, int nbc) ;
2) Module image : Fonction libérant une image en mémoire /**
* Libère la mémoire préalablement allouée pour une image de nbl*nbc pixels.
* PARAMETRES : p_img : l’image à libérer
*/
void free_image(IMAGE_T * p_img) ;
3) Module pgm_file : Fonction de lecture de fichier image PGM /**
* Lit un fichier image en niveaux de gris au format PGM P5.
* et retourne l’image stockée dans le fichier.
* L’allocation mémoire est dynamique la zone allouée est contiguë
* Seul le format P5 est géré. De plus, la valeur maximale des pixels dans
* le fichier doit être 255 (<=> un octet) ou une erreur se produit.
* Le commentaire (ligne commençant par ‘#’ dans le fichier) est ignoré.
* PARAMETRES :
*
fileName : nom du fichier PGM à lire
* RETOUR : pointeur vers l’IMAGE_T lue, ou NULL en cas d’erreur
*/
IMAGE_T * read_pgm_file(char *fileName) ;
Travail pour la première personne du binôme : encodage Les fonctions gérant l’encodage d’un message seront écrites dans le module encodage (fichiers
encodage.h et encodage.c) et le main() du programme encode dans le fichier encode_stegano.c
1) Fonction cachant un octet dans une image à partir d’un pixel donné /**
* Cache l’octet "b" dans le tableau de pixels "pixdata" de taille totale "n",
* en commencant à la position "*p_pos".
* L’octet "b" sera caché dans les pixels
* pixdata[*p_pos], pixdata[*p_pos+1], pixdata[*p_pos+2], etc. selon le nombre
* de bits de poids faible bitParPixel utilisé dans chaque pixel.
* (*p_pos) est incrémenté pour référencer le prochain pixel disponible
* pour cacher des données dans le tableau tab.
* PARAMETRES :
*
pixdata : tableau de pixels dans lequel cacher l’octet "b".
*
n : taille du tableau "pixdata"
*
p_pos : (*p_pos) est l'indice de la case de "pixdata"
*
où commencer à cacher "b". (*p_pos) est incrémenté par la fonction.
*
b : l’octet à cacher, à raison de bitParPixel bits par pixel.
*
bitParPixel : nombre de bits de poids faible utilisés par pixel
* RETOUR : 0 en l’absence d’erreur, un nombre non nul en cas d’erreur.
*/
int hide_byte(unsigned char pixdata[], int n, int *p_pos,
unsigned char b, char bitParPixel) ;
2) Fonction cachant un fichier dans une image /**
* Cache le contenu du fichier de nom fileName dans une copie de l’image p_img
* à raison de bitParPixel bits de poids faible par pixels.
* Cette fonction commence par copier l’image, puis cache dans la copie
* la taille du fichier puis son contenu, octet par octet.
* PARAMETRES :
*
fileName : nom du fichier à cacher
*
p_img : l’image dans laquelle cacher le fichier.
*
bitParPixel : nombre de bits de poids faible utilisés par pixel
* RETOUR : l’image copiée contenant le message caché, ou NULL en cas d’erreur.
*/
IMAGE_T * hide_file(char *fileName, IMAGE_T * p_img, char bitParPixel) ;
3) Programme principal d’encodage Dans le fichier encode_stegano.c, écrire la fonction main() du programme encode.
Ce programme s’utilise en ligne de commande dans le Terminal de la façon suivante :
encode <imageIn.pgm> <fileToHide> <imageOut.pgm> <nbbitsparpixel>
avec :
• <imageIn.pgm>
: nom du fichier image à tatouer (le support)
: nom du fichier contenant les données à cacher (le message)
: nom du fichier image tatoué, qui est créé par le programme
•
: entier. Nombre de bits de poids faible utilisés pour cacher le
message. Vaut 1, 2, 4 ou 8.
La fiche [6] explique comment passer des paramètres à un programme depuis le terminal.
•
•
<fileToHide>
<imageOut.pgm>
<nbbitsparpixel>
Travail pour la seconde personne du binôme : décodage Les fonctions gérant l’extraction d’un message seront écrites dans le module décodage (fichiers
decodage.h et decodage.c) et le main() du programme decode dans le fichier decode_stegano.c
1) Fonction qui extrait un octet caché dans une image à partir d’un pixel donné /**
* Extrait un octet caché dans le tableau de pixels "pixdata",
* de taille totale n,
* en commencant à la position *p_pos.
* Retourne dans (*p_b) l’octet caché dans les pixels
* (*p_i, *p_j) , (*p_i, *p_j+1), (*p_i, *p_j+2), (*p_i, *p_j+3), etc.
* selon le nombre de bits bitParPixel cachés dans un pixel.
* (*p_pos) est incrémenté pour référencer le prochain pixel à analyser
* pour extraire des données.
* PARAMETRES :
*
pixdata : tableau de pixels dans lequel l'octet à extraire est caché.
*
n : taille du tableau pixdata
*
p_pos : (*p_pos) est l'indice de la case de "pixdata"
*
où commencer à extraire.
*
(*p_pos) est incrémenté par la fonction.
*
p_b : pointeur vers l’octet qui contiendra les données extraites.
*
bitParPixel : nombre de bits de poids faible utilisés par pixel.
* RETOUR : 0 en l’absence d’erreur, un nombre non nul en cas d’erreur.
*/
int extract_byte(unsigned char pixdata[], int n, int *p_pos,
unsigned char *p_b, char bitParPixel) ;
2) Fonction qui recrée un fichier caché dans une image /
* Extrait le fichier caché dans l’image p_img, à raison de bitParPixel
* bits cachés dans un pixel, et recrée le fichier qui contenait le message.
* Cette fonction commence par extraire de l’image le nom du fichier caché,
* puis recrée octet par octet ce fichier à l’identique en extrayant
* son contenu.
* PARAMETRES :
*
fileName : nom du fichier à recréer avec le contenu caché.
*
p_img : l’image dans laquelle est caché le fichier.
*
bitParPixel : nombre de bits de poids faible utilisés par pixel
* RETOUR : 0 en l’absence d’erreur, un nombre non nul en cas d’erreur.
*/
int extract_file(char *fileName, IMAGE_T * p_img, char bitParPixel) ;
3) Programme principal de décodage Dans le fichier decode_stegano.c, écrire la fonction main() du programme decode.
Le programme decode s’utilise en ligne de commande dans le Terminal de la façon suivante :
% decode <imageIn.pgm> <fileToRecreate> [ <nbbitsparpixel> ]
avec :
• <imageIn.pgm>
: nom du fichier image tatouée qu’il s’agit de décoder.
• <fileToRecreate> : nom du fichier à recréer avec le contenu caché.
• <nbbitsparpixel> : entier. Optionnel. Nombre de bits de poids faible utilisés par le
message caché dans imageIn.pgm. Vaut 1, 2 ou 4.
nbbitsparpixel est un paramètre optionnel. Si ce paramètre n’est pas précisé en lançant le
programme, decode estime le nombre de plan de bits utilisés pour cacher le message (en utilisant
la fonction lsb_used()) avant de procéder au décodage.
La fiche [6] explique comment passer des paramètres à un programme depuis le terminal.
Conseil : Assurez vous que le nom du fichier recréé a la même extension que le fichier d’origine,
pour pouvoir le réouvrir aisément dans la bonne application.
Conseil : pour comparer le contenu de deux fichiers octet par octet, utilisez la commande diff du
terminal. Voir man diff.
Travail à réaliser en commun : estimation du nombre de bits Dans le module estimation (fichiers estimation.h et estimation.c), implantez la fonction qui estime
le nombre de bits utilisés pour cacher un message dans une image :
1) Fonction pour estimer le nb de bits utilisé /**
* Estime le nombre de bits utilisés pour cacher un message dans l’image «img».
* ainsi que la longueur du message caché
* PARAMETRES :
*
img : l’image dans laquelle est caché le fichier.
*
p_nb_used: pointeur vers le nombre de bits de poids faible estimé
*
p_size_msg: pointeur vers la taille estimée du message
* RETOUR : 0 en l’absence d’erreur, un nombre non nul si on ne sait pas estimer
*
la longueur
*/
int lsb_used(IMAGE_T * img, int* p_nb_used, int* p_size_msg) ;
Facultatif Voici quelques pistes pour étendre votre travail :
1. Modifiez vos programmes d’encodage/décodage de telle sorte que l’image tatouée contienne
aussi le nom du fichier qui a été caché. Le programme de décodage ne prendra plus en
paramètre le nom de fichier à recréer, puisqu’il est caché dans l’image. Cela permettra, lors
du décodage, de recréer le fichier à l’identique, nom (et extension) compris !
2. Concevoir un système d’encodage plus complexe. Par exemple, ne pas parcourir les pixels
les uns à la suite des autres, mais dans un ordre plus complexe, qui éventuellement peut
dépendre d’informations propres à l’image support (eg : nombre de lignes et de colonnes...).
3. Réaliser des encodages à plusieurs niveaux. Par exemple, cacher un message crypté dans
une image, elle même cachée dans une autre image....
4. Réaliser une documentation Doxygen de votre code source.
Annexe technique
Le format de fichier image PGM P5
Le format de fichier image PGM, que vous utiliserez durant ce TD pour lire et enregistrer des
images, est une variante du format PPM dédiée aux images en niveaux de gris.
Un fichier PGM comporte un en-tête textuel ASCII, suivi des valeurs des pixels de l’image,
stockées elles en binaire.
L’en-tête d’un fichier PGM comporte au minimum 3 lignes de texte, qui seront lues à l’aide de la
fonction C fgets() et écrite à l’aide la fonction fprint().
• Ligne 1 : la chaine "P5", qui identifie le format de fichier
• Ligne 2 : deux entiers, nombre de colonnes (largeur) et nombre de lignes (hauteur) de
l’image. L’image comprend donc largeur*hauteur pixels.
• Ligne 3 : un entier précisant la valeur maximale entière des pixels de l’image. Pour ce TD,
on ne considèrera que les images pour lesquels les pixels sont encodés sur 8 bits, soit une
valeur maximale de 255. Lire un fichier qui n’a pas une valeur maximale de 255 provoquera
une erreur.
Par ailleurs, un en-tête de fichier PGM peut comporter des lignes de commentaire supplémentaires.
Une ligne de commentaire commence par le caractère ‘#’ et ne sert pas dans notre cas.
Après cet en-tête, le fichier PGM comprend les valeurs des pixels, de gauche à droite et de haut en
bas. La valeur chaque pixel est stockée en binaire sur 8 bits (unsigned char). Ces données seront
lues et écrites en binaire à l’aide des fonctions C fread() et fwrite().
Enfin, notez que vous pouvez utiliser le programme gimp pour afficher une image PGM.
Binaire, décimal et hexadécimal.
Un même nombre entier peut être codé dans des bases différentes.
Le binaire est la représentation interne utilisée par une machine numérique et correspond à la base
2, le décimal est notre système habituel et l’hexadécimal utilise la base 16. En hexadécimal, les
éléments de base sont notés de 0,1,2...9,A,B,C,D,E,F.
Par convention, un nombre exprimé en hexadécimal est précédé de '0x'. Un octet est alors
représenté par 2 digits (chiffres) représentant 2 groupes de 4 bits. Par exemple :
255 s’écrit 1111 1111 en binaire et 0xFF en hexadécimal.
157 s’écrit 1001 1101 en binaire et 0x9D en hexadécimal
128 s’écrit 1000 0000 en binaire et
0x80 en hexadécimal.
1 s’écrit
0000 0001 en binaire et 0x01 en hexadécimal.
Une variable dans un programme est toujours représentée en binaire en machine et que seul
l’affichage nous le présente dans une base quelconque.
Ainsi, le programme suivant affiche :
c en base 10 :255 ; c en base 16 :0xFF.
c en base 10 :157; c en base 16 :0x9D.
c en base 10 :1; c en base 16 :0x1.
int main() { unsigned char c ;
c= 255 ; printf("c en base 10 : %d ; c en base 16 : %x \n",c,c) ;
c= 157; printf("c en base 10 : %d ; c en base 16 : %x \n",c,c) ;
c= 1 ; printf("c en base 10 : %d ; c en base 16 : %x \n",c,c) ;
return 0;
}
Manipulation de bits en C
Les opérateurs bit à bit & (et), | (ou), ^ (ou exclusif), ˜(inversion), >> (rotation à droite), <<(rotation
à gauche) réalisent des opérations bit à bit et permettent d’isoler des bits ou groupes de bits.
Si i est un octet, prenons par exemple i=100 (i.e. 0x64 en hexadécimal et 0110 0100 en binaire),
alors :
• i & 0x2 (2 en binaire 00000010) permet d’isoler le bit 2 de i : i&0x2 donne dans cet
exemple 0
• i & 0xF0 (soit 11110000 en binaire) permet d’isoler les 4 bits de poids forts de i. i & 0xF0
donne 01100000 en binaire ou 0x60 en hexadécimal ou 96 en décimal.
• i | 0x2 (2 en binaire 00000010) permet « d’allumer » le bit 2 de i : i|0x2 donne dans cet
exemple 0110 0110
• i | 0xF1 (soit 11110001 en binaire) permet « d’allumer » les 4 bits de poids forts et le dernier
bit de i. i | 0xF1 donne 1111 0101 en binaire ou 0xF5 en hexadécimal ou 245 en décimal.
• i >> 3 décale tous les bits de i de 3 crans vers la droite. On obtient alors 00001100 ou 12 en
décimal et 0xC en hexadécimal.
• i << 2 décale tous les bits de i de 2 crans vers la gauche, soit 10010000 ou 144 en décimal
et 0x90 en hexadécimal.
Avec ces opérateurs, il est par exemple possible d’afficher un à un dans le Terminal les bits d’un
octet. C’est ce que fait la fonction suivante, en commençant par les bits de poids fort :
void affichebin(unsigned char n) {
unsigned char bit = 0 ;
unsigned char mask = 0x80 ; // 1000 0000 en binaire
char i;
for ( i = 7 ; i >=0 ; i--) {
bit = (n & mask) ;
bit = bit >> i ;
// on decale le bit i crans a droite
printf("%d", bit) ;
mask >>= 1 ; // on decale le mask de un cran à droite
}
}
Références
[1] Francois Cayre : Cryptographie, stéganographie et tatouage : des secrets partagés,
Interstices, 2008, http://interstices.info/jcms/c_32093/cryptographiestéganographie-ettatouage-des-secrets-partages
[2] Sorina Dumitrescu and Xiaolin Wu and Nasir Memon : On steganalysis of random lsb
embedding in continuous-tone images, IEEE International Conference on Image Processing,
2002
[3] Fiche informatique Phelma : Notion de module en C et fichier header .h.
http://tdinfo.phelma.grenoble-inp.fr/2Aproj/fiches/modules_fichierHeader.pdf
[4] Fiche informatique Phelma : Coding Style - conventions de codage.
http://tdinfo.phelma.grenoble-inp.fr/2Aproj/fiches/coding_styles.pdf
[5] Linus Torvalds. Linux Kernel Coding Style.
http://computing.llnl.gov/linux/slurm/coding_style.pdf
[6] Fiche informatique Phelma : Le prototype de la fonction main().
http://tdinfo.phelma.grenoble-inp.fr/2Aproj/fiches/prototype_main.pdf