Stéganographie "BPC"
Tuteur : Pierre Hyvernat
Elève : Hania Boudjaj
Présentation du projet
Histoire
"Ensemble de techniques permettant de transmettre une information en la dissimulant au sein d'une autre information (photo, vidéo, texte, etc.) sans rapport avec la première et le plus souvent anodine, essentiellement à l'aide de logiciels spécialisés."
On attribue la première mention de la stéganographie telle que définieci-dessus à l'historien grec Hérodote. Il décrit dans son ouvrage Historia, une tablette de bois gravée qu’on recouvre de cire.
Dans le cadre de ce projet, nous nous intéresserons à des algorithmes de stéganographie qui permettent de cacher du texte dans une image.
La stéganographie LSB
Principe de base
Une image est composée de pixels eux-même composés de trois octets. Chacun de ces octets correspond à une composante couleur du pixel, Red, Green et Blue.
Chaque composante peut prendre une valeur allant de 0 à 255.
Ainsi, on peut modifier les n bits de poids faible d’un octet sans que cela soit visible à l'œil nu. Notons cependant que plus le poids du bit modifié augmente, plus la modification sera visible.
La stéganographie LSB permet donc de remplacer les n bits de poids faible des pixels d’une image par les bits d’une chaîne de caractère. On peut alors cacher un message dans une image.
Exemples
Code
rom PIL import Image
def cache(img_name: str, msg: str, nb_bits: int = 1, nom_img_fin: str = "SECRETFIN_BYTE2.png"):
if not (1 <= nb_bits <= 4):
print("Le nombre de bits par couleur doit être entre 1 et 4.")
return
# Ouvrir et convertir l'image en mode RGB :
image_ori = Image.open(img_name)
image_rgb = image_ori.convert("RGB") # Evite les problèmes liés à la transparence.
data_bytes = bytearray(image_rgb.tobytes())
# Encodage du message en binaire.
msg_bin = "".join(bin(ord(c))[2:].zfill(8) for c in msg)
msg_len = len(msg_bin)
msg_len_bin = bin(msg_len)[2:].zfill(16) # 16 bits pour représenter la taille du message
message_complet = msg_len_bin + msg_bin
# Vérification de la capacité
capacite = len(data_bytes) * nb_bits
if len(message_complet) > capacite:
print("Le message est trop long pour être encodé dans cette image avec " + str(nb_bits) + " bit(s) par couleur.")
return
# Encodage dans le bytearray.
bit_index = 0
for i in range(len(data_bytes)):
byte = data_bytes[i]
byte_bin = list(bin(byte)[2:].zfill(8)) # On s'assure que la chaîne fait 8 bits
for b in range(nb_bits):
if bit_index >= len(message_complet):
break
# Remplacer les bits les moins significatifs
byte_bin[-(b + 1)] = message_complet[bit_index]
bit_index += 1
data_bytes[i] = int("".join(byte_bin), 2)
if bit_index >= len(message_complet):
break
# Reconstruction de l'image.
img_fin = Image.frombytes("RGB", image_rgb.size, bytes(data_bytes))
img_fin.save(nom_img_fin)
print("Message encodé avec succès dans " + nom_img_fin + " avec " + str(nb_bits) + " bit(s) par couleur.")
def discover(img_name: str, nb_bits: int = 1):
if not (1 <= nb_bits <= 4):
print("Le nombre de bits par couleur doit être entre 1 et 4.")
return
# Ouvrir l'image.
image_ori = Image.open(img_name)
image_rgb = image_ori.convert("RGB")
data_bytes = bytearray(image_rgb.tobytes())
bits_lus = ""
taille_msg = None
for byte in data_bytes:
byte_bin = bin(byte)[2:].zfill(8)
for b in range(nb_bits):
bits_lus += byte_bin[-(b + 1)]
# Lecture des 16 premiers bits, qui contiennent la taille.
if len(bits_lus) == 16 and taille_msg is None:
taille_msg = int(bits_lus, 2)
total_bits = 16 + taille_msg
if taille_msg is not None and len(bits_lus) >= total_bits:
break
if taille_msg is not None and len(bits_lus) >= total_bits:
break
# Reconstruction du message.
message_bits = bits_lus[16:16 + taille_msg]
octets = [message_bits[i:i+8] for i in range(0, len(message_bits), 8)]
message = "".join(chr(int(o, 2)) for o in octets)
print("MESSAGE DÉCODÉ :", message)
return message
Problèmes
Plus la taille du message à cacher est importante, plus le nombre de bit nécessaire à l’encodage augmente.
Or, comme mentionné plus tôt, plus le poids du bit modifié augmente, plus la modification sera visible.
Pour remédier à ce problème, nous pouvons nous tourner vers la Stéganographie BPC.
La stéganographie BPC
Principe de base
Contrairement à la stéganographie LSB, nous allons maintenant choisir dans quelles parties de l’image cacher notre message. Pour cela, nous allons nous intéresser à la complexité d’une image.
Soit une image composée de pixel RGB. Nous allons diviser cette image en blocs de 8*8 pixels. Pour décider si oui ou non nous cachons une partie du message dans le bloc actuel, nous allons calculer sa complexité. Pour cela, il suffit de compter le nombre de changement entre deux bits consécutifs en ligne et en colonnes? Plus le nombre de changements est élevé dans un bloc, plus il est complexe. Intervient alors un nouveau problème : lorsqu’on encode une partie du message, il y est possible que le bloc change soudainement de complexité, ce qui pose problème pour le décodage. Pour y remédier, nous effectuons l’opération arithmétique XOR entre le bloc et un damier. Cette méthode permet de faire passer la complexité d’un bloc de simple à complexe.