diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..49bf6ed --- /dev/null +++ b/build.bat @@ -0,0 +1 @@ +python3 -m PyInstaller --onefile --windowed --icon=mirage.ico --name=Mirage mirage.py \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..de05362 --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +python3 -m PyInstaller --onefile --windowed --icon=mirage_icon.ico --add-data "mirage_icon.ico:." --name=Mirage mirage.py \ No newline at end of file diff --git a/mirage.ico b/mirage.ico new file mode 100644 index 0000000..61d9213 Binary files /dev/null and b/mirage.ico differ diff --git a/mirage.py b/mirage.py new file mode 100644 index 0000000..b087c6d --- /dev/null +++ b/mirage.py @@ -0,0 +1,282 @@ +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() \ No newline at end of file diff --git a/requierments.txt b/requierments.txt new file mode 100644 index 0000000..bebd196 --- /dev/null +++ b/requierments.txt @@ -0,0 +1,3 @@ +pillow>=9.0.0 +numpy>=1.21.0 +cryptography>=36.0.0 \ No newline at end of file