La stéganographie consiste à dissimuler un message dans un autre message. Dans cet article nous verrons une façon de dissimuler un texte dans une image à l’aide de Python.
Une méthode de stéganographie en Python
Je souhaite incruster les lettres “AB” dans l’image.
Code ASCII des caractères
Il faut d’abord que j’obtienne le code ASCII de chaque caractère. On l’obtient en Python avec la commande :
bin( ord( 'A' ) ) # donne : '0b1000001' bin( ord( 'B' ) ) # donne : '0b1000010'
Il nous faut un octet donc on supprime le ‘0b’:
bin( ord( 'A' ) )[2:] # donne : '1000001' bin( ord( 'B' ) )[2:] # donne : '1000010'
Il nous faut encore formater de sorte à bien avoir 8 binaires :
bin( ord( 'A' ) )[2:].rjust(8,'0') # donne : '01000001' bin( ord( 'B' ) )[2:].rjust(8,'0') # donne : '01000010'
Séparation des niveaux de couleurs
Vous le savez peut-être, une image est composée de 3 ou 4 couches; on va simplifier en disant qu’elle contient 3 niveaux de couleurs : rouge, vert et bleu. On va donc, pour chaque pixel de l’image, récupérer le nombre associé au niveau rouge (par exemple), puis prendre le nombre pair qui lui est inférieur ou égal (si le pixel “0” est associé à 37, on va arrondir à 36 et s’il est associé à 42, on garde 42). Pour chaque pixel, je vais noter R[i] le résultat obtenu pour le pixel i.
Transformation de la valeur
Un tableau est préférable pour expliquer, car là, ça devient chiant à décrire avec des mots ce qui se passe :
Pixel | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
Image | 30 | 71 | 25 | 20 | 10 | 152 | 15 | 96 | 18 | 20 | 20 | 20 | 20 | 87 | 15 | 101 |
Réduction | 30 | 70 | 24 | 20 | 10 | 152 | 14 | 96 | 18 | 20 | 20 | 20 | 20 | 86 | 14 | 100 |
Binaire | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
Couverture | 30 | 71 | 24 | 20 | 10 | 152 | 14 | 97 | 18 | 21 | 20 | 20 | 20 | 86 | 15 | 100 |
- première ligne : représente le numéro du pixel de l’image;
- deuxième ligne : représente le nombre associé au pixel i sur le niveau de rouge;
- troisième ligne : représente l’arrondi au nombre pair inférieur ou égal;
- quatrième ligne : représente les deux octets correspondant à ‘A’ et ‘B’ (le message à incruster);
- dernière ligne : représente la somme des lignes 3 et 4, qui donne le nombre attribué au pixel i sur la couche rouge dans l’image finale, que l’on appelle généralement la couverture.
Ainsi, à l’œil nu, on ne remarque aucune altération de l’image puisque l’on ajoute uniquement 1 (au maximum) sur une seule couche.
Stéganographie en Python à l’aide de PIL
La première chose à savoir est que nous allons manipuler des images en Python et donc utiliser le module PIL. Il faut alors l’installer. Dans un terminal, taper (je suis sous Python 3.8+) :
pip install Pillow
Incruster un message dans une image (stéganographie python)
Voici la définition de la fonction:
from PIL import Image def stegano(name_img , msg): im = Image.open(name_img) # on récupère les dimensions de l'image w , h = im.size # on sépare l'image en trois : rouge, vert et bleu r , g , b = im.split() # on transforme la partie rouge en liste r = list( r.getdata() ) # on calcule la longueur de la chaîne et on la transforme en binaire u = len(msg) v = bin( len(msg) )[2:].rjust(8,"0") # on transforme la chaîne en une liste de 0 et de 1 ascii = [ bin(ord(x))[2:].rjust(8,"0") for x in msg ] # transformation de la liste en chaîne a = ''.join(ascii) # on code la longueur de la liste dans les 8 premiers pixels rouges for j in range(8): r[j] = 2 * int( r[j] // 2 ) + int( v[j] ) # on code la chaîne dans les pixels suivants for i in range(8*u): r[i+8] = 2 * int( r[i+8] // 2 ) + int( a[i] ) # on recrée l'image rouge nr = Image.new("L",(16*w,16*h)) nr = Image.new("L",(w,h)) nr.putdata(r) # fusion des trois nouvelles images imgnew = Image.merge('RGB',(nr,g,b)) new_name_img = "couv_" + name_img imgnew.save(new_name_img)
En tapant:
stegano("grand_pere_a_la_plage.png" , "Il a mangé trop de cassoulet !")
le message est incrusté dans l’image et la couverture est sauvegardée sous le nom “couv_grand_pere_a_la_plage.png”.
Obtention du message
def get_msg(name_couv): im = Image.open(name_couv) r , g , b = im.split() r = list( r.getdata() ) # lecture de la longueur de la chaine p = [ str(x%2) for x in r[0:8] ] q = "".join(p) q = int(q,2) # lecture du message n = [ str(x%2) for x in r[8:8*(q+1)] ] b = "".join(n) message = "" for k in range(0,q): l = b[8*k:8*k+8] message += chr(int(l,2)) return message
À noter que n’importe qui peut obtenir le message… Ce n’est donc pas très sécurisé. Cette technique est donc à coupler avec une technique de cryptographie pour chiffrer et déchiffrer le message…
Un module pour la stéganographie
Si tout ceci vous paraît trop compliqué, autant utiliser le module steganocryptopy.