diff --git a/scripts/show_decrypted_texts.py b/scripts/show_decrypted_texts.py new file mode 100644 index 0000000..18d6af6 --- /dev/null +++ b/scripts/show_decrypted_texts.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Affiche les textes déchiffrés pour chaque mission en utilisant l'analyzer correspondant. +""" +import sys +from pathlib import Path +from typing import Dict, Tuple, Type + +sys.path.append('.') + +from src.analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer +from src.analyzers.chacha20_analyzer import ChaCha20_Analyzer +from src.analyzers.blowfish_analyzer import Blowfish_Analyzer +from src.analyzers.aes_gcm_analyzer import Aes_Gcm_Analyzer +from src.analyzers.fernet_analyzer import FernetAnalyzer +from src.utils import verifier_texte_dechiffre + +MISSIONS: Dict[str, Tuple[str, Type]] = { + 'AES-CBC': ('data/mission1.enc', Aes_Cbc_Analyzer), + 'ChaCha20': ('data/mission2.enc', ChaCha20_Analyzer), + 'Blowfish': ('data/mission3.enc', Blowfish_Analyzer), + 'AES-GCM': ('data/mission4.enc', Aes_Gcm_Analyzer), + 'Fernet': ('data/mission5.enc', FernetAnalyzer), +} + +WORDLIST = 'keys/wordlist.txt' + + +def main() -> None: + if not Path(WORDLIST).exists(): + print(f"Wordlist manquante: {WORDLIST}") + sys.exit(1) + + for algo, (mission_path, AnalyzerCls) in MISSIONS.items(): + print("=" * 70) + print(f"{algo} -> {mission_path}") + if not Path(mission_path).exists(): + print(f"Fichier introuvable: {mission_path}") + continue + + analyzer = AnalyzerCls() + try: + cles = analyzer.generer_cles_candidates(WORDLIST) + except Exception as e: + print(f"Erreur génération clés: {e}") + cles = [] + + meilleure_stat = -1.0 + meilleur_texte_b = b'' + meilleure_cle = None + + for cle in cles: + try: + res = analyzer.dechiffrer(mission_path, cle) + except Exception: + continue + if not res: + continue + # Sanitize and score + texte = res.decode('utf-8', errors='ignore').replace('\x00', ' ') + try: + taux = float(verifier_texte_dechiffre(texte).get('taux_succes', 0.0)) + except Exception: + taux = 0.0 + if taux > meilleure_stat: + meilleure_stat = taux + meilleur_texte_b = res + meilleure_cle = cle + # Early stop on good plaintext + if taux >= 60.0: + break + + if meilleure_cle is None: + print("❌ Aucun texte déchiffré trouvé") + else: + texte = meilleur_texte_b.decode('utf-8', errors='ignore') + print(f"✅ Meilleur taux: {meilleure_stat:.2f}%") + print("--- TEXTE DÉCHIFFRÉ ---") + print(texte.strip()) + print("------------------------") + +if __name__ == '__main__': + main() diff --git a/src/analyzers/aes_gcm_analyzer.py b/src/analyzers/aes_gcm_analyzer.py index 64856cb..93e96ba 100644 --- a/src/analyzers/aes_gcm_analyzer.py +++ b/src/analyzers/aes_gcm_analyzer.py @@ -1,6 +1,7 @@ from src.crypto_analyzer import CryptoAnalyzer from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from typing import List import re @@ -23,43 +24,30 @@ class Aes_Gcm_Analyzer(CryptoAnalyzer): _PBKDF2_ITERATIONS: int = 10000 #Fourni _PBKDF2_LONGUEUR_CLE: int = 32 #Longueur de la clé - def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> List[str]: + def __filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> List[str]: """ Filtre le dictionnaire en se basant sur les indices de la mission 4. L'indice pointe vers le format de clé "Acronyme en majuscules + 4 chiffres". - - Args: - chemin_dictionnaire(str): Le chemin vers le fichier de dictionnaire. - - Returns: - list[str]: Une liste de mots de passe filtrés. """ mots_filtres: List[str] = [] - - # L'année courante - annee_courante: str = "2024" #Normalement 2025 mais on considère 2024 pour se conformer à la wordlist - - # Définition du motif d'acronyme de 4 lettres en majuscules - # On utilise une expression régulière pour plus de robustesse + annee_courante: str = "2024" # Normalement 2025 mais on considère 2024 pour se conformer à la wordlist motif_acronyme = re.compile(r"^[A-Z]{4}$") - + try: with open(chemin_dictionnaire, "r", encoding="utf-8") as f: for ligne in f: mot: str = ligne.strip() - - # Vérifie si le mot de passe correspond au format de l'indice - # ex: NATO2024, UN2024, etc. if mot.endswith(annee_courante): - acronyme: str = mot[:-4] # Extrait la partie acronyme + acronyme: str = mot[:-4] if motif_acronyme.match(acronyme): mots_filtres.append(mot) - except FileNotFoundError: print(f"Erreur : Le fichier de dictionnaire '{chemin_dictionnaire}' est introuvable.") return [] - + return mots_filtres + + def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]: ''' @@ -72,7 +60,7 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]: list[bytes]: liste des clés candidates. ''' - mots_de_passe_cible: List[str] = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire) + mots_de_passe_cible: List[str] = self.__filtrer_dictionnaire_par_indices(chemin_dictionnaire) clees_candidates: List[bytes] = [] @@ -151,6 +139,11 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float: if probabilite >= 0.5: probabilite += 0.1 + # Normalisation du score dans [0.0, 1.0] + if probabilite > 1.0: + probabilite = 1.0 + if probabilite < 0.0: + probabilite = 0.0 return probabilite except FileNotFoundError: @@ -172,8 +165,41 @@ def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: bytes: Le contenu déchiffré ou une chaîne vide en cas d'échec. """ try: - # TODO: Implémenter la logique de déchiffrement AES GCM + # Validation taille de clé: AES-256 => 32 octets + if len(cle_donnee) != self._PBKDF2_LONGUEUR_CLE: + raise ValueError("Erreur : La clé AES-256 doit faire 32 bytes") + + # Lecture du fichier: nonce (12B) + données + tag (16B) + with open(chemin_fichier_chiffre, "rb") as f: + donnees = f.read() + + if len(donnees) < 12 + 16: + return b"" + + nonce = donnees[:12] + ciphertext_tag = donnees[12:] + if len(ciphertext_tag) < 16: + return b"" + ciphertext = ciphertext_tag[:-16] + tag = ciphertext_tag[-16:] + + # Déchiffrement AES-GCM + cipher = Cipher(algorithms.AES(cle_donnee), modes.GCM(nonce, tag)) + decryptor = cipher.decryptor() + try: + plaintext = decryptor.update(ciphertext) + decryptor.finalize() + return plaintext + except Exception: + # Tag invalide / clé incorrecte + return b"" + + except FileNotFoundError: + raise + except ValueError as e: + # Erreur de validation de clé + if "doit faire 32 bytes" in str(e): + raise return b"" except Exception as e: - print(f"Erreur lors du déchiffrement: {e}") + # Erreur générique return b"" diff --git a/src/analyzers/blowfish_analyzer.py b/src/analyzers/blowfish_analyzer.py index 5cb6ff9..0031765 100644 --- a/src/analyzers/blowfish_analyzer.py +++ b/src/analyzers/blowfish_analyzer.py @@ -3,9 +3,8 @@ import hashlib import base64 import re -from cryptography.hazmat.primitives.ciphers import Cipher, modes +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.padding import PKCS7 -from cryptography.hazmat.decrepit.ciphers.algorithms import Blowfish class Blowfish_Analyzer(CryptoAnalyzer): '''Détermine si l'algo blowfish est utilisé, génère des clés et tente de de déchffrer un fichier chiffré en utilisant les clés générées. @@ -72,36 +71,25 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float: return score - def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> list[str]: + def __filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> list[str]: """ Filtre le dictionnaire en se basant sur les indices de la mission 3. L'indice pointe vers un format de clé "sha + nombre + chiffres simples". - - Args: - chemin_dictionnaire(str): Le chemin vers le fichier de dictionnaire. - - Returns: - list[str]: Une liste de mots de passe filtrés. """ mots_filtres: list[str] = [] - - # Indices pour le préfixe et le suffixe - prefixes = ("sha256", "sha384", "sha512", "sha1") + prefixes = ("sha256", "sha384", "sha512", "sha1") suffixes = ("123", "456", "789") - + try: with open(chemin_dictionnaire, "r", encoding="utf-8") as f: for ligne in f: mot = ligne.strip() - - # Vérifie si le mot commence par un préfixe et se termine par un suffixe if mot.startswith(prefixes) and mot.endswith(suffixes): mots_filtres.append(mot) - except FileNotFoundError: print(f"Erreur : Le fichier de dictionnaire '{chemin_dictionnaire}' est introuvable.") return [] - + return mots_filtres def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: @@ -117,7 +105,7 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: """ cles_candidates: list[bytes] = [] # Utilisation de la méthode privée pour filtrer les mots - mots_de_passe_cible = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire) + mots_de_passe_cible = self.__filtrer_dictionnaire_par_indices(chemin_dictionnaire) for mot in mots_de_passe_cible: mot_en_bytes = mot.encode("utf-8") @@ -165,14 +153,13 @@ def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: raise ValueError('Taille de clé invalide.') try: - - algorithm_blowfish = Blowfish(self.decode_base64(cle_donnee)) - texte_chiffre = '' + # Use the key directly, not base64 decoded + algorithm_blowfish = algorithms.Blowfish(cle_donnee) #Récupération de l'IV et du texte chiffré dans le fichier with open(chemin_fichier_chiffre, 'rb') as f: donnees = f.read() - f.close() + initialization_vector = donnees[:self.__BLOWFISH_TAILLE_IV] texte_chiffre = donnees[self.__BLOWFISH_TAILLE_IV:] @@ -180,8 +167,8 @@ def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: cipher = Cipher(algorithm_blowfish, modes.CBC(initialization_vector)) decrypteur = cipher.decryptor() - #Suppresseur de padding - supresseur_padding = PKCS7(self.__BLOWFISH_TAILLE_BLOC).unpadder() + #Suppresseur de padding - PKCS7 uses bits, not bytes + supresseur_padding = PKCS7(64).unpadder() # 64 bits = 8 bytes #Décriptage des données avec le padding(remplissage aléatoire) donnees_chiffrees_avec_padding = decrypteur.update(texte_chiffre) + decrypteur.finalize() diff --git a/src/analyzers/chacha20_analyzer.py b/src/analyzers/chacha20_analyzer.py index 6b0629c..3ff0603 100644 --- a/src/analyzers/chacha20_analyzer.py +++ b/src/analyzers/chacha20_analyzer.py @@ -1,7 +1,6 @@ # Import des modules import hashlib -from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 -from cryptography.exceptions import InvalidTag +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms from rich import print import os import sys @@ -87,26 +86,46 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float: print(f"Erreur lors de l'identification de l'algorithme: {e}") return 0.0 - def __filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> List[bytes]: + def __filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> List[str]: """ - Cette fonction a pour but de filter le fichier de dictionnaire en fonction des différents niveaux d'indices - pour déterminer les données les plus pertinentes. + Filtre le dictionnaire selon les indices de mission pour sélectionner les mots pertinents. + + - Prioritaire: motifs "2024" + mot anglais en minuscules (ex: 2024hello) + - Secondaire: 4 chiffres + mot anglais en minuscules (ex: 1337secret) Args: chemin_dictionnaire(str): Le chemin vers le dictionnaire fourni Returns: - list[bytes]: La liste de tous les mots susceptibles d'être des clés adéquates. + List[str]: Les mots candidats conformément aux indices (prioritaires si présents, sinon secondaires). """ + candidats_prioritaires: List[str] = [] + candidats_secondaires: List[str] = [] + try: - with open(chemin_dictionnaire, 'rb') as f: - cle = f.readlines() - return cle + with open(chemin_dictionnaire, 'r', encoding='utf-8') as f: + for ligne in f: + mot = ligne.strip() + if not mot: + continue + + # Pattern principal des indices: 2024 + mot anglais simple + if len(mot) >= 6 and mot.startswith('2024') and mot[4:].isalpha() and mot[4:].islower(): + candidats_prioritaires.append(mot) + continue + + # Pattern secondaire: 4 chiffres + mot anglais simple (fallback si aucune clé prioritaire) + if len(mot) >= 6 and mot[:4].isdigit() and mot[4:].isalpha() and mot[4:].islower(): + candidats_secondaires.append(mot) + except FileNotFoundError: print(f"Erreur : Le fichier de dictionnaire '{chemin_dictionnaire}' est introuvable.") return [] + # Retourner d'abord les candidats prioritaires, sinon les secondaires + return candidats_prioritaires if candidats_prioritaires else candidats_secondaires + def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]: """ Cette fonction se charge de générer les clés candidates pour le déchifremment du fichier chiffré en utilisant @@ -118,9 +137,17 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]: Returns: cles_candidates (List[bytes]) : Un tableau de clés, chaque clé étant une séquence d'octets. """ - # Pour l'instant, retourner une liste vide comme attendu par le test - # TODO: Implémenter la logique de génération de clés candidates - return [] + cles_candidates: List[bytes] = [] + + # Utiliser la méthode de filtrage harmonisée + candidats: List[str] = self.__filtrer_dictionnaire_par_indices(chemin_dictionnaire) + + for cand in candidats: + # Dérivation clé: SHA256 du mot de passe (indices) + cle = hashlib.sha256(cand.encode('utf-8')).digest() + cles_candidates.append(cle) + + return cles_candidates def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: """ @@ -136,26 +163,29 @@ def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: # Validation de la taille de clé (ChaCha20 nécessite 32 bytes) if len(cle_donnee) != self._CHACHA20_LONGUEUR_CLE: raise ValueError("Erreur : La clé n'a pas la taille correcte") - + try: with open(chemin_fichier_chiffre, 'rb') as f: - nonce: bytes = f.read(self._CHACHA20_LONGUEUR_NONCE) - texte_chiffre: bytes = f.read() + nonce_12: bytes = f.read(self._CHACHA20_LONGUEUR_NONCE) + payload: bytes = f.read() + if len(nonce_12) != self._CHACHA20_LONGUEUR_NONCE or len(payload) == 0: + return b"" + + # ChaCha20 stream (cryptography attend un nonce 16B) + # Construire un nonce 16B en préfixant 4 octets nuls au nonce 12B + nonce_16 = b"\x00\x00\x00\x00" + nonce_12 try: - aead = ChaCha20Poly1305(cle_donnee) - resultat: bytes = aead.decrypt(nonce, texte_chiffre, None) + cipher = Cipher(algorithms.ChaCha20(cle_donnee, nonce_16), mode=None) + decryptor = cipher.decryptor() + resultat: bytes = decryptor.update(payload) + decryptor.finalize() return resultat - except Exception as e: - # Erreur de déchiffrement (clé incorrecte, tag invalide) + except Exception: return b"" except FileNotFoundError: raise - except InvalidTag: - # Erreur de déchiffrement (clé incorrecte, tag invalide) - return b"" - except Exception as e: + except Exception: # Erreur de déchiffrement (clé incorrecte, format invalide) return b"" diff --git a/src/analyzers/fernet_analyzer.py b/src/analyzers/fernet_analyzer.py index 0e7a600..78e721f 100644 --- a/src/analyzers/fernet_analyzer.py +++ b/src/analyzers/fernet_analyzer.py @@ -85,16 +85,13 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float: return score - def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> List[str]: + def __filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> List[str]: """ Filtre le dictionnaire en se basant sur les indices de la mission 5. L'indice pointe vers le format "Phrase complète en français minuscules avec espaces". Cette méthode cherche des phrases en minuscules de plus de 5 caractères avec au moins un espace. - Args: - chemin_dictionnaire (str): Le chemin vers le fichier de dictionnaire. - Returns: List[str]: Une liste de mots de passe (phrases) filtrés. """ @@ -104,17 +101,14 @@ def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> List[st with open(chemin_dictionnaire, "r", encoding="utf-8") as f: for ligne in f: mot = ligne.strip() - - # Vérifie si le mot est en minuscules, contient au moins un espace et a une longueur raisonnable. if mot.islower() and ' ' in mot and len(mot) > 5: mots_filtres.append(mot) - except FileNotFoundError: print(f"Erreur : Le fichier de dictionnaire '{chemin_dictionnaire}' est introuvable.") return [] return mots_filtres - + def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]: """ Génère une liste de clés candidates Fernet (32 octets) en dérivant @@ -126,7 +120,7 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]: Returns: List[bytes]: Une liste des clés candidates. """ - mots_de_passe_cible = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire) + mots_de_passe_cible = self.__filtrer_dictionnaire_par_indices(chemin_dictionnaire) cles_candidates: List[bytes] = [] for mot_de_passe in mots_de_passe_cible: diff --git a/test_identifier_algo.py b/test_identifier_algo.py new file mode 100644 index 0000000..f18fa40 --- /dev/null +++ b/test_identifier_algo.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +""" +Script de test croisé pour les méthodes identifier_algo de tous les analyzers +sur tous les fichiers de mission. + +Ce script teste chaque analyzer sur chaque fichier mission pour vérifier +que les heuristiques de détection fonctionnent correctement. +""" + +import sys +import os +from pathlib import Path + +# Ajouter le répertoire racine au path +sys.path.append('.') + +def test_identifier_algo(): + """Test croisé de toutes les méthodes identifier_algo""" + + print("=" * 60) + print("TEST CROISÉ DES MÉTHODES IDENTIFIER_ALGO") + print("=" * 60) + + # Import des analyzers + try: + from src.analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer + from src.analyzers.aes_gcm_analyzer import Aes_Gcm_Analyzer + from src.analyzers.chacha20_analyzer import ChaCha20_Analyzer + from src.analyzers.blowfish_analyzer import Blowfish_Analyzer + from src.analyzers.fernet_analyzer import FernetAnalyzer + print("✅ Tous les analyzers importés avec succès") + except Exception as e: + print(f"❌ Erreur d'import: {e}") + return + + # Configuration des analyzers + analyzers = { + 'AES-CBC': Aes_Cbc_Analyzer(), + 'AES-GCM': Aes_Gcm_Analyzer(), + 'ChaCha20': ChaCha20_Analyzer(), + 'Blowfish': Blowfish_Analyzer(), + 'Fernet': FernetAnalyzer() + } + + # Configuration des missions avec leurs algorithmes attendus + missions = { + 'mission1.enc': 'AES-CBC', + 'mission2.enc': 'ChaCha20', + 'mission3.enc': 'Blowfish', + 'mission4.enc': 'AES-GCM', + 'mission5.enc': 'Fernet' + } + + # Vérification de l'existence des fichiers + print("\n📁 Vérification des fichiers de mission:") + for mission_file in missions.keys(): + file_path = Path(f'data/{mission_file}') + if file_path.exists(): + size = file_path.stat().st_size + print(f" ✅ {mission_file} ({size} bytes)") + else: + print(f" ❌ {mission_file} - FICHIER MANQUANT") + return + + print("\n" + "=" * 60) + print("RÉSULTATS DES TESTS") + print("=" * 60) + + # Matrice des résultats + results = {} + + # Test de chaque analyzer sur chaque mission + for mission_file, expected_algo in missions.items(): + print(f"\n🎯 {mission_file} (Attendu: {expected_algo})") + print("-" * 50) + + results[mission_file] = {} + + for analyzer_name, analyzer in analyzers.items(): + try: + score = analyzer.identifier_algo(f'data/{mission_file}') + results[mission_file][analyzer_name] = score + + # Formatage avec indicateurs visuels + if analyzer_name == expected_algo: + if score >= 0.8: + status = "🟢 EXCELLENT" + elif score >= 0.6: + status = "🟡 BON" + elif score >= 0.4: + status = "🟠 MOYEN" + else: + status = "🔴 FAIBLE" + else: + if score <= 0.2: + status = "✅ Correct (faible)" + elif score <= 0.4: + status = "⚠️ Attention" + else: + status = "❌ FAUX POSITIF" + + print(f" {analyzer_name:10}: {score:.3f} - {status}") + + except Exception as e: + results[mission_file][analyzer_name] = None + print(f" {analyzer_name:10}: ERROR - {str(e)[:50]}...") + + # Résumé des performances + print("\n" + "=" * 60) + print("RÉSUMÉ DES PERFORMANCES") + print("=" * 60) + + correct_detections = 0 + total_tests = 0 + + for mission_file, expected_algo in missions.items(): + if mission_file in results and expected_algo in results[mission_file]: + score = results[mission_file][expected_algo] + if score is not None: + total_tests += 1 + if score >= 0.6: # Seuil de détection acceptable + correct_detections += 1 + print(f"✅ {mission_file}: {expected_algo} détecté avec {score:.3f}") + else: + print(f"❌ {mission_file}: {expected_algo} mal détecté ({score:.3f})") + else: + print(f"💥 {mission_file}: {expected_algo} - ERREUR") + + # Statistiques finales + if total_tests > 0: + success_rate = (correct_detections / total_tests) * 100 + print(f"\n📊 TAUX DE RÉUSSITE: {correct_detections}/{total_tests} ({success_rate:.1f}%)") + + if success_rate >= 80: + print("🎉 EXCELLENT - Les heuristiques fonctionnent bien!") + elif success_rate >= 60: + print("👍 BON - Quelques améliorations possibles") + else: + print("⚠️ ATTENTION - Les heuristiques nécessitent des corrections") + + # Détection des faux positifs + print(f"\n🔍 ANALYSE DES FAUX POSITIFS:") + false_positives = [] + + for mission_file, expected_algo in missions.items(): + if mission_file in results: + for analyzer_name, score in results[mission_file].items(): + if analyzer_name != expected_algo and score is not None and score > 0.4: + false_positives.append((mission_file, analyzer_name, score)) + + if false_positives: + print(" ⚠️ Faux positifs détectés:") + for mission, analyzer, score in false_positives: + print(f" - {mission}: {analyzer} = {score:.3f}") + else: + print(" ✅ Aucun faux positif significatif détecté") + + print("\n" + "=" * 60) + print("TEST TERMINÉ") + print("=" * 60) + +if __name__ == "__main__": + test_identifier_algo() \ No newline at end of file diff --git a/tests/test_dechiffrement_missions.py b/tests/test_dechiffrement_missions.py new file mode 100644 index 0000000..ef5ecf4 --- /dev/null +++ b/tests/test_dechiffrement_missions.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +Test de déchiffrement pour chaque mission avec l'analyzer correspondant. +- Utilise keys/wordlist.txt pour générer les clés candidates +- Tente le déchiffrement et valide le texte avec utils.verifier_texte_dechiffre +- Affiche un récapitulatif des succès/échecs +""" + +import sys +from pathlib import Path +from typing import Dict, Tuple, Type + +# Assurer l'import du projet +sys.path.append('.') + +from src.analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer +from src.analyzers.chacha20_analyzer import ChaCha20_Analyzer +from src.analyzers.blowfish_analyzer import Blowfish_Analyzer +from src.analyzers.aes_gcm_analyzer import Aes_Gcm_Analyzer +from src.analyzers.fernet_analyzer import FernetAnalyzer +from src.utils import verifier_texte_dechiffre + +# Mapping missions -> (fichier, analyzer class) +MISSIONS: Dict[str, Tuple[str, Type]] = { + 'AES-CBC': ('data/mission1.enc', Aes_Cbc_Analyzer), + 'ChaCha20': ('data/mission2.enc', ChaCha20_Analyzer), + 'Blowfish': ('data/mission3.enc', Blowfish_Analyzer), + 'AES-GCM': ('data/mission4.enc', Aes_Gcm_Analyzer), + 'Fernet': ('data/mission5.enc', FernetAnalyzer), +} + +WORDLIST = 'keys/wordlist.txt' + + +def test_dechiffrement_missions() -> None: + print('=' * 70) + print('TEST DE DECHIFFREMENT PAR MISSION') + print('=' * 70) + + global_success = 0 + total = 0 + + # Vérification préliminaire + if not Path(WORDLIST).exists(): + print(f"❌ Wordlist manquante: {WORDLIST}") + return + + for algo, (mission_path, AnalyzerCls) in MISSIONS.items(): + total += 1 + print(f"\n🎯 Mission: {mission_path} | Analyzer attendu: {algo}") + if not Path(mission_path).exists(): + print(f" ❌ Fichier introuvable: {mission_path}") + continue + + analyzer = AnalyzerCls() + + # Génération des clés + try: + cles = analyzer.generer_cles_candidates(WORDLIST) + print(f" 🔑 Clés candidates générées: {len(cles)}") + except Exception as e: + print(f" 💥 Erreur génération clés: {e}") + cles = [] + + if not cles: + print(" ⚠️ Aucune clé candidate (le test peut échouer)") + + # Essais de déchiffrement + trouve = False + meilleure_stat = 0.0 + meilleure_cle = None + meilleur_texte = b'' + + for idx, cle in enumerate(cles): + try: + res = analyzer.dechiffrer(mission_path, cle) + except ValueError as ve: + # clés de taille invalide pour l'algo + continue + except FileNotFoundError: + print(f" 💥 Fichier introuvable pendant le test: {mission_path}") + break + except Exception: + # Toute autre erreur: considérer comme tentative échouée + continue + + if not res: + continue + + try: + texte = res.decode('utf-8', errors='ignore') + except Exception: + texte = '' + + stats = verifier_texte_dechiffre(texte) + taux = float(stats.get('taux_succes', 0.0)) + if taux > meilleure_stat: + meilleure_stat = taux + meilleure_cle = cle + meilleur_texte = res + + # Seuil de succès raisonnable + if taux >= 60.0: + trouve = True + break + + if trouve: + global_success += 1 + print(f" ✅ Déchiffrement RÉUSSI | Taux succès: {meilleure_stat:.2f}%") + else: + # Note: AES-GCM n'a pas d'implémentation de déchiffrement -> probablement échec + hint = '' + if algo == 'AES-GCM': + hint = " (implémentation dechiffrer() absente)" + print(f" ❌ Déchiffrement ÉCHEC{hint} | Meilleur taux: {meilleure_stat:.2f}%") + + print('\n' + '=' * 70) + print(f"RÉSUMÉ: {global_success}/{total} missions déchiffrées") + if global_success == total: + print('🎉 Tous les déchiffrements ont réussi !') + else: + print('⚠️ Certains déchiffrements ont échoué. Voir détails ci-dessus.') + + +if __name__ == '__main__': + test_dechiffrement_missions()