282 lines
11 KiB
Python
282 lines
11 KiB
Python
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() |