🖼️
Cryptographie
Stéganographie : l'art de cacher des données
📅 2025-06-08 ⏱ 9 min de lecture 🏷 Intermédiaire

// SOMMAIRE

Stéganographie : l'art de cacher des données dans les fichiers

La stéganographie est l'art de dissimuler un message dans un support apparemment anodin — une image, un fichier audio, une vidéo. Contrairement à la cryptographie qui rend un message illisible, la stéganographie cache son existence même. Utilisée depuis l'antiquité, elle est aujourd'hui employée par les cybercriminels, les espions et les militants des droits de l'homme.

Stéganographie vs Cryptographie

Cryptographie :

Message visible → ILLISIBLE

"Réunion à 15h" → "kR8#mP2nL5"

→ Tout le monde SAIT qu'il y a un message, mais ne peut pas le lire

Stéganographie :

Message → INVISIBLE dans un support anodin

"Réunion à 15h" → [photo d'un chat tout à fait normale]

→ Personne ne sait qu'il y a un message caché

Steganocryptographie (combinaison) :

Message chiffré → caché dans une image

→ Protection maximale

Techniques de stéganographie numérique

LSB — Least Significant Bit

La technique la plus courante pour les images.

# Principe LSB : modifier le bit le moins significatif de chaque pixel

Un pixel RGB = 3 octets (Rouge, Vert, Bleu)

Le LSB = le bit de poids faible = modification imperceptible à l'œil

Pixel original : Rouge = 11001010 = 202

Pixel modifié : Rouge = 11001011 = 203 (différence de 1/255 ≈ 0.4%)

Imperceptible !

Chaque pixel peut stocker 3 bits (1 par canal RGB)

Une image 1920×1080 = 2 073 600 pixels × 3 bits = 777 600 octets ≈ 760 Ko de données cachées !

def encode_lsb(image_path, message, output_path):

from PIL import Image

import numpy as np

img = Image.open(image_path).convert('RGB')

pixels = np.array(img)

# Convertir le message en bits

message_bytes = message.encode('utf-8') + b'\x00\x00\x00' # Marqueur de fin

bits = ''.join(format(byte, '08b') for byte in message_bytes)

flat = pixels.flatten().copy()

if len(bits) > len(flat):

raise ValueError("Message trop grand pour cette image")

# Injecter les bits dans les LSB

for i, bit in enumerate(bits):

flat[i] = (flat[i] & 0xFE) | int(bit) # Remplace le LSB

# 0xFE = 11111110 → efface le LSB

# | int(bit) → met le bit du message

modified = flat.reshape(pixels.shape)

Image.fromarray(modified.astype(np.uint8)).save(output_path, 'PNG')

print(f"Message caché dans {output_path}")

def decode_lsb(image_path, message_length):

from PIL import Image

import numpy as np

img = Image.open(image_path).convert('RGB')

flat = np.array(img).flatten()

# Extraire les LSB

bits = ''.join(str(pixel & 1) for pixel in flat)

# Reconvertir en bytes

chars = []

for i in range(0, message_length * 8, 8):

byte = bits[i:i+8]

chars.append(chr(int(byte, 2)))

return ''.join(chars)

Démonstration

encode_lsb('chat.png', 'Réunion secrète à 15h salle B', 'chat_modifie.png')

message = decode_lsb('chat_modifie.png', 30)

print(f"Message extrait : {message}")

Steghide — Outil classique

# Installer steghide

sudo apt install steghide

Cacher un fichier dans une image JPEG

steghide embed -cf photo.jpg -sf secret.txt -p "motdepasse"

-cf : cover file (l'image)

-sf : secret file (le fichier à cacher)

-p : mot de passe

Extraire le fichier caché

steghide extract -sf photo.jpg -p "motdepasse"

Voir les informations sans extraire

steghide info photo.jpg

Stéganalyse avec StegDetect

# Détecter la présence de stéganographie dans une image

StegDetect cherche les signatures statistiques

stegdetect -t p photo.jpg

-t p : teste spécifiquement JSteg, JPHide, Invisible Secrets

Analyse statistique avec zsteg (PNG et BMP)

gem install zsteg

zsteg image.png

zsteg -a image.png # Tous les tests

Analyse avec binwalk

binwalk -e image.png # Extraire les fichiers embarqués

binwalk --dd='.*' image.png # Extraire tout

Stéganographie avancée

Audio stéganographie

# Cacher des données dans un fichier WAV

import wave, struct

def hide_in_audio(audio_file, message, output_file):

with wave.open(audio_file, 'rb') as audio:

frames = bytearray(audio.readframes(audio.getnframes()))

params = audio.getparams()

message_bits = ''.join(format(ord(c), '08b') for c in message) + '1111111111111110'

for i, bit in enumerate(message_bits):

frames[i] = (frames[i] & 0xFE) | int(bit)

with wave.open(output_file, 'wb') as out:

out.setparams(params)

out.writeframes(bytes(frames))

Stéganographie dans la phase du signal audio (plus résistante)

OpenStego, DeepSound, SilentEye

Stéganographie réseau

# Cacher des données dans des paquets réseau

Covert channels : communication cachée dans le trafic légitime

TCP Timestamp Covert Channel

Le champ timestamp de TCP peut transmettre des données

ICMP Covert Channel

Les octets de payload des pings peuvent contenir des messages

import subprocess

def icmp_covert_send(target, message):

"""Envoie un message caché dans des pings"""

for char in message:

# Mettre le caractère dans le payload ICMP

payload = f"{'A' 16}{char}{'A' 15}" # 32 octets avec le char au milieu

subprocess.run(['ping', '-c', '1', '-p',

payload.encode().hex(), target],

capture_output=True)

DNS Covert Channel (utilisé par les malwares pour exfiltrer)

Les données sont encodées dans les noms de domaine DNS

"dGhpcyBpcyBhIHRlc3Q=.evil.com" (base64 dans sous-domaine)

Stéganographie dans les documents

# Stéganographie dans les fichiers Office/PDF

Technique "Zero-Width Characters" (caractères invisibles)

Encoder un message binaire avec des caractères Unicode invisibles

ZERO_WIDTH_SPACE = '\u200b' # bit 0

ZERO_WIDTH_NON_JOINER = '\u200c' # bit 1

def encode_in_text(cover_text, secret_message):

"""Cache un message dans du texte avec des caractères invisibles"""

binary_message = ''.join(format(ord(c), '08b') for c in secret_message)

hidden_chars = ''.join(

ZERO_WIDTH_SPACE if bit == '0' else ZERO_WIDTH_NON_JOINER

for bit in binary_message

)

# Insérer au milieu du texte couverture

mid = len(cover_text) // 2

return cover_text[:mid] + hidden_chars + cover_text[mid:]

cover = "Cette photo de vacances est magnifique, le soleil était au rendez-vous."

stego_text = encode_in_text(cover, "Opération Soleil: J+3")

Le texte semble identique mais contient 22 caractères invisibles

Utilisations réelles

Usages légitimes

Watermarking (tatouage numérique) :

→ Protéger les droits d'auteur des images/vidéos

→ Prouver la propriété d'un contenu

→ Tracer les fuites (canary documents)

Communications sécurisées :

→ Journalistes et sources dans des pays autoritaires

→ WhatsApp utilise la stéganographie pour les messages "disappearing"

→ Militants des droits de l'homme

Forensique :

→ Cacher des métadonnées d'attribution

→ Preuves numériques inaltérables

Utilisations malveillantes

Malwares stéganographiques :

→ Sunburst (SolarWinds 2020) : commandes C2 cachées dans des images

→ Zloader : charges malveillantes dans des PDFs

→ APT29 (Cozy Bear) : communications C2 via images sur Instagram

Exfiltration de données :

→ Données sensibles cachées dans des images envoyées par email

→ Contourne les DLP (Data Loss Prevention)

→ Exemple : 1000 mots de passe dans une photo de vacances

CTF (Capture The Flag) :

→ Les CTF adorent la stéganographie

→ Flags cachés dans des images, audio, vidéo

Outils stéganographie

# Pour cacher

steghide # JPEG, BMP, WAV, AU

OpenStego # PNG (interface graphique)

SilentEye # BMP, JPEG, WAV (interface graphique)

snow # Espaces blancs dans le texte

Pour détecter / analyser (stéganalyse)

stegdetect # JPEG (détection statistique)

zsteg # PNG, BMP

binwalk # Fichiers embarqués dans les binaires

foremost # Récupération forensique

exiftool # Métadonnées (souvent des données cachées là)

En Python

from stegano import lsb

secret = lsb.hide("cover.png", "Mon message secret")

secret.save("output.png")

message = lsb.reveal("output.png")

Détecter la stéganographie

# Analyse statistique pour détecter le LSB stego

import numpy as np

from PIL import Image

def detect_lsb_stego(image_path):

img = np.array(Image.open(image_path).convert('RGB'))

# Extraire les LSBs

lsbs = img.flatten() & 1

# Dans une image naturelle, les LSBs suivent une distribution pseudo-aléatoire

# Dans une image stégo, la distribution est plus uniforme (50/50)

ones_ratio = np.sum(lsbs) / len(lsbs)

print(f"Ratio de 1 dans les LSBs : {ones_ratio:.3f}")

if 0.45 < ones_ratio < 0.55:

print("⚠️ Distribution suspecte - Possible stéganographie LSB")

else:

print("✅ Distribution normale - Probablement pas de stégo LSB")

detect_lsb_stego("image_suspecte.png")

Conclusion

La stéganographie illustre que la sécurité ne se limite pas au visible — les données cachées peuvent être aussi dangereuses que les données chiffrées. Pour les défenseurs, les outils DLP modernes intègrent de plus en plus la détection stéganographique. Pour les curieux et les CTF players, c'est un domaine fascinant qui mêle mathématiques, perception humaine et créativité.

💬 Voir l'article avec commentaires →
← Sécurité Kubernetes : protéger vos clusters Buffer Overflow : comprendre et exploiter les débordements →