Revenir
Revenir

Cacher un message dans une image

Contexte de la mise en œuvre de l'activité : en classe

Sommaire

Avant de commencerPrésentation
Exercice PythonPréparationCode (base 64) adapté - RéférenceImage sans messageInsérer une message codé avec Python - Exercice 1Récupérer le message codé avec Python - Exercice 2Aide sur les fonctionsExercice 1Exercice 2Vers le notebook Capytale - Site

Avant de commencer

Prérequis : 
  • Maîtriser les bases de la programmation Python (variables, boucles, entrées/sorties, fonctions)
  • Avoir déjà abordé les notions de liste et de dictionnaire
Contexte de la mise en œuvre de l'activité : en classe
Ressources, matériel, documents nécessaires : bibliothèque Python : PIL

Présentation

Bob souhaite envoyer un message secret à Alice. Il décide de cacher son message dans une image selon le principe de la stéganographie. Voici comment il s'y prend :
  • Il chiffre d’abord le message à l’aide d’un code (base 64) sur 6 bits
  • Il remplace les deux bits de poids faible de chaque composante du pixel (RVB) de l'image par les six bits du codage précédent : pour RVB (29, 24,0) avec la lettre « b » ("011011") cela donne (29, 26,3), ce qui ne change pas énormément la couleur du pixel.
Pour voir le message, Alice devra effectuer l'opération inverse depuis l'image codée. Elle devra récupérer les deux bits de poids faible de chaque composante RVB de chaque pixel de l'image codée, les regrouper par six pour déchiffrer chaque caractère. (29, 26,3) donne en binaire (11101, 11010,11) et en ne gardant que les deux bits de poids faible,  ("011011") ce qui donne la lettre « b ».
Alice et Bob s'accordent sur le fait que le message se termine par un point.

Exercice Python

Préparation

La bibliothèque Python « PIL » (Python Imaging Library) permet de traiter les images dans de nombreux formats. On l’importe avec from PIL import Image.
On ouvre l'image avec img = Image.open("nom_image"),on crée une nouvelle image avec Image.new("RGB",(l,h))où RGB est le mode couleur et l, h les dimensions.
img.sizepermet d'obtenir la taille (lxh) de l'image, img.format, le format (jpg, png, etc.) et img.modele mode couleur (RVB) ou niveaux de gris (L) de l'image.
On utilise la méthode getdata()pour récupérer les composantes RVB de chaque pixel dans une liste et la méthode putdata(une_liste)pour stocker les composantes RVB de chaque pixel dans un fichier. Exemple : 
dataimage = img.getdata()
resultat = Image.new("RGB", (l, h))
resultat.putdata(dataimage) # l'image n'a pas changée !
resultat.save(image.bmp) # on sauvegarde l'image au format bmp
resultat.show() # pour afficher l'image

Code (base 64) adapté - Référence

Image sans message

Insérer une message codé avec Python - Exercice 1

Partie 1 : encodage de l'image avec le message
On donne la bibliothèque et les variables nécessaires ainsi que les fonctions qui permettent de chiffrer le message et de l'insérer dans l'image :
from PIL import Image
image = "tigre.jpg"
image_codee = "tigreCode.bmp"
listeimage = []
def encode64(codein):
    code64 = {"A":"000000", "B":"000001", "C":"000010", "D":"000011", 
              "E":"000100", "F":"000101", "G":"000110", "H":"000111", 
              "I":"001000", "J":"001001", "K":"001010", "L":"001011", 
              "M":"001100", "N":"001101", "O":"001110", "P":"001111", 
              "Q":"010000", "R":"010001", "S":"010010", "T":"010011", 
              "U":"010100", "V":"010101", "W":"010110", "X":"010111", 
              "Y":"011000", "Z":"011001", "a":"011010", "b":"011011", 
              "c":"011100", "d":"011101", "e":"011110", "f":"011111", 
              "g":"100000", "h":"100001", "i":"100010", "j":"100011", 
              "k":"100100", "l":"100101", "m":"100110", "n":"100111", 
              "o":"101000", "p":"101001", "q":"101010", "r":"101011", 
              "s":"101100", "t":"101101", "u":"101110", "v":"101111", 
              "w":"110000", "x":"110001", "y":"110010", "z":"110011", 
              "0":"110100", "1":"110101", "2":"110110", "3":"110111", 
              "4":"111000", "5":"111001", "6":"111010", "7":"111011", 
              "8":"111100", "9":"111101", " ":"111110", ".":"111111"}    
    return code64[codein]
def stegano(pixel, code):
    """ Remplace les deux bits de poids faible de chaque composante RVB de 
    pixel par deux bits du code """
    r = (pixel[0]&0b11111100)|(int(code[0])*2+int(code[1]))
    v = (pixel[1]&0b11111100)|(int(code[2])*2+int(code[3]))
    b = (pixel[2]&0b11111100)|(int(code[4])*2+int(code[5]))
    return (r,v,b)
