Lena

Lenna

La technique de stéganographie utilisée précédemment pour cacher un message dans une photo est très loin d’être parfaite, comme Christophe la justement fait remarquer. Elle consistait simplement à remplacer la composante rouge (un octet) de chaque pixel par la valeur ASCII d’un caractère du message à cacher. Ceci en partant du début du fichier. La transformation est la suivante:

(R, G, B)
devient:
(ord(ascii_character), G, B)

Le rouge est totalement modifié. On altére considérablement autant de pixels que le message comporte de caractères. Doù la trace noire en haut à gauche de la photo.

Vous vous en doutez, il existe de meilleures façons de dissimuler un message dans une image. Par exemple la méthode bien connue LSB (Least Significant Bit). Les données sont insérées à la place des bits les moins importants. Pour chaque composante RGB on a 256 valeurs possibles et nous faisons varier uniquement le bit de poids faible. La transformation est la suivante:

(R, G, B) = (0000000****, 00000001, 0000000****)
devient:
(R, G, B) = (00000001, 0000000****, 00000001)

Maintenant les composantes sont faiblement détériorées. Évidemment cette méthode encore très simple est attaquable. Mais la belle Lena mérite cet effort.

Voici le code pour voir le message caché dans la photo de ce billet:

import urllib
from PIL import Image

def bs(s):
    """
    Convert an int to its bits representation as a string of 0's and 1's.
    """
    return str(s) if s<=1 else bs(s>>1) + str(s&1)

def decode_image(img):
    """Find a message in an encoded image (with the
    LSB technic).
    """
    width, height = img.size
    bits, index = "", 0
    for row in range(height):
        for col in range(width):
            r, g, b = img.getpixel((col, row))
            bits += bs(r)[-1] + bs(g)[-1] + bs(b)[-1]

            if len(bits) >= 8:
                if int(bits[-8:], 2) == 126:
                    list_of_string_bits = ["".join(list(bits[i*8:(i*8)+8])) for i in range(0, len(bits)/8)]

                    list_of_character = [chr(int(elem, 2)) for elem in list_of_string_bits]
                    return "".join(list_of_character)[:-2]
    return ""

urllib.urlretrieve("https://www.cedricbonhomme.org/images/blog/2014/08/Lenna_LSB.png", "Lenna_LSB.png")
img = Image.open("Lenna_LSB.png")
print decode_image(img)

Le code complet (cacher et retrouver un message). Il s’agit d’une première implémentation. J’ai encore quelques améliorations à effectuer, mais le script fonctionne plutôt correctement.

Je pense que je vais bientôt créer un dépôt Mercurial avec des algorithmes de stéganographie 😉

Posts in this series

Related Posts