Dévoiler le message caché dans les 2 bits de poids faible de l’image fournie (dont l’adresse est https://info-tsi-vieljeux.github.io/cryptedimage.png).

Importons d’abord les modules nécessaires :
from PIL import Image
import urllib.request # pour récupérer une image sur le web
from IPython.display import display # pour afficher dans un notebook
import numpy as np
Ce petit code suffit pour dévoiler l’image cachée dans l’image :
def decache_image(image):
image_decrypt = (image % 2**2) * 2**6 + 2**5
return image_decrypt
image % 2**2 récupère les deux bits de poids faibles.* 2**6 permet de “dilater” les 4 valeurs $\{0,1,2,3\}\rightarrow\{0,64,128,192\}$+ 2**5 permet de décaller les valeurs pour les centrer entre 0 et 255 $\rightarrow\{32,96,160,224\}$.Pour l’afficher (sur un notebook type Colab) :
final = decache_image(cache)
affichage = Image.fromarray(comparaison)
display(affichage)

On peut vérifier que l’image obtenue est bien codée sur 2 bits avec le code suivant :
print(set(final.flatten()))
{32, 224, 96, 160}
flatten est une méthode de numpy permettant de transformer un tableau multidimensionnel en un tableau unidimensionnel (ici, le tableau image 3D devient donc un tableau 1D de longueur $l\times L \times 3$).
set est une fonction native python permettant de convertir une séquence d’objets en une nouvelle structure, l’ensemble (en python, c’est le typeset). Son grand avantage est d’éliminer automatiquement tout doublon !
Il faut donc réussir à convertir l’image secrète en 6 bits (2 bits pour chaque pixel) et l’image servant de cache en 18 bits (6 bits pour chaque pixel) avant d’additionner les deux images, faisant en sorte que l’image à cacher corresponde aux deux bits de poids faible de l’image finale.

def cache_image(image1,image2):
"""
cache_image(image1 : numpy.ndarray (H1,L1,3),image2 : numpy.ndarray (H2,L2,3)) -> numpy.ndarray (H1,L1,3)
cache image2 dans image1 après l'avoir recadrée si besoin
"""
H1,L1,_ = image1.shape
H2,L2,_ = image2.shape
hidden = np.copy(image2)[:H1,:L1,:3] # sans np.copy, le slicing rend image2 mutable
hidden = (hidden.astype(np.uint16)//2**6).astype(np.uint8)
image1 = image1//2**2 * 2**2
imagemix = image1[:,:,:3] # le :3 sert à retirer une éventuelle transparence, codée comme une 4e couleur
h , l = max((H1-H2)//2,0), max((L1-L2)//2,0)
imagemix[h:h+H2,l:l+L2] += hidden
return imagemix
Et pour faciliter l’utilisation d’url pour chaque image :
def cachageimage():
opener=urllib.request.build_opener()
opener.addheaders=[('User-Agent','Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1941.0 Safari/537.36')]
urllib.request.install_opener(opener) # sans ce bazar, certains sites refusent la visite car le fouineur se présente pas comme un browser
url1 = input("url de l'image à afficher : ")
urllib.request.urlretrieve(url1, '1')
url2 = input("url de l'image à cacher : ")
urllib.request.urlretrieve(url2, '2')
image1 = np.array(Image.open('1'))
image2 = np.array(Image.open('2'))
return cache_image(image1,image2)
On obtient finalement :

Et en “décachant” (pour vérifier) :
