Mirage/mirage.py

282 lines
11 KiB
Python
Raw Normal View History

2026-06-26 08:43:12 +02:00
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
from PIL import Image, ImageTk
import numpy as np
from cryptography.fernet import Fernet
import base64
import hashlib
import sys, os
from datetime import datetime
# --- Fonctions de chiffrement/déchiffrement ---
def generate_key_from_password(password: str) -> bytes:
"""Génère une clé Fernet à partir d'un mot de passe."""
digest = hashlib.sha256(password.encode()).digest()
return base64.urlsafe_b64encode(digest)
def encrypt_text(text: str, key: bytes) -> bytes:
"""Chiffre le texte avec Fernet."""
f = Fernet(key)
return f.encrypt(text.encode('utf-8'))
def decrypt_text(encrypted_text: bytes, key: bytes) -> str:
"""Déchiffre le texte avec Fernet."""
f = Fernet(key)
return f.decrypt(encrypted_text).decode('utf-8')
# --- Fonctions de stéganographie ---
def text_to_bin(text: str) -> str:
"""Convertit du texte en binaire."""
return ''.join(format(ord(c), '08b') for c in text)
def bin_to_text(binary: str) -> str:
"""Convertit du binaire en texte."""
return ''.join(chr(int(binary[i:i+8], 2)) for i in range(0, len(binary), 8))
def insert_text_in_image(image_path: str, output_path: str, text: str, password: str) -> tuple:
try:
key = generate_key_from_password(password)
encrypted_text = encrypt_text(text, key)
binary_text = text_to_bin(encrypted_text.decode('utf-8'))
binary_text += '1111111111111110' # Délimiteur de fin
img = Image.open(image_path)
if img.mode != "RGB":
img = img.convert("RGB") # Force RGB
pixels = np.array(img)
flat_pixels = pixels.flatten()
if len(binary_text) > len(flat_pixels):
return (False, "L'image est trop petite pour contenir le texte.")
for i in range(len(binary_text)):
flat_pixels[i] = (flat_pixels[i] & 0xFE) | int(binary_text[i])
new_pixels = flat_pixels.reshape(pixels.shape)
new_img = Image.fromarray(new_pixels.astype('uint8')) # Assure-toi que le type est uint8
new_img.save(output_path, format="PNG") # Sauvegarde en PNG
return (True, f"Texte inséré avec succès dans {output_path}")
except Exception as e:
return (False, f"Erreur : {str(e)}")
def extract_text_from_image(image_path: str, password: str) -> tuple:
"""Extrait le texte chiffré d'une image."""
try:
img = Image.open(image_path)
pixels = np.array(img)
flat_pixels = pixels.flatten()
binary_text = ''
for pixel in flat_pixels:
binary_text += str(pixel & 1)
# Trouver le délimiteur de fin
delimiter = '1111111111111110'
end_index = binary_text.find(delimiter)
if end_index == -1:
return (False, "Aucun texte trouvé dans l'image.")
binary_text = binary_text[:end_index]
encrypted_text = bin_to_text(binary_text).encode('utf-8')
key = generate_key_from_password(password)
decrypted_text = decrypt_text(encrypted_text, key)
return (True, decrypted_text)
except Exception as e:
return (False, f"Erreur : {str(e)}")
def generate_output_filename(input_path: str) -> str:
"""Génère un nom de fichier de sortie."""
base, ext = os.path.splitext(input_path)
return f"{base}_stego{ext}"
def resource_path(relative_path):
""" Gère les chemins pour PyInstaller """
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
# --- Interface graphique ---
class SteganographyApp:
def __init__(self, root):
self.root = root
self.root.title("Mirage")
icon_path = resource_path("mirage.ico")
try:
self.root.iconphoto(True, tk.PhotoImage(file=icon_path))
except:
print(f"Icône non trouvée à {icon_path}")
self.image_path = tk.StringVar()
self.output_path = tk.StringVar()
self.password = tk.StringVar()
self.setup_ui()
def setup_ui(self):
# Frame principal
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# Onglets
notebook = ttk.Notebook(main_frame)
notebook.pack(fill=tk.BOTH, expand=True)
# Onglet pour insérer du texte
insert_tab = ttk.Frame(notebook)
notebook.add(insert_tab, text="Insérer du texte")
# Onglet pour extraire du texte
extract_tab = ttk.Frame(notebook)
notebook.add(extract_tab, text="Extraire du texte")
# Onglet Aide
help_tab = ttk.Frame(notebook)
notebook.add(help_tab, text="Aide")
self.setup_help_tab(help_tab)
# Widgets pour l'onglet Insérer
ttk.Label(insert_tab, text="Image source:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(insert_tab, textvariable=self.image_path).grid(row=0, column=1, sticky=tk.EW, padx=5, pady=5)
ttk.Button(insert_tab, text="Parcourir", command=self.browse_image_insert).grid(row=0, column=2, padx=5, pady=5)
ttk.Label(insert_tab, text="Image de sortie:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(insert_tab, textvariable=self.output_path).grid(row=1, column=1, sticky=tk.EW, padx=5, pady=5)
ttk.Button(insert_tab, text="Parcourir", command=self.browse_output_insert).grid(row=1, column=2, padx=5, pady=5)
ttk.Button(insert_tab, text="Générer un nom", command=self.generate_output_name).grid(row=1, column=3, padx=5, pady=5)
ttk.Label(insert_tab, text="Texte à cacher:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
self.text_to_hide = scrolledtext.ScrolledText(insert_tab, height=10)
self.text_to_hide.grid(row=2, column=1, columnspan=3, sticky=tk.EW, padx=5, pady=5)
ttk.Label(insert_tab, text="Mot de passe:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(insert_tab, textvariable=self.password, show="*").grid(row=3, column=1, sticky=tk.EW, padx=5, pady=5)
ttk.Button(insert_tab, text="Insérer le texte", command=self.insert_text).grid(row=4, column=1, pady=10)
# Widgets pour l'onglet Extraire
ttk.Label(extract_tab, text="Image avec texte caché:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(extract_tab, textvariable=self.image_path).grid(row=0, column=1, sticky=tk.EW, padx=5, pady=5)
ttk.Button(extract_tab, text="Parcourir", command=self.browse_image_extract).grid(row=0, column=2, padx=5, pady=5)
ttk.Label(extract_tab, text="Mot de passe:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Entry(extract_tab, textvariable=self.password, show="*").grid(row=1, column=1, sticky=tk.EW, padx=5, pady=5)
ttk.Button(extract_tab, text="Extraire le texte", command=self.extract_text).grid(row=2, column=1, pady=10)
ttk.Label(extract_tab, text="Texte extrait:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
self.result_area = scrolledtext.ScrolledText(extract_tab, height=10)
self.result_area.grid(row=3, column=1, columnspan=3, sticky=tk.EW, padx=5, pady=5)
# Configuration des colonnes et lignes
insert_tab.columnconfigure(1, weight=1)
extract_tab.columnconfigure(1, weight=1)
def setup_help_tab(self, help_tab):
"""Configure l'onglet Aide avec le contenu du README."""
help_text = scrolledtext.ScrolledText(help_tab, height=20, wrap=tk.WORD)
help_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
help_content = """Mirage - V1.00 - Outil de stéganographie et chiffrement
📌 DESCRIPTION
Ce projet est un outil graphique qui permet de :
- Chiffrer et déchiffrer du texte avec l'algorithme Fernet
- Cacher et extraire du texte dans des images (stéganographie)
🛠 PRÉREQUIS
- Python 3.8+
- Pillow 9.0.0+ (traitement des images)
- numpy 1.21.0+ (calculs numériques)
- cryptography 36.0.0+ (chiffrement Fernet)
- tkinter (inclus avec Python)
🚀 UTILISATION
1. Lancer l'application : python3 mirage.py
2. Onglet "Insérer du texte" :
- Sélectionnez une image source
- Spécifiez l'image de sortie
- Entrez le texte à cacher
- Entrez un mot de passe
- Cliquez sur "Insérer le texte"
3. Onglet "Extraire du texte" :
- Sélectionnez une image avec texte caché
- Entrez le mot de passe
- Cliquez sur "Extraire le texte"
🔐 SÉCURITÉ
- Le chiffrement utilise une clé générée à partir de votre mot de passe
- La clé est dérivée en SHA-256 et encodée en base64
- Utilisez des mots de passe forts pour une meilleure sécurité
LIMITATIONS
- L'image doit être suffisamment grande pour contenir le texte
- Le mot de passe est nécessaire pour extraire le texte
📧 CONTACT
Pour toute question ou support : f4iyt@free.fr
📜 LICENCE
Ce projet est sous licence MIT."""
help_text.insert("1.0", help_content)
help_text.config(state=tk.DISABLED)
def browse_image_insert(self):
filename = filedialog.askopenfilename(filetypes=[("Images", "*.png *.jpg *.jpeg *.bmp")])
if filename:
self.image_path.set(filename)
self.output_path.set(generate_output_filename(filename))
def browse_output_insert(self):
filename = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("Images", "*.png *.jpg *.jpeg *.bmp")])
if filename:
self.output_path.set(filename)
def generate_output_name(self):
input_path = self.image_path.get()
if input_path:
self.output_path.set(generate_output_filename(input_path))
def browse_image_extract(self):
filename = filedialog.askopenfilename(filetypes=[("Images", "*.png *.jpg *.jpeg *.bmp")])
if filename:
self.image_path.set(filename)
# Fonctions pour insérer et extraire le texte
def insert_text(self):
image_path = self.image_path.get()
output_path = self.output_path.get()
text = self.text_to_hide.get("1.0", tk.END).strip()
password = self.password.get()
if not all([image_path, output_path, text, password]):
messagebox.showerror("Erreur", "Tous les champs sont obligatoires !")
return
success, message = insert_text_in_image(image_path, output_path, text, password)
if success:
messagebox.showinfo("Succès", message)
else:
messagebox.showerror("Erreur", message)
def extract_text(self):
image_path = self.image_path.get()
password = self.password.get()
if not all([image_path, password]):
messagebox.showerror("Erreur", "Tous les champs sont obligatoires !")
return
success, message = extract_text_from_image(image_path, password)
if success:
self.result_area.delete("1.0", tk.END)
self.result_area.insert("1.0", message)
else:
messagebox.showerror("Erreur", message)
# --- Lancement de l'application ---
if __name__ == "__main__":
root = tk.Tk()
app = SteganographyApp(root)
root.mainloop()