Vous devez insérer votre message secret dans l'image. Un index i est initialisé à 0. Pour chaque pixel de l'image (de la liste dataimage), si l'index i est inférieur au nombre de valeurs dans la liste msg_code, on ajoute àlisteimage le pixel de l'image modifiée avec le code du message, sinon, on ajoute simplement le pixel de l'image. On crée une nouvelle image en gardant les dimensions de la précédente et on y place la liste "listeimage". On sauvegarde la nouvelle image (tigreCode.bmp) et on l'affiche.
1. Compléter le code suivant :
msg = "rdv place du Pantheon a 10h demain matin."
msg_code = []
for c in msg:
    msg_code.append(encode64(c))
print("message :", msg)
print("message codé : ", msg_code)
img = Image.open(image)
dataimage = img.getdata()
l, h = img.size
i = 0
for pixel in dataimage :
    # A compléter

Récupérer le message codé avec Python - Exercice 2

Partie 2 : décodage du message caché
On donne la bibliothèque nécessaire ainsi que les fonctions qui permettent d'extraire le code du message de l'image et de déchiffrer le message :
from PIL import Image
def decode64(codein):
    code64 = {'000000': 'A', '000001': 'B', '000010': 'C', '000011': 'D', 
              '000100': 'E', '000101': 'F', '000110': 'G', '000111': 'H', 
              '001000': 'I', '001001': 'J', '001010': 'K', '001011': 'L', 
              '001100': 'M', '001101': 'N', '001110': 'O', '001111': 'P', 
              '010000': 'Q', '010001': 'R', '010010': 'S', '010011': 'T', 
              '010100': 'U', '010101': 'V', '010110': 'W', '010111': 'X', 
              '011000': 'Y', '011001': 'Z', '011010': 'a', '011011': 'b', 
              '011100': 'c', '011101': 'd', '011110': 'e', '011111': 'f', 
              '100000': 'g', '100001': 'h', '100010': 'i', '100011': 'j', 
              '100100': 'k', '100101': 'l', '100110': 'm', '100111': 'n', 
              '101000': 'o', '101001': 'p', '101010': 'q', '101011': 'r', 
              '101100': 's', '101101': 't', '101110': 'u', '101111': 'v', 
              '110000': 'w', '110001': 'x', '110010': 'y', '110011': 'z', 
              '110100': '0', '110101': '1', '110110': '2', '110111': '3', 
              '111000': '4', '111001': '5', '111010': '6', '111011': '7', 
              '111100': '8', '111101': '9', '111110': ' ', '111111': '.'}
    return code64[codein]
def decode(pixel):
    """ Permet de récupérer le code binaire sur 6 bits dans les trois composantes 
    RVB de pixel. Le code se trouve dans les deux bits de poids faible de 
    chaque composante"""
    r = pixel[0] & 0b11
    v = pixel[1] & 0b11
    b = pixel[2] & 0b11
    rvb = r*16 + v*4 + b
    rvbb = bin(rvb)[2:]
    while len(rvbb) < 6:
        rvbb = '0'+rvbb
    return rvbb
Vous allez découvrir le message caché dans l'image. Pour chaque composante RVB de chaque pixel de l'image il faut :
  • récupérer le code binaire 6 bits du message,
  • traduire le code binaire en caractère,
  • ajouter le caractère dans message,
  • interrompre la boucle (break) si le caractère est le point.
Il faut ensuite afficher le message.
2. Compléter le programme suivant :
img_codee = "tigreCode.bmp"
message = ""
listeimage = []
img = Image.open(img_codee)
dataimage = img.getdata()
for pixel in dataimage:
    # A compléter

Aide sur les fonctions

Exercice 1

encode64(codein): dictionnaire qui permet de traduire un caractère en un code binaire sur six bits. Pour le caractère "r" ce sera '101011'.
stegano(pixel, code): permet d'insérer le code binaire sur six bits dans les trois octets RVB d'un pixel. Exemple : soit le premier pixel de l'image dataimage[0] = (35, 56, 39), que l'on peut traduire en binaire par (0010 0011, 0011 1000, 0010 0111). Il faut remplacer les deux bits de poids faible (ceux de droite !) de ces trois valeurs par les six bits du code avec l'opération (pixel[0]&0b11111100)|(int(code[0])*2+int(code[1])) pour la composante rouge. 0010 0011devient 0010 0010, 0011 1000 devient 0011 1010 et 0010 0111 devient 0010 0111 . Finalement, le pixel vaut (34, 58, 39).

Exercice 2

decode64(codein): dictionnaire qui permet de traduire un code binaire sur six bits en  un caractère. Pour le code '101011' ce sera "r".
decode(pixel): permet d'extraire le code binaire sur six bits des trois octets RVB d'un pixel. Exemple : soit le premier pixel de l'image dataimage[0] = (34, 58, 39), que l'on peut traduire en binaire par (0010 0010, 0011 1010, 0010 0111). Il faut extraire les deux bits de poids faible (ceux de droite !) de ces trois valeurs puis les recomposer pour former les six bits du code (101011).

Vers le notebook Capytale - Site