From 80c1f1b10606b12f0b9376c044a63c425c881a0a Mon Sep 17 00:00:00 2001 From: Eunock-web Date: Sat, 2 Aug 2025 06:20:34 +0200 Subject: [PATCH 01/26] =?UTF-8?q?Int=C3=A9gration=20de=20AesCbcAnalyzer=20?= =?UTF-8?q?dans=20DetecteurCryptoOrchestrateur.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/detecteur_crypto.py | 104 +++++++++++++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 11 deletions(-) diff --git a/src/detecteur_crypto.py b/src/detecteur_crypto.py index 45c6775..ef27e8f 100644 --- a/src/detecteur_crypto.py +++ b/src/detecteur_crypto.py @@ -1,17 +1,99 @@ -import AesCbcAnalyzer -from crypto_analyzer import identifier_algo +# Import des modules +import os + +# Import des modules d'analyse +from .analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer + +class ResultatAnalyse: + """ + Classe représentant un résultat d'analyse. + """ + def __init__(self, algo: str, cle: bytes, texte_dechiffre: bytes): + self.algo = algo + self.cle = cle + self.texte_dechiffre = texte_dechiffre -""" - Classe principale qui centralise tout: - -Lance l’analyse des fichiers et identifie l'algorithme probable, - -Lance les attaquespar dictionnaire, - -Lance et coordonnes le processus de dechiffrement -""" class DetecteurCryptoOrchestrateur: """ - Initialisation de l'analyseur AES-CBC + Classe principale qui centralise tout: + -Lance l'analyse des fichiers et identifie l'algorithme probable, + -Lance les attaques par dictionnaire, + -Lance et coordonnes le processus de dechiffrement """ + def __init__(self): - self.aes_cbc_analyzer = AesCbcAnalyzer() - + """ + Initialisation de tous les modules d'analyse disponibles (AES-CBC) pour le moment + """ + self.analyzers = { + "AES-CBC": Aes_Cbc_Analyzer(), + } + def Analyser_fichier_uniquement(self, chemin_fichier_chiffre: str) -> ResultatAnalyse: + """ + Analyse un seul fichier chiffré et retourne le résultat de l'analyse. + + Args: + chemin_fichier_chiffre(str): chemin du fichier chiffré à analyser + + Returns: + ResultatAnalyse: résultat de l'analyse + """ + try: + #Initialisation des variables + algorithme_detecte = "" + cle = b"" + texte_dechiffre = b"" + + #Parcours des algorithmes disponibles + for nom_algo, analyzer in self.analyzers.items(): + score_probabilite = analyzer.identifier_algo(chemin_fichier_chiffre) # ← Retourne un float + print(f"{nom_algo}: score {score_probabilite:.2f}") + + if score_probabilite > 0.5: # ← Comparaison correcte avec le seuil + algorithme_detecte = nom_algo # ← Stockage du nom de l'algorithme + print(f"Algorithme détecté: {algorithme_detecte} (score: {score_probabilite:.2f})") + + # Génération des clés candidates + cles_candidates = analyzer.generer_cles_candidates("dicoEn") + print(f"{len(cles_candidates)} clés candidates générées") + + # Test de déchiffrement avec la première clé (pour l'exemple) + if cles_candidates: + texte_dechiffre = analyzer.dechiffrer(chemin_fichier_chiffre, cles_candidates[0]) + if texte_dechiffre: + cle = cles_candidates[0] + print("Déchiffrement réussi avec la première clé!") + else: + print("Déchiffrement échoué avec la première clé") + break + + if not algorithme_detecte: + print("Aucun algorithme détecté avec confiance suffisante") + + return ResultatAnalyse(algorithme_detecte, cle, texte_dechiffre) + except Exception as e: + print(f"Erreur lors de l'analyse du fichier {chemin_fichier_chiffre}: {str(e)}") + return ResultatAnalyse("", b"", b"") + + + def Analyser_fichiers_sequentiels(self, dossier_chiffres: str) -> list[ResultatAnalyse]: + """ + Analyse plusieurs fichiers chiffrés dans un dossier et retourne les résultats de l'analyse. + + Args: + dossier_chiffres(str): dossier contenant les fichiers chiffrés à analyser + + Returns: + list[ResultatAnalyse]: liste des résultats d'analyse pour chaque fichier + """ + try: + resultats = [] + for fichier_chiffre in os.listdir(dossier_chiffres): + chemin_fichier_chiffre = os.path.join(dossier_chiffres, fichier_chiffre) + resultat = self.Analyser_fichier_uniquement(chemin_fichier_chiffre) + resultats.append(resultat) + return resultats + except Exception as e: + print(f"Erreur lors de l'analyse des fichiers: {str(e)}") + return [] \ No newline at end of file From a41256c29fe1093e8f9e67072cffdf19d0ef6683 Mon Sep 17 00:00:00 2001 From: Eunock-web Date: Sat, 2 Aug 2025 16:38:41 +0200 Subject: [PATCH 02/26] Debut de l'implementation du code dans la classe principale avec le menu --- src/detecteur_crypto.py | 6 +-- src/interface_console.py | 80 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/detecteur_crypto.py b/src/detecteur_crypto.py index ef27e8f..8e25c99 100644 --- a/src/detecteur_crypto.py +++ b/src/detecteur_crypto.py @@ -47,11 +47,11 @@ def Analyser_fichier_uniquement(self, chemin_fichier_chiffre: str) -> ResultatAn #Parcours des algorithmes disponibles for nom_algo, analyzer in self.analyzers.items(): - score_probabilite = analyzer.identifier_algo(chemin_fichier_chiffre) # ← Retourne un float + score_probabilite = analyzer.identifier_algo(chemin_fichier_chiffre) # Retourne un float print(f"{nom_algo}: score {score_probabilite:.2f}") - if score_probabilite > 0.5: # ← Comparaison correcte avec le seuil - algorithme_detecte = nom_algo # ← Stockage du nom de l'algorithme + if score_probabilite > 0.5: # Comparaison correcte avec le seuil + algorithme_detecte = nom_algo # Stockage du nom de l'algorithme print(f"Algorithme détecté: {algorithme_detecte} (score: {score_probabilite:.2f})") # Génération des clés candidates diff --git a/src/interface_console.py b/src/interface_console.py index 5f8b70b..8e91cfe 100644 --- a/src/interface_console.py +++ b/src/interface_console.py @@ -3,6 +3,8 @@ from rich.markdown import Markdown from rich import print from rich.prompt import Prompt +from detecteur_crypto import Analyser_fichier_uniquement +from detecteur_crypto import Analyser_fichier_sequentiels import time, os install() @@ -50,7 +52,81 @@ def default_menu(self): self.console.print(menuTag,menuOption) time.sleep(0.04) - self.prompt.ask("Veuillez choisir une option ",choices=["1","2","3","4","5","6"]) + choix = self.prompt.ask("Veuillez choisir une option ",choices=["1","2","3","4","5","6"]) + if choix == "1": + self.menu_1() + elif choix == "2": + self.menu_2() + elif choix == "3": + self.menu_3() + elif choix == "4": + self.menu_4() + elif choix == "5": + self.menu_5() + elif choix == "6": + self.menu_6() -consoleInterface() \ No newline at end of file + def menu_1(self): + self.console.clear() + self.dynamiqueText("Analyse d'un fichier spécifique","green") + self.dynamiqueText("Veuillez entrer le chemin du fichier :","white") + time.sleep(0.04) + chemin_fichier = self.prompt.ask("Veuillez entrer le chemin du fichier : ") + resultat = Analyser_fichier_uniquement(self,chemin_fichier) + self.console.clear() + self.dynamiqueText("Analyse en cours...","green") + time.sleep(0.04) + self.console.clear() + self.dynamiqueText("Analyse terminée","green") + time.sleep(0.04) + self.default_menu() + + def menu_2(self): + self.console.clear() + self.dynamiqueText("Mission complète automatique","green") + self.dynamiqueText("Veuillez entrer le chemin du dossier :","white") + time.sleep(0.04) + chemin_dossier = self.prompt.ask("Veuillez entrer le chemin du dossier : ") + self.console.clear() + self.dynamiqueText("Mission en cours...","green") + time.sleep(0.04) + self.console.clear() + self.dynamiqueText("Mission terminée","green") + time.sleep(0.04) + self.default_menu() + + def menu_3(self): + self.console.clear() + self.dynamiqueText("Attaque par dictionnaire manuelle","green") + self.dynamiqueText("Veuillez entrer le chemin du fichier :","white") + time.sleep(0.04) + chemin_fichier = self.prompt.ask("Veuillez entrer le chemin du fichier : ") + self.console.clear() + self.dynamiqueText("Attaque en cours...","green") + time.sleep(0.04) + self.console.clear() + self.dynamiqueText("Attaque terminée","green") + time.sleep(0.04) + self.default_menu() + + def menu_4(self): + self.console.clear() + self.dynamiqueText("Affichage des rapports","green") + time.sleep(0.04) + self.default_menu() + + def menu_5(self): + self.console.clear() + self.dynamiqueText("Système d'aide intégré","green") + time.sleep(0.04) + self.default_menu() + + def menu_6(self): + self.console.clear() + self.dynamiqueText("Au revoir !","green") + time.sleep(0.04) + self.console.clear() + self.console.exit() + +consoleInterface() From f40bb5916365fd5459b8293ca627d506c85d3c14 Mon Sep 17 00:00:00 2001 From: e-mandy Date: Sat, 2 Aug 2025 15:40:23 +0100 Subject: [PATCH 03/26] Documentation de la fonction d'entropie --- src/utils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/utils.py b/src/utils.py index 62ab214..92f5308 100644 --- a/src/utils.py +++ b/src/utils.py @@ -3,7 +3,16 @@ def valider_texte_dechiffre(texte): pass -def calculer_entropie(bytes): +def calculer_entropie(bytes) -> float: + ''' + Calcul l'entropie (le désordre dans une suite de données) afin de déterminer le degré d'improbabilité d'une chaine de données. + + Args: + bytes(bytes): La donnée brute contenue dans le fichier crypté. + + Returns: + float: l'entropie calculée. + ''' entropie = 0 proba_byte = 0 for specifique_byte in bytes: From cac3bf59d22738e187a90332460fa2bd42f20187 Mon Sep 17 00:00:00 2001 From: Eunock-web Date: Sat, 2 Aug 2025 16:50:56 +0200 Subject: [PATCH 04/26] Suite de l'implementation du code dans la classe principale avec le menu --- src/interface_console.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/interface_console.py b/src/interface_console.py index 8e91cfe..0eb9537 100644 --- a/src/interface_console.py +++ b/src/interface_console.py @@ -53,19 +53,27 @@ def default_menu(self): time.sleep(0.04) choix = self.prompt.ask("Veuillez choisir une option ",choices=["1","2","3","4","5","6"]) - if choix == "1": - self.menu_1() - elif choix == "2": - self.menu_2() - elif choix == "3": - self.menu_3() - elif choix == "4": - self.menu_4() - elif choix == "5": - self.menu_5() - elif choix == "6": - self.menu_6() - + try: + if choix == "1": + self.menu_1() + elif choix == "2": + self.menu_2() + elif choix == "3": + self.menu_3() + elif choix == "4": + self.menu_4() + elif choix == "5": + self.menu_5() + elif choix == "6": + self.menu_6() + + while choix > "6" or choix < "1": + self.console.print("Veuillez entrer un nombre entre 1 et 6") + choix = self.prompt.ask("Veuillez choisir une option ",choices=["1","2","3","4","5","6"]) + except ValueError: + self.console.print("Veuillez entrer un nombre entre 1 et 6") + except Exception as e: + self.console.print(f"Une erreur est survenue : {e}") def menu_1(self): self.console.clear() @@ -73,7 +81,7 @@ def menu_1(self): self.dynamiqueText("Veuillez entrer le chemin du fichier :","white") time.sleep(0.04) chemin_fichier = self.prompt.ask("Veuillez entrer le chemin du fichier : ") - resultat = Analyser_fichier_uniquement(self,chemin_fichier) + resultat = Analyser_fichier_uniquement(chemin_fichier) self.console.clear() self.dynamiqueText("Analyse en cours...","green") time.sleep(0.04) From 8f0dbfce701e060940317e458cb7a3e353c3da4d Mon Sep 17 00:00:00 2001 From: e-mandy Date: Sat, 2 Aug 2025 16:04:26 +0100 Subject: [PATCH 05/26] Essai de fusion (1/2) --- src/utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils.py b/src/utils.py index 997098b..3d2bfdb 100644 --- a/src/utils.py +++ b/src/utils.py @@ -45,7 +45,7 @@ def verifier_texte_dechiffre(texte: str): #Statistiques sur le texte - stats={ + stats={ 'imprimable':0, 'nombre_mots':0, 'p_mots_valide':0, @@ -55,9 +55,9 @@ def verifier_texte_dechiffre(texte: str): #Verifier le pourcentage de caractères imprimables. - for lettre in texte: - if lettre.isprintable(): - stats['imprimable']+= 100/len(texte) + for lettre in texte: + if lettre.isprintab:le(): + stats['imprimable']+= 100/len(texte) # Traitement du texte brut pour obtenir une séquence distinct de pseudo-mot à cette étape séparé par des espaces @@ -126,9 +126,9 @@ def verifier_texte_dechiffre(texte: str): def rangerDico(): - """ - Fonction utilitaire de rangement du dictionnaire anglais téléchargé - """ + """ + Fonction utilitaire de rangement du dictionnaire anglais téléchargé + """ i=0 compte = 0 # Ouverture du grand dictionnaire. From b638a494081c7c90f6a181e936b3f81cc7476416 Mon Sep 17 00:00:00 2001 From: e-mandy Date: Sun, 3 Aug 2025 17:55:15 +0100 Subject: [PATCH 06/26] =?UTF-8?q?R=C3=A9cup=C3=A9ration=20du=20main=20(1/2?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/detecteur_crypto.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/detecteur_crypto.py b/src/detecteur_crypto.py index 45c6775..153e418 100644 --- a/src/detecteur_crypto.py +++ b/src/detecteur_crypto.py @@ -12,6 +12,4 @@ class DetecteurCryptoOrchestrateur: Initialisation de l'analyseur AES-CBC """ def __init__(self): - self.aes_cbc_analyzer = AesCbcAnalyzer() - - + self.aes_cbc_analyzer = AesCbcAnalyzer() \ No newline at end of file From e96580101f4ddcd0030487efcd4cc0b081672910 Mon Sep 17 00:00:00 2001 From: e-mandy Date: Sun, 3 Aug 2025 21:37:00 +0100 Subject: [PATCH 07/26] Mise en place des tests unitaires (1/4) --- src/utils.py | 29 +++++++---------------------- tests/test_global.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/utils.py b/src/utils.py index 0804109..5919cf4 100644 --- a/src/utils.py +++ b/src/utils.py @@ -21,15 +21,11 @@ def calculer_entropie(bytes) -> float: for chaque_byte in bytes: if(chaque_byte == specifique_byte): i += 1 -<<<<<<< HEAD + proba_byte = 1 / i entropie += (proba_byte) * math.log(1/proba_byte, 8) return entropie -======= - proba_byte = 1 / i - entropie += (proba_byte) * math.log(1/proba_byte, 8) - return entropie ->>>>>>> 267a282fa6e0765b0437b0aa3ef6139dc3453987 + def est_dechiffre(texte:str) -> bool: @@ -79,15 +75,6 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: #Statistiques sur le texte -<<<<<<< HEAD - stats={ - 'imprimable':0, - 'nombre_mots':0, - 'p_mots_valide':0, - 'non_mots':[], - 'ponctuation_valide':0 - } -======= stats: dict = { 'imprimable':0, 'nombre_mots':0, @@ -95,7 +82,6 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: 'non_mots':[], 'ponctuation_valide':0 } ->>>>>>> 267a282fa6e0765b0437b0aa3ef6139dc3453987 #Verifier le pourcentage de caractères imprimables. @@ -105,7 +91,7 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: # Traitement du texte brut pour obtenir une séquence distinct de pseudo-mot à cette étape séparé par des espaces - tab='./:!\\}{_%*$£&#;,~"()[]=§|`^@' + tab='./:!\\}{_%*$£&#;,~"()[]=§|`^@?' copy=texte for lettre in tab: copy=copy.replace(lettre, ' ') @@ -119,9 +105,9 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: trouve=False if mot == '': continue for syl in ['Fr', 'En']: - chemin=f"{os.curdir}\\CryptoForensic-Python\\dico{syl}\\{mot[0]}.txt" - - with open(chemin, 'r') as f: + chemin=f"{os.curdir}.\\CryptoForensic-Python\\dico{syl}\\{mot[0]}.txt" + exit + with open(chemin, 'r') as f: ligne=f.readline() ligne=ligne.removesuffix('\n') @@ -129,7 +115,7 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: if ligne == mot: stats['p_mots_valide']+=100/len(copy) - print(stats['p_mots_valide'], mot) + print('\n', stats['p_mots_valide'], mot,) trouve=True break @@ -196,4 +182,3 @@ def rangerDico(): # rangerDico() -print(verifier_texte_dechiffre('neither#nor avec, ded_caractère a')) diff --git a/tests/test_global.py b/tests/test_global.py index e5e65e5..19dcc5a 100644 --- a/tests/test_global.py +++ b/tests/test_global.py @@ -1,5 +1,9 @@ # import de la library pour les tests from unittest import TestCase, main +import sys +sys.path.append('.') +sys.path.append('..') +from src.utils import verifier_texte_dechiffre, calculer_entropie """ Ici le TestCase pour le regroupement des Cas de figures de Tests et le main pour l'exécution automatique des tests définis dans la classe ci-dessous @@ -16,6 +20,15 @@ def test_addition(self): self.assertEqual(add(5,5),10) + def test_verification_texte_dechiffre(self): + self.assertDictEqual(verifier_texte_dechiffre("je talk !a mamamia:?"), {"imprimable": 100, "nombre_mots": 4, "p_mots_valide": 3, "nom_mots": ["mamamia"], "ponctuation_valide": 1}) + + def test_calcul_entropie(self): + self.assertGreater(calculer_entropie("aaaaaaaa"), 0) + + + + """ # La fonction doit être préfixé du mot test pour que le TestCase puisse le l'identifier en tant que méthode à tester (le snake_case ici devra être appliqué ici) From e119f5dc57f198d6c8978647a8b907ce0656ce73 Mon Sep 17 00:00:00 2001 From: e-mandy Date: Sun, 3 Aug 2025 21:41:06 +0100 Subject: [PATCH 08/26] Revert "Mise en place des tests unitaires (1/4)" This reverts commit e96580101f4ddcd0030487efcd4cc0b081672910. --- src/utils.py | 29 ++++++++++++++++++++++------- tests/test_global.py | 13 ------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/utils.py b/src/utils.py index 5919cf4..0804109 100644 --- a/src/utils.py +++ b/src/utils.py @@ -21,11 +21,15 @@ def calculer_entropie(bytes) -> float: for chaque_byte in bytes: if(chaque_byte == specifique_byte): i += 1 - +<<<<<<< HEAD proba_byte = 1 / i entropie += (proba_byte) * math.log(1/proba_byte, 8) return entropie - +======= + proba_byte = 1 / i + entropie += (proba_byte) * math.log(1/proba_byte, 8) + return entropie +>>>>>>> 267a282fa6e0765b0437b0aa3ef6139dc3453987 def est_dechiffre(texte:str) -> bool: @@ -75,6 +79,15 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: #Statistiques sur le texte +<<<<<<< HEAD + stats={ + 'imprimable':0, + 'nombre_mots':0, + 'p_mots_valide':0, + 'non_mots':[], + 'ponctuation_valide':0 + } +======= stats: dict = { 'imprimable':0, 'nombre_mots':0, @@ -82,6 +95,7 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: 'non_mots':[], 'ponctuation_valide':0 } +>>>>>>> 267a282fa6e0765b0437b0aa3ef6139dc3453987 #Verifier le pourcentage de caractères imprimables. @@ -91,7 +105,7 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: # Traitement du texte brut pour obtenir une séquence distinct de pseudo-mot à cette étape séparé par des espaces - tab='./:!\\}{_%*$£&#;,~"()[]=§|`^@?' + tab='./:!\\}{_%*$£&#;,~"()[]=§|`^@' copy=texte for lettre in tab: copy=copy.replace(lettre, ' ') @@ -105,9 +119,9 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: trouve=False if mot == '': continue for syl in ['Fr', 'En']: - chemin=f"{os.curdir}.\\CryptoForensic-Python\\dico{syl}\\{mot[0]}.txt" - exit - with open(chemin, 'r') as f: + chemin=f"{os.curdir}\\CryptoForensic-Python\\dico{syl}\\{mot[0]}.txt" + + with open(chemin, 'r') as f: ligne=f.readline() ligne=ligne.removesuffix('\n') @@ -115,7 +129,7 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: if ligne == mot: stats['p_mots_valide']+=100/len(copy) - print('\n', stats['p_mots_valide'], mot,) + print(stats['p_mots_valide'], mot) trouve=True break @@ -182,3 +196,4 @@ def rangerDico(): # rangerDico() +print(verifier_texte_dechiffre('neither#nor avec, ded_caractère a')) diff --git a/tests/test_global.py b/tests/test_global.py index 19dcc5a..e5e65e5 100644 --- a/tests/test_global.py +++ b/tests/test_global.py @@ -1,9 +1,5 @@ # import de la library pour les tests from unittest import TestCase, main -import sys -sys.path.append('.') -sys.path.append('..') -from src.utils import verifier_texte_dechiffre, calculer_entropie """ Ici le TestCase pour le regroupement des Cas de figures de Tests et le main pour l'exécution automatique des tests définis dans la classe ci-dessous @@ -20,15 +16,6 @@ def test_addition(self): self.assertEqual(add(5,5),10) - def test_verification_texte_dechiffre(self): - self.assertDictEqual(verifier_texte_dechiffre("je talk !a mamamia:?"), {"imprimable": 100, "nombre_mots": 4, "p_mots_valide": 3, "nom_mots": ["mamamia"], "ponctuation_valide": 1}) - - def test_calcul_entropie(self): - self.assertGreater(calculer_entropie("aaaaaaaa"), 0) - - - - """ # La fonction doit être préfixé du mot test pour que le TestCase puisse le l'identifier en tant que méthode à tester (le snake_case ici devra être appliqué ici) From 14e759db98c3a4dbf914719981fb1e327b7dee03 Mon Sep 17 00:00:00 2001 From: e-mandy Date: Sun, 3 Aug 2025 21:54:35 +0100 Subject: [PATCH 09/26] Mise en place des tests unitaires (1/4) --- src/utils.py | 29 +++++++---------------------- tests/test_global.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/utils.py b/src/utils.py index 0804109..5919cf4 100644 --- a/src/utils.py +++ b/src/utils.py @@ -21,15 +21,11 @@ def calculer_entropie(bytes) -> float: for chaque_byte in bytes: if(chaque_byte == specifique_byte): i += 1 -<<<<<<< HEAD + proba_byte = 1 / i entropie += (proba_byte) * math.log(1/proba_byte, 8) return entropie -======= - proba_byte = 1 / i - entropie += (proba_byte) * math.log(1/proba_byte, 8) - return entropie ->>>>>>> 267a282fa6e0765b0437b0aa3ef6139dc3453987 + def est_dechiffre(texte:str) -> bool: @@ -79,15 +75,6 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: #Statistiques sur le texte -<<<<<<< HEAD - stats={ - 'imprimable':0, - 'nombre_mots':0, - 'p_mots_valide':0, - 'non_mots':[], - 'ponctuation_valide':0 - } -======= stats: dict = { 'imprimable':0, 'nombre_mots':0, @@ -95,7 +82,6 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: 'non_mots':[], 'ponctuation_valide':0 } ->>>>>>> 267a282fa6e0765b0437b0aa3ef6139dc3453987 #Verifier le pourcentage de caractères imprimables. @@ -105,7 +91,7 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: # Traitement du texte brut pour obtenir une séquence distinct de pseudo-mot à cette étape séparé par des espaces - tab='./:!\\}{_%*$£&#;,~"()[]=§|`^@' + tab='./:!\\}{_%*$£&#;,~"()[]=§|`^@?' copy=texte for lettre in tab: copy=copy.replace(lettre, ' ') @@ -119,9 +105,9 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: trouve=False if mot == '': continue for syl in ['Fr', 'En']: - chemin=f"{os.curdir}\\CryptoForensic-Python\\dico{syl}\\{mot[0]}.txt" - - with open(chemin, 'r') as f: + chemin=f"{os.curdir}.\\CryptoForensic-Python\\dico{syl}\\{mot[0]}.txt" + exit + with open(chemin, 'r') as f: ligne=f.readline() ligne=ligne.removesuffix('\n') @@ -129,7 +115,7 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: if ligne == mot: stats['p_mots_valide']+=100/len(copy) - print(stats['p_mots_valide'], mot) + print('\n', stats['p_mots_valide'], mot,) trouve=True break @@ -196,4 +182,3 @@ def rangerDico(): # rangerDico() -print(verifier_texte_dechiffre('neither#nor avec, ded_caractère a')) diff --git a/tests/test_global.py b/tests/test_global.py index e5e65e5..19dcc5a 100644 --- a/tests/test_global.py +++ b/tests/test_global.py @@ -1,5 +1,9 @@ # import de la library pour les tests from unittest import TestCase, main +import sys +sys.path.append('.') +sys.path.append('..') +from src.utils import verifier_texte_dechiffre, calculer_entropie """ Ici le TestCase pour le regroupement des Cas de figures de Tests et le main pour l'exécution automatique des tests définis dans la classe ci-dessous @@ -16,6 +20,15 @@ def test_addition(self): self.assertEqual(add(5,5),10) + def test_verification_texte_dechiffre(self): + self.assertDictEqual(verifier_texte_dechiffre("je talk !a mamamia:?"), {"imprimable": 100, "nombre_mots": 4, "p_mots_valide": 3, "nom_mots": ["mamamia"], "ponctuation_valide": 1}) + + def test_calcul_entropie(self): + self.assertGreater(calculer_entropie("aaaaaaaa"), 0) + + + + """ # La fonction doit être préfixé du mot test pour que le TestCase puisse le l'identifier en tant que méthode à tester (le snake_case ici devra être appliqué ici) From 93fd27d840c1ee137beea52b504e30781c00f978 Mon Sep 17 00:00:00 2001 From: wesley-kami Date: Mon, 4 Aug 2025 16:55:26 +0100 Subject: [PATCH 10/26] Update de l'option quitter --- src/interface_console.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/interface_console.py b/src/interface_console.py index 0eb9537..fee6eb5 100644 --- a/src/interface_console.py +++ b/src/interface_console.py @@ -3,8 +3,8 @@ from rich.markdown import Markdown from rich import print from rich.prompt import Prompt -from detecteur_crypto import Analyser_fichier_uniquement -from detecteur_crypto import Analyser_fichier_sequentiels +# from detecteur_crypto import Analyser_fichier_uniquement +# from detecteur_crypto import Analyser_fichier_sequentiels import time, os install() @@ -39,7 +39,7 @@ def dynamiqueText(self,text,color): def default_menu(self): self.console.clear() - self.dynamiqueText("😈​ Bienvenue sur Forensic je suis Crypto votre assitant IA minimaliste🤖​ ","green") + self.dynamiqueText("😈​ Bienvenue sur Forensic je suis Crypto votre assitant IA minimaliste 🤖​ ","green") self.dynamiqueText("En quoi puis-je vous aider ? :","white") time.sleep(0.04) menuTag = Markdown("# Menu",style="blue") @@ -80,22 +80,22 @@ def menu_1(self): self.dynamiqueText("Analyse d'un fichier spécifique","green") self.dynamiqueText("Veuillez entrer le chemin du fichier :","white") time.sleep(0.04) - chemin_fichier = self.prompt.ask("Veuillez entrer le chemin du fichier : ") - resultat = Analyser_fichier_uniquement(chemin_fichier) + # chemin_fichier = self.prompt.ask("Veuillez entrer le chemin du fichier : ") + # resultat = Analyser_fichier_uniquement(chemin_fichier) self.console.clear() self.dynamiqueText("Analyse en cours...","green") time.sleep(0.04) self.console.clear() self.dynamiqueText("Analyse terminée","green") time.sleep(0.04) - self.default_menu() + # def menu_2(self): self.console.clear() self.dynamiqueText("Mission complète automatique","green") self.dynamiqueText("Veuillez entrer le chemin du dossier :","white") time.sleep(0.04) - chemin_dossier = self.prompt.ask("Veuillez entrer le chemin du dossier : ") + # chemin_dossier = self.prompt.ask("Veuillez entrer le chemin du dossier : ") self.console.clear() self.dynamiqueText("Mission en cours...","green") time.sleep(0.04) @@ -109,7 +109,7 @@ def menu_3(self): self.dynamiqueText("Attaque par dictionnaire manuelle","green") self.dynamiqueText("Veuillez entrer le chemin du fichier :","white") time.sleep(0.04) - chemin_fichier = self.prompt.ask("Veuillez entrer le chemin du fichier : ") + # chemin_fichier = self.prompt.ask("Veuillez entrer le chemin du fichier : ") self.console.clear() self.dynamiqueText("Attaque en cours...","green") time.sleep(0.04) @@ -132,9 +132,8 @@ def menu_5(self): def menu_6(self): self.console.clear() - self.dynamiqueText("Au revoir !","green") - time.sleep(0.04) + self.dynamiqueText("😄​ Merci pour votre visite et à la revoyure 👋​ !","yellow") + time.sleep(2) self.console.clear() - self.console.exit() consoleInterface() From 52433f75af6b71f7fcf1ff444b64437e2c8da27f Mon Sep 17 00:00:00 2001 From: e-mandy Date: Mon, 4 Aug 2025 17:12:44 +0100 Subject: [PATCH 11/26] =?UTF-8?q?Mise=20en=20place=20des=20tests=20li?= =?UTF-8?q?=C3=A9s=20=C3=A0=20l'analyzer=20aes=20cbc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/analyzers/aes_cbc_analyzer.py | 2 +- src/utils.py | 1 - tests/test_analyzers.py | 33 +++++++++++++++++++++++++++++++ tests/test_global.py | 30 +++++++++++++--------------- 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/analyzers/aes_cbc_analyzer.py b/src/analyzers/aes_cbc_analyzer.py index d1b07f5..c11b365 100644 --- a/src/analyzers/aes_cbc_analyzer.py +++ b/src/analyzers/aes_cbc_analyzer.py @@ -99,7 +99,7 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: chemin_dictionnaire(str): le chemin du dictionnaire de mots de passes pour l'attaque par dictionnaire. Returns: - list[bytes]: liste des clés candidates. + list[bytes]: liste des clés candidates. ''' mots_de_passe_cible = self.filtrer_dictionnaire_par_indices(chemin_dictionnaire) diff --git a/src/utils.py b/src/utils.py index ede0a54..280a45d 100644 --- a/src/utils.py +++ b/src/utils.py @@ -3,7 +3,6 @@ import sys import os - def calculer_entropie(bytes) -> float: ''' Calcul l'entropie (le désordre dans une suite de données) afin de déterminer le degré d'improbabilité d'une chaine de données. diff --git a/tests/test_analyzers.py b/tests/test_analyzers.py index e69de29..28a22ce 100644 --- a/tests/test_analyzers.py +++ b/tests/test_analyzers.py @@ -0,0 +1,33 @@ +from unittest import TestCase, main +import sys +sys.path.append('.') +sys.path.append('..') +from src.analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer + +class AnalyzersTester(TestCase): + + """ + Cette classe est principalement destinée à recueillir toutes les fonctions de test des analyseurs d'algorithme + de chiffrement. + """ + + def setUp(self): + self.chemin_fichier_chiffre = "data/mission1.enc" + self.wordlist = "keys/wordlist.txt" + self.analyser = Aes_Cbc_Analyzer() + + + def test_aes_cbc_identifier_algo(self): + self.assertAlmostEqual(self.analyser.identifier_algo(self.chemin_fichier_chiffre), 1) + + def test_aes_cbc_filtrage_dict(self): + self.assertIsInstance(self.analyser.filtrer_dictionnaire_par_indices(self.wordlist), list) + + def test_generation_cles_candidate(self): + self.assertIsInstance(self.analyser.generer_cles_candidates(self.wordlist), list) + + def test_exception_dechiffrer(self): + with self.assertRaises(FileNotFoundError): + self.analyser.dechiffrer("no_file_dohi.txt", self.analyser.generer_cles_candidates(self.wordlist)) + +main() \ No newline at end of file diff --git a/tests/test_global.py b/tests/test_global.py index 19dcc5a..67d0c42 100644 --- a/tests/test_global.py +++ b/tests/test_global.py @@ -15,6 +15,20 @@ def add(a,b): class BetaTester(TestCase): #Définition de la méthode de test + """ + # La fonction doit être préfixé du mot test pour que le TestCase puisse le l'identifier en tant que méthode à tester (le snake_case ici devra être appliqué ici) + + # En fonction du type de vérification que vous souhaitez effectué par rapport aux test les méthodes assert devront variés. + ex : * assertEqual() pour vérifier l'égalité. Dans le cas utilisé cette fonction vérifie si le retour de la fonction add correspond à la valeur 10 + * assertIn() pour vérifier si une variable est dans une iterable + * assertIsInstance() pour vérifier le type de retour d'une variable ou fonction etc... (description des méthodes à l'appui) + + NB : Pour tester sa fonction chacun devra faire un import pour éviter la redondance. + Chaque fonction à tester devra se retrouver dans la class BetaTester avec un nom clair et propre à sa fonctionnalité précédé du mot "test" + + command : pyhton test_global.py [-v (-- verbose)] (verbose pour un test avec plus de précision) + + """ def test_addition(self): self.assertEqual(add(5,5),10) @@ -27,20 +41,4 @@ def test_calcul_entropie(self): self.assertGreater(calculer_entropie("aaaaaaaa"), 0) - - -""" - # La fonction doit être préfixé du mot test pour que le TestCase puisse le l'identifier en tant que méthode à tester (le snake_case ici devra être appliqué ici) - - # En fonction du type de vérification que vous souhaitez effectué par rapport aux test les méthodes assert devront variés. - ex : * assertEqual() pour vérifier l'égalité. Dans le cas utilisé cette fonction vérifie si le retour de la fonction add correspond à la valeur 10 - * assertIn() pour vérifier si une variable est dans une iterable - * assertIsInstance() pour vérifier le type de retour d'une variable ou fonction etc... (description des méthodes à l'appui) - - NB : Pour tester sa fonction chacun devra faire un import pour éviter la redondance. - Chaque fonction à tester devra se retrouver dans la class BetaTester avec un nom clair et propre à sa fonctionnalité précédé du mot "test" - - command : pyhton test_global.py [-v (-- verbose)] (verbose pour un test avec plus de précision) - -""" main() \ No newline at end of file From af85ee29c6e700e3b6d6e5ae2c9498a397f043ad Mon Sep 17 00:00:00 2001 From: mouwaficbdr Date: Mon, 4 Aug 2025 21:55:10 +0100 Subject: [PATCH 12/26] Corrections de typage et de logique --- src/utils.py | 133 ++++++++++++++++++++++++++------------------------- 1 file changed, 67 insertions(+), 66 deletions(-) diff --git a/src/utils.py b/src/utils.py index 280a45d..e49fb1a 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,29 +1,36 @@ import math import string import sys -import os +from typing import Any, Dict, List, TypedDict -def calculer_entropie(bytes) -> float: - ''' - Calcul l'entropie (le désordre dans une suite de données) afin de déterminer le degré d'improbabilité d'une chaine de données. +class StatsDict(TypedDict): + imprimable: float + nombre_mots: int + p_mots_valide: float + non_mots: List[str] + ponctuation_valide: int - Args: - bytes(bytes): La donnée brute contenue dans le fichier crypté. +def calculer_entropie(bytes: bytes) -> float: + ''' + Calcul l'entropie (le désordre dans une suite de données) afin de déterminer le degré d'improbabilité d'une chaine de données. + + Args: + bytes(bytes): La donnée brute contenue dans le fichier crypté. - Returns: - float: l'entropie calculée. + Returns: + float: l'entropie calculée. ''' - entropie = 0 - proba_byte = 0 - for specifique_byte in bytes: - i = 1 - for chaque_byte in bytes: - if(chaque_byte == specifique_byte): - i += 1 + entropie = 0 + proba_byte = 0 + for specifique_byte in bytes: + i = 1 + for chaque_byte in bytes: + if(chaque_byte == specifique_byte): + i += 1 - proba_byte = 1 / i - entropie += (proba_byte) * math.log(1/proba_byte, 8) - return entropie + proba_byte = 1 / i + entropie += (proba_byte) * math.log(1/proba_byte, 8) + return entropie @@ -52,11 +59,10 @@ def est_dechiffre(texte:str) -> bool: pourcent += 20 return True if pourcent > 70 else False - - - -def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: + + +def verifier_texte_dechiffre(texte: str) -> Dict[str, Any]: """ Verifie que le dechiffrement d'un message a bien été effectué sur la base de certains critères. @@ -82,51 +88,51 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: 'ponctuation_valide':0 } + if not texte: + return stats + #Verifier le pourcentage de caractères imprimables. - - for lettre in texte: - if lettre.isprintable(): - stats['imprimable']+= 100/len(texte) - + stats['imprimable'] = int(sum(1 for char in texte if char.isprintable()) / len(texte) * 100) + # Traitement du texte brut pour obtenir une séquence distincte de pseudo-mot à cette étape séparé par des espaces tab='./:!\\}{_%*$£&#;,~"()[]=§|`^@?' copy=texte for lettre in tab: copy=copy.replace(lettre, ' ') - copy=copy.strip().split(' ') - stats['nombre_mots']=len(copy) + mots = [mot for mot in copy.strip().split(' ') if mot] + stats['nombre_mots']=len(mots) # Verifier que le chaque mot du texte est un mot anglais/francais try: - for mot in copy: + mots_valides = 0 + for mot in mots: trouve=False - if mot == '': continue + if not mot: continue + + first_char = mot[0].lower() + for syl in ['Fr', 'En']: - chemin=f"{os.curdir}.\\CryptoForensic-Python\\dico{syl}\\{mot[0]}.txt" - exit - with open(chemin, 'r') as f: - ligne=f.readline() - ligne=ligne.removesuffix('\n') - - while not trouve and ligne != "": - - if ligne == mot: - stats['p_mots_valide']+=100/len(copy) - print('\n', stats['p_mots_valide'], mot,) - trouve=True - break - - ligne=f.readline() - ligne=ligne.removesuffix('\n') - - f.close() + chemin=f"dico{syl}/{first_char}.txt" + try: + with open(chemin, 'r', encoding='latin-1') as f: + for ligne in f: + if ligne.strip() == mot: + mots_valides += 1 + trouve=True + break + except FileNotFoundError: + continue if trouve : break if not trouve : stats['non_mots'].append(mot) + if mots: + stats['p_mots_valide'] = round((mots_valides / len(mots)) * 100, 2) + else: + stats['p_mots_valide'] = 0.0 except Exception: tb=sys.exception().__traceback__ @@ -136,24 +142,17 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]: #Verifier la structure de ponctuation. points='.?!;,' - nbr_ponct=0 - for point in points : - nbr_ponct+=texte.count(point) - for point in points : - partition= texte.partition(point) - if partition[2].startswith(' ') : - if (point in '?!.' and partition[2].lstrip()[0].isupper()) or (point in ';,' and partition[2].lstrip()[0].islower()): - stats['ponctuation_valide']+=100/nbr_ponct - - for key in stats: - print(key) - if isinstance(stats[key], float): - stats[key]=round(stats[key], 2) + count = 0 + for i, char in enumerate(texte): + if char in points: + if (i == len(texte) - 1) or (texte[i+1] == ' '): + count += 1 + stats['ponctuation_valide'] = count return stats -def rangerDico(): +def rangerDico() -> None: """ Fonction utilitaire de rangement du dictionnaire anglais téléchargé Pour effectuer des tests @@ -162,11 +161,13 @@ def rangerDico(): compte = 0 # Ouverture du grand dictionnaire. try : - with open(f"{os.path.abspath(os.curdir)}\\words_alpha.txt",'r') as f: + # Utilisation de Path pour un chemin portable + words_path = Path.cwd() / "words_alpha.txt" + with open(words_path,'r') as f: while i<26: # Définition du chemin vers le fichier de chaque mot en fonction de l'alphabet. - chemin=f"{os.curdir}\\dicoEn\\{string.ascii_lowercase[i]}.txt" - with open(chemin, 'a') as fichier: + dico_path = Path.cwd() / "dicoEn" / f"{string.ascii_lowercase[i]}.txt" + with open(dico_path, 'a') as fichier: #Ecriture dans le fichier. fichier.write(string.ascii_lowercase[i]+'\n') while 1 : From 896696aac1ce9f707ac3bb7b66dc54182a010b12 Mon Sep 17 00:00:00 2001 From: mouwaficbdr Date: Mon, 4 Aug 2025 21:55:32 +0100 Subject: [PATCH 13/26] =?UTF-8?q?Correction=20du=20comportement=20=C3=A0?= =?UTF-8?q?=20la=20lev=C3=A9e=20de=20l'exception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/analyzers/aes_cbc_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyzers/aes_cbc_analyzer.py b/src/analyzers/aes_cbc_analyzer.py index c11b365..719381e 100644 --- a/src/analyzers/aes_cbc_analyzer.py +++ b/src/analyzers/aes_cbc_analyzer.py @@ -155,4 +155,4 @@ def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: return b"" except FileNotFoundError: - return b"" \ No newline at end of file + raise \ No newline at end of file From 339140377e7d4e921e35558ffc8e024f751f202f Mon Sep 17 00:00:00 2001 From: mouwaficbdr Date: Mon, 4 Aug 2025 21:56:28 +0100 Subject: [PATCH 14/26] =?UTF-8?q?Correction=20de=20la=20lgoque=20de=20test?= =?UTF-8?q?=20de=20test=5Fexception=5Fd=C3=A9chiffrer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_analyzers.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_analyzers.py b/tests/test_analyzers.py index 28a22ce..8129c39 100644 --- a/tests/test_analyzers.py +++ b/tests/test_analyzers.py @@ -27,7 +27,15 @@ def test_generation_cles_candidate(self): self.assertIsInstance(self.analyser.generer_cles_candidates(self.wordlist), list) def test_exception_dechiffrer(self): + cles_candidates = self.analyser.generer_cles_candidates(self.wordlist) + + if not cles_candidates: + self.fail("La liste des clés candidates ne devrait pas être vide.") + + premiere_cle = cles_candidates[0] + with self.assertRaises(FileNotFoundError): - self.analyser.dechiffrer("no_file_dohi.txt", self.analyser.generer_cles_candidates(self.wordlist)) + self.analyser.dechiffrer("no_file_dohi.txt", premiere_cle) -main() \ No newline at end of file +if __name__ == '__main__': + main() \ No newline at end of file From 9b38d2763331de5def4a5b6db0e6dde9ac98f1cf Mon Sep 17 00:00:00 2001 From: mouwaficbdr Date: Mon, 4 Aug 2025 21:57:36 +0100 Subject: [PATCH 15/26] Corection de la logique de test_verification_texte_dechiffre --- tests/test_global.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/test_global.py b/tests/test_global.py index 67d0c42..f59e2cd 100644 --- a/tests/test_global.py +++ b/tests/test_global.py @@ -35,10 +35,16 @@ def test_addition(self): def test_verification_texte_dechiffre(self): - self.assertDictEqual(verifier_texte_dechiffre("je talk !a mamamia:?"), {"imprimable": 100, "nombre_mots": 4, "p_mots_valide": 3, "nom_mots": ["mamamia"], "ponctuation_valide": 1}) - - def test_calcul_entropie(self): + resultat = verifier_texte_dechiffre("je talk !a mamamia:?") + self.assertAlmostEqual(resultat['imprimable'], 100.0) + self.assertEqual(resultat['nombre_mots'], 4) + self.assertAlmostEqual(resultat['p_mots_valide'], 75.0) + self.assertEqual(resultat['non_mots'], ["mamamia"]) + self.assertEqual(resultat['ponctuation_valide'], 1) + + def test_calcul_entropie(self) -> None: self.assertGreater(calculer_entropie("aaaaaaaa"), 0) -main() \ No newline at end of file +if __name__ == '__main__': + main() \ No newline at end of file From 275b47adab2fa97fe1fab2f216c70271d4fd5b5c Mon Sep 17 00:00:00 2001 From: mouwaficbdr Date: Mon, 4 Aug 2025 22:05:41 +0100 Subject: [PATCH 16/26] fix: Utilisation de pathlib pour une gestion portable des chemins. --- src/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils.py b/src/utils.py index e49fb1a..40b05f4 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,6 +1,7 @@ import math import string import sys +from pathlib import Path from typing import Any, Dict, List, TypedDict class StatsDict(TypedDict): @@ -114,7 +115,7 @@ def verifier_texte_dechiffre(texte: str) -> Dict[str, Any]: first_char = mot[0].lower() for syl in ['Fr', 'En']: - chemin=f"dico{syl}/{first_char}.txt" + chemin = Path(f"dico{syl}") / f"{first_char}.txt" try: with open(chemin, 'r', encoding='latin-1') as f: for ligne in f: From c1c0b228cfbaedd2c2a697046f4996e3c5882096 Mon Sep 17 00:00:00 2001 From: Eunock-web Date: Tue, 5 Aug 2025 03:14:34 +0200 Subject: [PATCH 17/26] Revu des fonctions pour une meilleure optimisation --- src/detecteur_crypto.py | 248 ++++++++++++++++++++++++++++++++-------- 1 file changed, 200 insertions(+), 48 deletions(-) diff --git a/src/detecteur_crypto.py b/src/detecteur_crypto.py index 8e25c99..32a68fb 100644 --- a/src/detecteur_crypto.py +++ b/src/detecteur_crypto.py @@ -1,5 +1,7 @@ # Import des modules import os +import time +from typing import List # Import des modules d'analyse from .analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer @@ -8,10 +10,13 @@ class ResultatAnalyse: """ Classe représentant un résultat d'analyse. """ - def __init__(self, algo: str, cle: bytes, texte_dechiffre: bytes): + def __init__(self, algo: str, cle: bytes, score_probabilite: float, texte_dechiffre: bytes, temps_execution: float = 0.0, nb_tentatives: int = 0): self.algo = algo self.cle = cle + self.score_probabilite = score_probabilite self.texte_dechiffre = texte_dechiffre + self.temps_execution = temps_execution + self.nb_tentatives = nb_tentatives class DetecteurCryptoOrchestrateur: """ @@ -28,72 +33,219 @@ def __init__(self): self.analyzers = { "AES-CBC": Aes_Cbc_Analyzer(), } + self.missions_completees = [] + self.statistiques_globales = { + "total_fichiers": 0, + "fichiers_dechiffres": 0, + "temps_total": 0.0, + "tentatives_total": 0 + } - def Analyser_fichier_uniquement(self, chemin_fichier_chiffre: str) -> ResultatAnalyse: + def analyser_fichier_specifique(self, chemin_fichier_chiffre: str) -> ResultatAnalyse: """ - Analyse un seul fichier chiffré et retourne le résultat de l'analyse. - - Args: - chemin_fichier_chiffre(str): chemin du fichier chiffré à analyser - - Returns: - ResultatAnalyse: résultat de l'analyse + ANALYSE D'UN FICHIER SPÉCIFIQUE + - Sélection du fichier à analyser + - Identification automatique de l'algorithme + - Affichage des scores de probabilité + + Args: + chemin_fichier_chiffre(str): chemin du fichier chiffré à analyser + + Returns: + ResultatAnalyse: résultat de l'analyse """ + debut_analyse = time.time() + try: - #Initialisation des variables + # Vérification de l'existence du fichier + if not os.path.exists(chemin_fichier_chiffre): + print("Erreur: Fichier non trouvé") + return ResultatAnalyse("", b"", 0.0, b"", 0.0, 0) + + # Initialisation des variables algorithme_detecte = "" cle = b"" + score_probabilite = 0.0 texte_dechiffre = b"" + nb_tentatives = 0 - #Parcours des algorithmes disponibles + # Parcours des algorithmes disponibles + scores_algorithmes = {} for nom_algo, analyzer in self.analyzers.items(): - score_probabilite = analyzer.identifier_algo(chemin_fichier_chiffre) # Retourne un float - print(f"{nom_algo}: score {score_probabilite:.2f}") + score = analyzer.identifier_algo(chemin_fichier_chiffre) + scores_algorithmes[nom_algo] = score + print(f"{nom_algo}: score {score:.2f}") + + if score > 0.5: # Seuil de confiance + algorithme_detecte = nom_algo + score_probabilite = score + print(f"Algorithme détecté: {algorithme_detecte} (score: {score:.2f})") + break + + if not algorithme_detecte: + print("Aucun algorithme coréctement détecté ") + temps_execution = time.time() - debut_analyse + return ResultatAnalyse("", b"", 0.0, b"", temps_execution, nb_tentatives) + + temps_execution = time.time() - debut_analyse + + return ResultatAnalyse(algorithme_detecte, cle, score_probabilite, texte_dechiffre, temps_execution, nb_tentatives) + + except Exception as e: + print(f"Erreur lors de l'analyse: {str(e)}") + temps_execution = time.time() - debut_analyse + return ResultatAnalyse("", b"", 0.0, b"", temps_execution, 0) + + def mission_complete_automatique(self, dossier_chiffres: str) -> List[ResultatAnalyse]: + """ + MISSION COMPLÈTE AUTOMATIQUE + - Analyse des 5 fichiers séquentiellement + - Tentatives de déchiffrement avec retour visuel + - Rapport de synthèse final + + Args: + dossier_chiffres(str): dossier contenant les fichiers chiffrés + + Returns: + list[ResultatAnalyse]: liste des résultats d'analyse + """ + + debut_mission = time.time() + resultats = [] + + try: + # Récupération des fichiers .enc + fichiers_enc = [f for f in os.listdir(dossier_chiffres) if f.endswith(".enc")] + + if not fichiers_enc: + print("Aucun fichier .enc trouvé dans le dossier") + return [] + + print(f"{len(fichiers_enc)} fichiers .enc détectés") + print("\nANALYSE SÉQUENTIELLE DES FICHIERS") + + for i, fichier in enumerate(fichiers_enc, 1): + print(f"\nFICHIER {i}/{len(fichiers_enc)}: {fichier}") - if score_probabilite > 0.5: # Comparaison correcte avec le seuil - algorithme_detecte = nom_algo # Stockage du nom de l'algorithme - print(f"Algorithme détecté: {algorithme_detecte} (score: {score_probabilite:.2f})") + chemin_fichier = os.path.join(dossier_chiffres, fichier) + + # Analyse du fichier + resultat = self.analyser_fichier_specifique(chemin_fichier) + + # Tentative de déchiffrement si algorithme détecté + if resultat.algo: + print(f"\nTENTATIVE DE DÉCHIFFREMENT") - # Génération des clés candidates + analyzer = self.analyzers[resultat.algo] cles_candidates = analyzer.generer_cles_candidates("dicoEn") - print(f"{len(cles_candidates)} clés candidates générées") - # Test de déchiffrement avec la première clé (pour l'exemple) if cles_candidates: - texte_dechiffre = analyzer.dechiffrer(chemin_fichier_chiffre, cles_candidates[0]) - if texte_dechiffre: - cle = cles_candidates[0] - print("Déchiffrement réussi avec la première clé!") + print(f"Test de {len(cles_candidates)} clés candidates...") + + for j, cle in enumerate(cles_candidates): + resultat.nb_tentatives += 1 + + if j % 100 == 0: # retour visuel tous les 100 essais + print(f" Tentative {j+1}/{len(cles_candidates)}...") + + texte_dechiffre = analyzer.dechiffrer(chemin_fichier, cle) + if texte_dechiffre and len(texte_dechiffre) > 0: + resultat.cle = cle + resultat.texte_dechiffre = texte_dechiffre + print(f" Clé trouvée après {j+1} tentatives!") + break else: - print("Déchiffrement échoué avec la première clé") - break + print(" Aucune clé valide trouvée") + else: + print(" Aucune clé candidate générée") + + resultats.append(resultat) + + # retour visuel + if resultat.algo: + print(f"{fichier}: {resultat.algo} (score: {resultat.score_probabilite:.2f})") + else: + print(f"{fichier}: Aucun algorithme détecté") - if not algorithme_detecte: - print("Aucun algorithme détecté avec confiance suffisante") + # Rapport de synthèse final + self.generer_rapport_synthese(resultats, time.time() - debut_mission) - return ResultatAnalyse(algorithme_detecte, cle, texte_dechiffre) - except Exception as e: - print(f"Erreur lors de l'analyse du fichier {chemin_fichier_chiffre}: {str(e)}") - return ResultatAnalyse("", b"", b"") - - - def Analyser_fichiers_sequentiels(self, dossier_chiffres: str) -> list[ResultatAnalyse]: - """ - Analyse plusieurs fichiers chiffrés dans un dossier et retourne les résultats de l'analyse. + # Mise à jour des statistiques globales + self.missions_completees.append({ + "dossier": dossier_chiffres, + "resultats": resultats, + "temps_total": time.time() - debut_mission + }) - Args: - dossier_chiffres(str): dossier contenant les fichiers chiffrés à analyser + return resultats - Returns: - list[ResultatAnalyse]: liste des résultats d'analyse pour chaque fichier + except Exception as e: + print(f"Erreur lors de la mission complète: {str(e)}") + return [] + + def attaque_dictionnaire_manuelle(self, chemin_fichier: str, algorithme_choisi: str) -> ResultatAnalyse: + """ + ATTAQUE PAR DICTIONNAIRE MANUELLE + - Choix du fichier et de l'algorithme + - Suivi en temps réel des tentatives + - Affichage des résultats intermédiaires + + Args: + chemin_fichier(str): chemin du fichier à attaquer + algorithme_choisi(str): algorithme à utiliser + + Returns: + ResultatAnalyse: résultat de l'attaque """ + + + debut_attaque = time.time() + try: - resultats = [] - for fichier_chiffre in os.listdir(dossier_chiffres): - chemin_fichier_chiffre = os.path.join(dossier_chiffres, fichier_chiffre) - resultat = self.Analyser_fichier_uniquement(chemin_fichier_chiffre) - resultats.append(resultat) - return resultats + if algorithme_choisi not in self.analyzers: + print(f"Algorithme {algorithme_choisi} non disponible") + return ResultatAnalyse("", b"", 0.0, b"", 0.0, 0) + + analyzer = self.analyzers[algorithme_choisi] + + # Vérification de l'algorithme + score = analyzer.identifier_algo(chemin_fichier) + print(f"Score de confirmation: {score:.2f}") + + if score < 0.3: + print("Score de confiance faible pour cet algorithme") + + # Génération des clés candidates + print(f"Génération des clés candidates") + cles_candidates = analyzer.generer_cles_candidates("dicoEn") + print(f"{len(cles_candidates)} clés candidates générées") + + # Attaque par dictionnaire + + cle_trouvee = b"" + texte_dechiffre = b"" + nb_tentatives = 0 + + for i, cle in enumerate(cles_candidates): + nb_tentatives += 1 + + # retour visuel en temps réel + if i % 50 == 0: + print(f"Tentative {i+1}/{len(cles_candidates)}... ({(i+1)/len(cles_candidates)*100:.1f}%)") + texte_dechiffre = analyzer.dechiffrer(chemin_fichier, cle) + if texte_dechiffre and len(texte_dechiffre) > 0: + cle_trouvee = cle + break + else: + print(f"Aucune clé valide trouvée après {len(cles_candidates)} tentatives") + + temps_execution = time.time() - debut_attaque + print(f"Temps d'exécution: {temps_execution:.2f} secondes") + + return ResultatAnalyse(algorithme_choisi, cle_trouvee, score, texte_dechiffre, temps_execution, nb_tentatives) + except Exception as e: - print(f"Erreur lors de l'analyse des fichiers: {str(e)}") - return [] \ No newline at end of file + print(f"Erreur lors de l'attaque: {str(e)}") + temps_execution = time.time() - debut_attaque + return ResultatAnalyse("", b"", 0.0, b"", temps_execution, 0) + From b74715c5779696ed05345d7060984bfdd3baf78d Mon Sep 17 00:00:00 2001 From: wesley-kami Date: Tue, 5 Aug 2025 16:26:16 +0100 Subject: [PATCH 18/26] Ajout du guide d'utilisation --- guideUtilisation.txt | 15 ++++++++++ src/interface_console.py | 62 +++++++++++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 guideUtilisation.txt diff --git a/guideUtilisation.txt b/guideUtilisation.txt new file mode 100644 index 0000000..e4451cd --- /dev/null +++ b/guideUtilisation.txt @@ -0,0 +1,15 @@ +## **Algorithmes déployés lors des mission** +**AES-256** : +###### 1. **Hello Word** : Ceci est un exemple veuillez suivre ce format pour la documentation de vos algorithmes + +**Chacha20** : +###### 1. **Hello Word** : Ceci est un exemple veuillez suivre ce format pour la documentation de vos algorithmes + +**Blowfish** : +###### 1. **Hello Word** : Ceci est un exemple veuillez suivre ce format pour la documentation de vos algorithmes + +**AES-GCM** : +###### 1. **Hello Word** : Ceci est un exemple veuillez suivre ce format pour la documentation de vos algorithmes + +**Fernet** : +###### 1. **Hello Word** : Ceci est un exemple veuillez suivre ce format pour la documentation de vos algorithmes diff --git a/src/interface_console.py b/src/interface_console.py index fee6eb5..205140b 100644 --- a/src/interface_console.py +++ b/src/interface_console.py @@ -2,7 +2,9 @@ from rich.traceback import install from rich.markdown import Markdown from rich import print +from rich.text import Text from rich.prompt import Prompt +from rich.table import Table # from detecteur_crypto import Analyser_fichier_uniquement # from detecteur_crypto import Analyser_fichier_sequentiels import time, os @@ -66,10 +68,6 @@ def default_menu(self): self.menu_5() elif choix == "6": self.menu_6() - - while choix > "6" or choix < "1": - self.console.print("Veuillez entrer un nombre entre 1 et 6") - choix = self.prompt.ask("Veuillez choisir une option ",choices=["1","2","3","4","5","6"]) except ValueError: self.console.print("Veuillez entrer un nombre entre 1 et 6") except Exception as e: @@ -127,8 +125,58 @@ def menu_4(self): def menu_5(self): self.console.clear() self.dynamiqueText("Système d'aide intégré","green") - time.sleep(0.04) - self.default_menu() + title = Markdown("# Guide d'utilisation",style="yellow bold") + contexte_title = Markdown("### 📋 Contexte de la Mission",style="green") + contexte= Markdown("### Vous êtes un analyste en cybersécurité travaillant pour une agence gouvernementale. Lors d'une opération d'investigation, votre équipe a intercepté 5 fichiers chiffrés contenant des informations cruciales. Votre mission est d'identifier l'algorithme de chiffrement utilisé pour chaque fichier, de découvrir la clé de déchiffrement, puis d'extraire le contenu secret.\n" \ + "### Les criminels ont utilisé 5 algorithmes de chiffrement symétrique différents pour protéger leurs communications. Votre expertise en cryptanalyse sera mise à l'épreuve pour déchiffrer ces messages et découvrir les secrets qu'ils contiennent.\n") + + mission_table = Table(title = "Missions Accomplies",style="",show_lines= True,leading=1) + mission_table.add_column("Intitulé",style="violet",justify="center") + mission_table.add_column("Fichier cible",style="red",justify="center") + mission_table.add_column('Indice',style="yellow",justify="center") + mission_table.add_column("Défi",style="green",justify="center") + + mission_table.add_row("AES-256-CBC","mission1.enc","La clé est liée à une ville française célèbre et une année olympique"," Identifier l'algorithme AES en mode CBC et récupérer la clé par attaque dictionnaire") + mission_table.add_row("ChaCha20","mission2.enc","Combinaison d'une année récente et d'un mot de passe anglais commun","Reconnaître le chiffrement de flux moderne ChaCha20") + mission_table.add_row("Blowfish","mission3.enc","Nom d'un algorithme de hachage populaire suivi de chiffres","Détecter l'algorithme Blowfish et ses spécificités") + mission_table.add_row("AES-256-GCM","mission4.enc","Acronyme d'une organisation internationale + année courante","Identifier le mode authentifié GCM et gérer l'authentification") + mission_table.add_row("Fernet","mission5.enc","Phrase française simple encodée, liée à notre domaine d'étude","Reconnaître le format Fernet et sa structure particulière") + + f = open("guideUtilisation.txt",'r') + algo_docs = Markdown(f.read()) + f.close() + + process= Markdown("### Processus d'usage logiciel",style="purple underline") + intro = Markdown("Comme vous l'avez probablement remarqué le menu de ce logiciel est composé de 06 options dont 04 principales :") + usage_guide_1 = Markdown("1. ### Analyse d'un fichier spécifique \n",style="black on white") + analysis_1 = Markdown(" Cette option a pour but de traiter un fichier crypter ( prise en charge des '.enc' exceptionnellement ) afin d'identifier l'algorithme \n\n" \ + " de cryptage qui lui a été appliqué ainsi que le score de probabilité de chaque algorithme de cryptage cité ci-dessus \n\n") + usage_guide_2 = Markdown("2. ### Mission complète automatique \n",style="black on white") + analysis_2 = Markdown(" Cette option permet de traiter les 05 missions de façon séquentielle afin de ressortir de chacune d'entre elle :\n\n" \ + " -la clé de crypatage\n\n" \ + " -le message déchiffrer\n\n" \ + " A la fin des traitement un synthèse finale est générée sur l'état des Test effectué") + + usage_guide_3 = Markdown("3. ### Attaque par dictionnaire manuelle",style="black on white") + analysis_3=Markdown(" En optant pour cette option vous aurez à sélectionner le fichier que vous souhaitez décrypté et par suite l'algorithme de décryptage que vous voudiez appliquer.\n" \ + " Vous aurez dun suivez en tempps réel de l'evolution des tentatives ainsi que l'affichage du résultat obtenu") + + usage_guide_4 = Markdown("4. ### Affichage des rapports \n",style="black on white") + analysis_4 =Markdown(" Cette option vous permettra d'oberver les rapports des différents tests de décryptages effectués au cours de l'utilisation de ce logiciel") + + final = Markdown("# 😁​ Merci d'utiliser notre logiciel 👾​ et bonne continuation ( **Appuyez sur la touche Enter pour retourner au menu principal** )",style="yellow") + + # print(title,contexte_title,contexte,mission_table,algo_docs,process,intro,usage_guide_1,analysis_1,usage_guide_2,analysis_2,usage_guide_3,analysis_3,usage_guide_4,analysis_4,final) + # escape = input('') + guides = [title,contexte_title,contexte,mission_table,algo_docs,process,intro,usage_guide_1,analysis_1,usage_guide_2,analysis_2,usage_guide_3,analysis_3,usage_guide_4,analysis_4,final] + + for guide in guides: + print(guide) + print("\n") + + escape= input('') + if escape != None: + self.default_menu() def menu_6(self): self.console.clear() @@ -136,4 +184,4 @@ def menu_6(self): time.sleep(2) self.console.clear() -consoleInterface() +consoleInterface() \ No newline at end of file From ed453b18e82b6c91706c00617054c39b394fb9ad Mon Sep 17 00:00:00 2001 From: e-mandy Date: Tue, 5 Aug 2025 22:55:29 +0100 Subject: [PATCH 19/26] Unsaved files --- src/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.py b/src/utils.py index d0a28fa..0e481e6 100644 --- a/src/utils.py +++ b/src/utils.py @@ -115,7 +115,7 @@ def verifier_texte_dechiffre(texte: str) -> Dict[str, Any]: for syl in ['Fr', 'En']: - chemin = Path(f"dico{syl}") / f"{first_char}.txt" + chemin = Path(f"dico{syl}")/f"{first_char}.txt" try: with open(chemin, 'r', encoding='latin-1') as f: for ligne in f: From d0e307ee902778b49fb156970ea4a2d07d3de367 Mon Sep 17 00:00:00 2001 From: e-mandy Date: Wed, 6 Aug 2025 11:02:50 +0100 Subject: [PATCH 20/26] =?UTF-8?q?Mise=20en=20place=20de=20la=20fonction=20?= =?UTF-8?q?de=20g=C3=A9n=C3=A9ration=20de=20cl=C3=A9s=20candidates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/analyzers/chacha20_analyzer.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/analyzers/chacha20_analyzer.py b/src/analyzers/chacha20_analyzer.py index e69de29..7a08c44 100644 --- a/src/analyzers/chacha20_analyzer.py +++ b/src/analyzers/chacha20_analyzer.py @@ -0,0 +1,30 @@ +import hashlib + +class ChaCha20_Analyzer: + + def filtrer_dictionnaire_par_indices(self, chemin_fichier_chiffre): + pass + + + + def generer_cle_candidates(self, chemin_fichier_chiffre): + ''' + Cette fonction se charge de générer les clés candidates pour le déchiffremment du fichier chiffré en utilisant + la dérivation sha256 pour renforcer les clées de chiffrement. + + + Args: + chemin_fichier_chiffre(str) : Le chemin vers le fichier chiffré + + Returns: + cles_candidates (list[bytes]) : Un tableau de clés, chaque clé étant une séquence d'octets + + ''' + + donnees_fichier_filtre = self.filtrer_dictionnaire_par_indices(chemin_fichier_chiffre) + + cle_candidates: list[bytes] = [] + for cle in donnees_fichier_filtre: + cle_candidates.append(hashlib.sha256(cle).encode(encoding="utf-8")) + + return cle_candidates \ No newline at end of file From 3ac183093170a7eef29fe14307b1b564cb045ff9 Mon Sep 17 00:00:00 2001 From: wesley-kami Date: Wed, 6 Aug 2025 21:53:06 +0100 Subject: [PATCH 21/26] =?UTF-8?q?pr=C3=A9mices=20du=20d=C3=A9chiffrement?= =?UTF-8?q?=20Chacha20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/analyzers/chacha20_analyzer.py | 42 +++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/analyzers/chacha20_analyzer.py b/src/analyzers/chacha20_analyzer.py index 7a08c44..edf6cee 100644 --- a/src/analyzers/chacha20_analyzer.py +++ b/src/analyzers/chacha20_analyzer.py @@ -1,4 +1,7 @@ import hashlib +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from rich import print +import os class ChaCha20_Analyzer: @@ -27,4 +30,41 @@ def generer_cle_candidates(self, chemin_fichier_chiffre): for cle in donnees_fichier_filtre: cle_candidates.append(hashlib.sha256(cle).encode(encoding="utf-8")) - return cle_candidates \ No newline at end of file + return cle_candidates + + def dechiffrer(self,chemin_fichier_chiffer : str ,clef :bytes)->str: + if len(clef) != 32 : return ValueError("Erreur : La clé a pas la taille correcte ") + else: + try: + with open(f"data/{chemin_fichier_chiffer}",'rb') as f: + nonce = f.read(12) + texte_chiffrer = f.read() + + algorithm_chacha20 = algorithms.ChaCha20(clef,nonce) + cipher = Cipher(algorithm_chacha20,mode=None) + decrypteur = cipher.decryptor() + return decrypteur.update(texte_chiffrer) + + + + except Exception as e: + print(f"Une erreur est survenu : {e}") + + +key=os.urandom(32) +nonce =os.urandom(16) +message = b"Ceci est un test" +algo = algorithms.ChaCha20(key,nonce) +cipher = Cipher(algo,mode=None) +encryptor = cipher.encryptor() +ct = encryptor.update(message) + +print(ct) + +decryptor = cipher.decryptor() +dt = encryptor.update(ct) + +print(dt) + +# ChaCha20_Analyzer().dechiffrer("mission2.enc",) + From 8e3e4688022c23770f3fea74887f8f6ee91c3194 Mon Sep 17 00:00:00 2001 From: wesley-kami Date: Thu, 7 Aug 2025 01:32:45 +0100 Subject: [PATCH 22/26] logique ddecryptage --- src/analyzers/chacha20_analyzer.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/analyzers/chacha20_analyzer.py b/src/analyzers/chacha20_analyzer.py index edf6cee..715ddbe 100644 --- a/src/analyzers/chacha20_analyzer.py +++ b/src/analyzers/chacha20_analyzer.py @@ -1,7 +1,7 @@ import hashlib from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from rich import print -import os +import os, struct class ChaCha20_Analyzer: @@ -37,9 +37,10 @@ def dechiffrer(self,chemin_fichier_chiffer : str ,clef :bytes)->str: else: try: with open(f"data/{chemin_fichier_chiffer}",'rb') as f: - nonce = f.read(12) + nonce = f.read(16) texte_chiffrer = f.read() + counter=0 algorithm_chacha20 = algorithms.ChaCha20(clef,nonce) cipher = Cipher(algorithm_chacha20,mode=None) decrypteur = cipher.decryptor() @@ -51,20 +52,4 @@ def dechiffrer(self,chemin_fichier_chiffer : str ,clef :bytes)->str: print(f"Une erreur est survenu : {e}") -key=os.urandom(32) -nonce =os.urandom(16) -message = b"Ceci est un test" -algo = algorithms.ChaCha20(key,nonce) -cipher = Cipher(algo,mode=None) -encryptor = cipher.encryptor() -ct = encryptor.update(message) - -print(ct) - -decryptor = cipher.decryptor() -dt = encryptor.update(ct) - -print(dt) - -# ChaCha20_Analyzer().dechiffrer("mission2.enc",) - +# print(ChaCha20_Analyzer().dechiffrer("mission2.enc",os.urandom(32))) \ No newline at end of file From 6ba0a411b81895c77b2c5b1b3ae04ed5f30ccaf5 Mon Sep 17 00:00:00 2001 From: Eunock-web Date: Thu, 7 Aug 2025 05:26:43 +0200 Subject: [PATCH 23/26] Correction des erreurs dans la fonction identifier_algo --- src/analyzers/chacha20_analyzer.py | 100 +++++++++++++---------------- 1 file changed, 44 insertions(+), 56 deletions(-) diff --git a/src/analyzers/chacha20_analyzer.py b/src/analyzers/chacha20_analyzer.py index fb4bccf..0279920 100644 --- a/src/analyzers/chacha20_analyzer.py +++ b/src/analyzers/chacha20_analyzer.py @@ -5,8 +5,12 @@ import os, struct import math -# Import de la classe CryptoAnalyzer -from ..crypto_analyzer import CryptoAnalyzer +# Import de la classe CryptoAnalyzer et utils +import sys +import os +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +from crypto_analyzer import CryptoAnalyzer +from utils import calculer_entropie # Définition de la classe ChaCha20_Analyzer class ChaCha20_Analyzer(CryptoAnalyzer): @@ -30,34 +34,7 @@ class ChaCha20_Analyzer(CryptoAnalyzer): _CHACHA20_LONGUEUR_TAG = 16 _CHACHA20_LONGUEUR_BLOC = 64 - def calculer_entropie(self, data: bytes) -> float: - """ - Calcule l'entropie de Shannon d'une séquence d'octets. - - Args: - data (bytes): Les données à analyser - - Returns: - float: L'entropie calculée (0-8 bits par octet) - """ - if not data: - return 0.0 - - # Compter les occurrences de chaque octet - compteur = [0] * 256 - for byte in data: - compteur[byte] += 1 - - # Calculer l'entropie - entropie = 0.0 - longueur = len(data) - - for count in compteur: - if count > 0: - probabilite = count / longueur - entropie -= probabilite * math.log2(probabilite) - - return entropie + def identifier_algo(self, chemin_fichier_chiffre: str) -> float: """ @@ -83,39 +60,47 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float: return 0.0 # Fichier trop petit pour contenir un nonce # Extraire le nonce présumé (12 premiers bytes) - nonce_presume = donnees[:self._CHACHA20_LONGUEUR_NONCE] + nonce = donnees[:self._CHACHA20_LONGUEUR_NONCE] donnees_chiffrees = donnees[self._CHACHA20_LONGUEUR_NONCE:] if len(donnees_chiffrees) == 0: return 0.0 # Pas de données chiffrées - # Critère 1: Vérifier la taille minimale (nonce + au moins quelques bytes de données) - score_taille = 1.0 if len(donnees) >= self._CHACHA20_LONGUEUR_NONCE + 16 else 0.0 + # Critère 1: Vérifier la taille minimale + if len(donnees) >= self._CHACHA20_LONGUEUR_NONCE + 16: + taille_min = 1.0 + else: + taille_min = 0.0 # Critère 2: Vérifier l'entropie des données chiffrées (doit être très élevée) - entropie = self.calculer_entropie(donnees_chiffrees) + entropie = calculer_entropie(donnees_chiffrees) # L'entropie d'un chiffrement ChaCha20 devrait être proche de 8 bits/octet - score_entropie = min(entropie / 8.0, 1.0) + if entropie / 8.0 > 1.0: + entropie_max = 1.0 + else: + entropie_max = entropie / 8.0 # Critère 3: Vérifier l'absence de padding (pas de contrainte de taille) # ChaCha20 est un chiffrement de flux, donc pas de padding # On vérifie que la taille des données chiffrées n'est pas un multiple d'une taille de bloc commune taille_donnees = len(donnees_chiffrees) - score_padding = 1.0 # Par défaut, on suppose pas de padding - - # Vérifier si la taille est un multiple de tailles de bloc communes (AES = 16, DES = 8) if taille_donnees % 16 == 0 or taille_donnees % 8 == 0: - score_padding = 0.5 # Réduire le score si c'est un multiple de tailles de bloc communes + padding_max = 0.5 + else: + padding_max = 1.0 # Critère 4: Vérifier l'entropie du nonce (doit être élevée aussi) - entropie_nonce = self.calculer_entropie(nonce_presume) - score_nonce = min(entropie_nonce / 8.0, 1.0) + entropie_nonce = calculer_entropie(nonce) + if entropie_nonce / 8.0 > 1.0: + nonce_max = 1.0 + else: + nonce_max = entropie_nonce / 8.0 # Calcul de la probabilité finale (moyenne pondérée des scores) - probabilite = (score_taille * 0.1 + - score_entropie * 0.4 + - score_padding * 0.3 + - score_nonce * 0.2) + probabilite = (taille_min * 0.1 + + entropie_max * 0.4 + + padding_max * 0.3 + + nonce_max * 0.2) return min(probabilite, 1.0) @@ -126,7 +111,7 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float: def filtrer_dictionnaire_par_indices(self, chemin_fichier_chiffre): pass - def generer_cle_candidates(self, chemin_fichier_chiffre): + def generer_cles_candidates(self, chemin_fichier_chiffre): ''' Cette fonction se charge de générer les clés candidates pour le déchiffremment du fichier chiffré en utilisant la dérivation sha256 pour renforcer les clées de chiffrement. @@ -146,29 +131,32 @@ def generer_cle_candidates(self, chemin_fichier_chiffre): return cle_candidates - def dechiffrer(self, chemin_fichier_chiffre: str, clef: bytes) -> str: - if len(clef) != 32: + def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: + if len(cle_donnee) != 32: raise ValueError("Erreur : La clé n'a pas la taille correcte") else: try: - with open(f"data/{chemin_fichier_chiffre}", 'rb') as f: + # Utiliser le chemin complet si c'est un chemin absolu, sinon ajouter le préfixe data/ + if os.path.isabs(chemin_fichier_chiffre): + fichier_path = chemin_fichier_chiffre + else: + fichier_path = f"data/{chemin_fichier_chiffre}" + + with open(fichier_path, 'rb') as f: nonce = f.read(self._CHACHA20_LONGUEUR_NONCE) texte_chiffre = f.read() - algorithm_chacha20 = algorithms.ChaCha20(clef, nonce) + algorithm_chacha20 = algorithms.ChaCha20(cle_donnee, nonce) cipher = Cipher(algorithm_chacha20, mode=None) decrypteur = cipher.decryptor() resultat = decrypteur.update(texte_chiffre) - # Essayer de décoder en UTF-8 avec gestion d'erreurs - try: - return resultat.decode('utf-8') - except UnicodeDecodeError: - return resultat.decode('utf-8', errors='ignore') + # Retourner les bytes bruts comme attendu par l'interface + return resultat except Exception as e: print(f"Une erreur est survenue : {e}") - return None + return b"" # print(ChaCha20_Analyzer().dechiffrer("mission2.enc",os.urandom(32))) \ No newline at end of file From 649fe35cfb79454e91af445e3bad7cf7f79aa0d7 Mon Sep 17 00:00:00 2001 From: mouwaficbdr Date: Fri, 8 Aug 2025 14:39:46 +0100 Subject: [PATCH 24/26] =?UTF-8?q?add:=20Impl=C3=A9mentation=20de=20Aes=5Fg?= =?UTF-8?q?cm=5Fanalyzer.generer=5Fcles=5Fcandidates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/analyzers/aes_gcm_analyzer.py | 88 +++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/analyzers/aes_gcm_analyzer.py b/src/analyzers/aes_gcm_analyzer.py index e69de29..460f376 100644 --- a/src/analyzers/aes_gcm_analyzer.py +++ b/src/analyzers/aes_gcm_analyzer.py @@ -0,0 +1,88 @@ +from ..crypto_analyzer import CryptoAnalyzer +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.hazmat.primitives import hashes +import re + +class Aes_Gcm_Analyzer(CryptoAnalyzer): + '''Détermine si l'algo aes_gcm 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. + + Cette classe a trois méthodes principales: + - identifier_algo: Détermine si l'algo de chiffrement utilsé sur le fichier chiffré qui lui est passé en paramètre est l'aes_gcm. + - generer_cles_candidates: Génère une liste de clés candidates pour le déchiffrement du fichier chiffré + - dechiffrer: fait le déchiffrement proprement dit sur la base de la liste des clés générées + + Attributes: + _PBKDF2_SALT: le salt utilisé pour le chiffrement + _PBKDF2_ITERATIONS: le nombre d'itérations faites au chiffrement + _PBKDF2_LONGUEUR_CLE: la longueur en octets de la clé à utiliser + + ''' + + _PBKDF2_SALT = b"AES_GCM_SALT_2024" #Fourni + _PBKDF2_ITERATIONS = 10000 #Fourni + _PBKDF2_LONGUEUR_CLE = 32 #Longueur de la clé + + def __filtrer_dictionnaire_par_indice(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 = "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 + motif_acronyme = re.compile(r"^[A-Z]{4}$") + + try: + with open(chemin_dictionnaire, "r", encoding="utf-8") as f: + for ligne in f: + mot = 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 = mot[:-4] # Extrait la partie acronyme + 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]: + ''' + Génère les clées candidates pour déchiffrer le fichier à partir de la liste retournée par filtrer_dictionnaire_par_indices. + + Args: + chemin_dictionnaire(str): le chemin du dictionnaire de mots de passes pour l'attaque par dictionnaire. + + Returns: + list[bytes]: liste des clés candidates. + ''' + + mots_de_passe_cible = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire) + + clees_candidates: list[bytes] = [] + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=self._PBKDF2_LONGUEUR_CLE, + iterations=self._PBKDF2_ITERATIONS, + salt=self._PBKDF2_SALT + ) + for mot_de_passe in mots_de_passe_cible: + mot_de_passe_en_octets: bytes = mot_de_passe.encode('utf-8') + cle_derivee: bytes = kdf.derive(mot_de_passe_en_octets) + clees_candidates.append(cle_derivee) + + return clees_candidates \ No newline at end of file From bbd767cc04656f26f275099be0b9388722fccd48 Mon Sep 17 00:00:00 2001 From: wesley-kami Date: Sun, 10 Aug 2025 22:57:09 +0100 Subject: [PATCH 25/26] =?UTF-8?q?impl=C3=A9mentatino=20de=20li'dentificati?= =?UTF-8?q?on=20de=20l'algo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/analyzers/aes_gcm_analyzer.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/analyzers/aes_gcm_analyzer.py b/src/analyzers/aes_gcm_analyzer.py index 22924d1..f162232 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 src.utils import calculer_entropie import re class Aes_Gcm_Analyzer(CryptoAnalyzer): @@ -88,7 +89,18 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: return clees_candidates def identifier_algo(self, chemin_fichier_chiffre): - return super().identifier_algo(chemin_fichier_chiffre) + try : + with open(chemin_fichier_chiffre,'rb') as f: + if len(f.read()) < 20 : # Prise en compte de l'entropie (12 bytes) et du tag (16 bytes) comme taille minimales pour un cryptage AES-GCM + entropie = 0.00 + if calculer_entropie(f.read()) > 8 : + entropie = 1.00 + except FileNotFoundError : + return 0.0 + + return entropie def dechiffrer(self, chemin_fichier_chiffre, cle_donnee): - return super().dechiffrer(chemin_fichier_chiffre, cle_donnee) \ No newline at end of file + return super().dechiffrer(chemin_fichier_chiffre, cle_donnee) + +Aes_Gcm_Analyzer().identifier_algo("data/mission2.enc") \ No newline at end of file From e14a4d5bf82638628339f2bc8dfc74b8443b0ad8 Mon Sep 17 00:00:00 2001 From: wesley-kami Date: Mon, 11 Aug 2025 00:12:25 +0100 Subject: [PATCH 26/26] update de l'identification --- src/analyzers/aes_gcm_analyzer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/analyzers/aes_gcm_analyzer.py b/src/analyzers/aes_gcm_analyzer.py index f162232..671a3b1 100644 --- a/src/analyzers/aes_gcm_analyzer.py +++ b/src/analyzers/aes_gcm_analyzer.py @@ -91,14 +91,14 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: def identifier_algo(self, chemin_fichier_chiffre): try : with open(chemin_fichier_chiffre,'rb') as f: - if len(f.read()) < 20 : # Prise en compte de l'entropie (12 bytes) et du tag (16 bytes) comme taille minimales pour un cryptage AES-GCM - entropie = 0.00 + if len(f.read()) < 28 : # Prise en compte de l'entropie (12 bytes) et du tag (16 bytes) comme taille minimales pour un cryptage AES-GCM + proba = 0.00 if calculer_entropie(f.read()) > 8 : - entropie = 1.00 + proba = 0.60 except FileNotFoundError : return 0.0 - return entropie + return proba def dechiffrer(self, chemin_fichier_chiffre, cle_donnee): return super().dechiffrer(chemin_fichier_chiffre, cle_donnee)