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