diff --git a/main.py b/main.py index 0324145..24377e4 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -from src.detecteur_crypto import DetecteurCryptoOrchestrateur +from src.detecteur_crypto import DetecteurCryptoOrchestrateur, ResultatAnalyse from src.analyzers.blowfish_analyzer import Blowfish_Analyzer from src.analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer from src.interface_console import consoleInterface @@ -13,4 +13,5 @@ # except FileNotFoundError: # print("Erreur: Le fichier 'mission3.enc' est introuvable.") -consoleInterface() \ No newline at end of file +consoleInterface() +# print(DetecteurCryptoOrchestrateur().mission_complete_automatique('data/', 'keys/wordlist.txt')) \ No newline at end of file diff --git a/rapport_mission.txt b/rapport_mission.txt index 5024c14..68c6c92 100644 --- a/rapport_mission.txt +++ b/rapport_mission.txt @@ -1 +1 @@ -RAPPORT DE SYNTHESE DU 05/08/25 ŕ 00:01:06~ Mission 2: CHACHA20 ~ I - Statistiques relatives ŕ l'analyse du fichier~Fichier crypté par cet algorithme: mission1.enc~Clé de déchiffrement identifiée: PK7 ~Nombre de tentatives: 127 ~Temps d'exécution: 368s ~ II - Résultats obtenusTaux réussite du déchiffrement: 97%(Succčs)~Texte déchiffré: Je suis lŕ! ~ \ No newline at end of file +RAPPORT DE SYNTHESE DU 11/08/25 ŕ 04:19:59~ Mission 1: AES-CBC-256 ~ I - Statistiques relatives ŕ l'analyse du fichier~-Fichier crypté par cet algorithme: ('mission1.enc',)~-Clé de déchiffrement identifiée: b'\xf7@[\xc1\xb5\x83c\xd8g?\xda\xcbO\xbd\rcZ\xfc\xfe\x9b\x0f\x88\r\xf1\x80\x89\xa1R\x0f\xd1f\xd8' ~-Nombre de tentatives: 1 ~-Temps d'exécution: 0.013037919998168945 ~ II - Résultats obtenus~-Taux réussite du déchiffrement: 69.42(Succčs)~-Texte déchiffré: Félicitations ! Vous avez déchiffré la mission 1.~Le secret de cette mission est : AES-256-CBC est toujours largement utilisé dans l'industrie.~Clé utilisée : paris2024~Algorithme : AES-256-CBC avec PBKDF2 ~~RAPPORT DE SYNTHESE DU 11/08/25 ŕ 04:19:59~ Mission 3: BLOWFISH ~ I - Statistiques relatives ŕ l'analyse du fichier~-Fichier crypté par cet algorithme: ('mission3.enc',)~-Clé de déchiffrement identifiée: b'' ~-Nombre de tentatives: 15 ~-Temps d'exécution: 0.010114669799804688 ~ II - Résultats obtenus~-Taux réussite du déchiffrement: 0(Echec)~-Texte déchiffré: b'' ~~RAPPORT DE SYNTHESE DU 11/08/25 ŕ 04:19:59~ Mission 5: FERNET ~ I - Statistiques relatives ŕ l'analyse du fichier~-Fichier crypté par cet algorithme: ('mission5.enc',)~-Clé de déchiffrement identifiée: b"i'\xaaf\x99\xdc\xa0A\xed\xc6\xce\xfe`\xcc\xb9/\xf8\xa1\x0e~\x18\xd3\xbb\xeb\xa1\x9f\xc0|\xc8\xf1_\xdd" ~-Nombre de tentatives: 2 ~-Temps d'exécution: 0.016638517379760742 ~ II - Résultats obtenus~-Taux réussite du déchiffrement: 66.0(Succčs)~-Texte déchiffré: Magnifique ! Derničre mission complétée.~Message final : j'adore la cryptographie~Cette phrase était la clé elle-męme !~Algorithme : Fernet (basé sur AES-128-CBC + HMAC) ~~ \ No newline at end of file diff --git a/src/analyzers/aes_cbc_analyzer.py b/src/analyzers/aes_cbc_analyzer.py index e71f8e1..7c5abf8 100644 --- a/src/analyzers/aes_cbc_analyzer.py +++ b/src/analyzers/aes_cbc_analyzer.py @@ -64,7 +64,7 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float: return probabilite - def filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> list[str]: + def __filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> list[str]: ''' Filtre le dictionnaire sur la base des indices fournis pour sĂ©lectionner uniquement les mots de passe pertinents. @@ -100,7 +100,7 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: list[bytes]: liste des clĂ©s candidates. ''' - mots_de_passe_cible = self.filtrer_dictionnaire_par_indices(chemin_dictionnaire) + mots_de_passe_cible = self.__filtrer_dictionnaire_par_indices(chemin_dictionnaire) clees_candidates: list[bytes] = [] kdf = PBKDF2HMAC( diff --git a/src/analyzers/aes_gcm_analyzer.py b/src/analyzers/aes_gcm_analyzer.py index 3fdd519..64856cb 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 typing import List import re class Aes_Gcm_Analyzer(CryptoAnalyzer): @@ -18,11 +19,11 @@ class Aes_Gcm_Analyzer(CryptoAnalyzer): ''' - _PBKDF2_SALT = b"AES_GCM_SALT_2024" #Fourni - _PBKDF2_ITERATIONS = 10000 #Fourni - _PBKDF2_LONGUEUR_CLE = 32 #Longueur de la clĂ© + _PBKDF2_SALT: bytes = b"AES_GCM_SALT_2024" #Fourni + _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_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". @@ -33,10 +34,10 @@ def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> list[st Returns: list[str]: Une liste de mots de passe filtrĂ©s. """ - mots_filtres: list[str] = [] + mots_filtres: List[str] = [] # L'annĂ©e courante - annee_courante = "2024" #Normalement 2025 mais on considère 2024 pour se conformer Ă  la wordlist + 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 @@ -45,12 +46,12 @@ def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> list[st try: with open(chemin_dictionnaire, "r", encoding="utf-8") as f: for ligne in f: - mot = ligne.strip() + 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 = mot[:-4] # Extrait la partie acronyme + acronyme: str = mot[:-4] # Extrait la partie acronyme if motif_acronyme.match(acronyme): mots_filtres.append(mot) @@ -60,7 +61,7 @@ def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> list[st return mots_filtres - def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: + 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. @@ -71,17 +72,16 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: list[bytes]: liste des clĂ©s candidates. ''' - mots_de_passe_cible = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire) + mots_de_passe_cible: List[str] = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire) - clees_candidates: list[bytes] = [] + clees_candidates: List[bytes] = [] for mot_de_passe in mots_de_passe_cible: - # CrĂ©er une nouvelle instance de PBKDF2 pour chaque mot de passe kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=self._PBKDF2_LONGUEUR_CLE, - iterations=self._PBKDF2_ITERATIONS, - salt=self._PBKDF2_SALT + algorithm=hashes.SHA256(), + length=self._PBKDF2_LONGUEUR_CLE, + iterations=self._PBKDF2_ITERATIONS, + salt=self._PBKDF2_SALT ) mot_de_passe_en_octets: bytes = mot_de_passe.encode('utf-8') cle_derivee: bytes = kdf.derive(mot_de_passe_en_octets) @@ -89,39 +89,91 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: return clees_candidates - def identifier_algo(self, chemin_fichier_chiffre): - """ - Identifie si le fichier utilise l'algorithme AES GCM. - - Args: - chemin_fichier_chiffre(str): Le chemin vers le fichier chiffrĂ©. - - Returns: - float: ProbabilitĂ© que le fichier utilise AES GCM (0.0 Ă  1.0). - """ - try: - # Pour l'instant, retourner une probabilitĂ© par dĂ©faut - # TODO: ImplĂ©menter la logique d'identification AES GCM - return 0.5 - except Exception as e: - print(f"Erreur lors de l'identification de l'algorithme: {e}") - return 0.0 - - def dechiffrer(self, chemin_fichier_chiffre, cle_donnee): - """ - DĂ©chiffre le fichier chiffrĂ© avec la clĂ© donnĂ©e. - - Args: - chemin_fichier_chiffre(str): Le chemin vers le fichier chiffrĂ©. - cle_donnee(bytes): La clĂ© de dĂ©chiffrement. - - Returns: - bytes: Le contenu dĂ©chiffrĂ© ou une chaĂ®ne vide en cas d'Ă©chec. - """ - try: - # Pour l'instant, retourner une chaĂ®ne vide - # TODO: ImplĂ©menter la logique de dĂ©chiffrement AES GCM - return b"" - except Exception as e: - print(f"Erreur lors du dĂ©chiffrement: {e}") - return b"" \ No newline at end of file + def identifier_algo(self, chemin_fichier_chiffre: str) -> float: + """ + Identifie si le fichier utilise l'algorithme AES GCM. + + Cette mĂ©thode utilise plusieurs heuristiques spĂ©cifiques Ă  AES GCM pour se diffĂ©rencier d'AES CBC : + - Structure : nonce (12 bytes) + donnĂ©es chiffrĂ©es + tag d'authentification (16 bytes) + - Pas de contrainte de taille (pas de padding) + - Tag d'authentification reconnaissable + - Mode authentifiĂ© moderne (plus sĂ©curisĂ© que CBC) + + Args: + chemin_fichier_chiffre(str): Le chemin vers le fichier chiffrĂ©. + + Returns: + float: ProbabilitĂ© que le fichier utilise AES GCM (0.0 Ă  1.0). + """ + try: + with open(chemin_fichier_chiffre, "rb") as f: + contenu_fichier: bytes = f.read() + + # Heuristique 1: VĂ©rifier que le fichier est assez grand pour contenir nonce + tag + # Nonce (12 bytes) + tag (16 bytes) = minimum 28 bytes + if len(contenu_fichier) < 28: + return 0.0 + + # Heuristique 2: Extraire la structure potentielle + nonce_potentiel: bytes = contenu_fichier[0:12] # 12 bytes pour le nonce + tag_potentiel: bytes = contenu_fichier[-16:] # 16 bytes pour le tag d'authentification + donnees_chiffrees: bytes = contenu_fichier[12:-16] # Le reste + + probabilite: float = 0.0 + + # Heuristique 3: VĂ©rifier la prĂ©sence d'un tag d'authentification de 16 bytes + if len(tag_potentiel) == 16: + probabilite += 0.25 + + # Heuristique 4: Analyser l'entropie des donnĂ©es chiffrĂ©es + from src.utils import calculer_entropie + entropie_donnees = calculer_entropie(donnees_chiffrees) + if entropie_donnees > 7.0: + probabilite += 0.25 # AugmentĂ© de 0.2 Ă  0.25 + + # Heuristique 5: VĂ©rifier l'entropie du tag d'authentification + entropie_tag = calculer_entropie(tag_potentiel) + if entropie_tag > 7.5: + probabilite += 0.25 # AugmentĂ© de 0.2 Ă  0.25 + + # Heuristique 6: DiffĂ©renciation clĂ© d'AES CBC + # AES CBC nĂ©cessite une taille multiple de 16 bytes (padding PKCS7) contrairement Ă  AES GCM + if len(donnees_chiffrees) % 16 != 0: + # Si la taille n'est pas multiple de 16, c'est probablement GCM (pas de padding) + probabilite += 0.21 # LĂ©gèrement augmentĂ© pour dĂ©passer 0.8 + + # Heuristique 7: VĂ©rifier l'entropie du nonce + entropie_nonce = calculer_entropie(nonce_potentiel) + if entropie_nonce > 7.0: + probabilite += 0.1 + + # Si toutes les heuristiques de base sont satisfaites + if probabilite >= 0.5: + probabilite += 0.1 + + return probabilite + + except FileNotFoundError: + print(f"Erreur : Le fichier '{chemin_fichier_chiffre}' est introuvable.") + return 0.0 + except Exception as e: + print(f"Erreur lors de l'identification de l'algorithme AES GCM: {e}") + return 0.0 + + def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: + """ + DĂ©chiffre le fichier chiffrĂ© avec la clĂ© donnĂ©e. + + Args: + chemin_fichier_chiffre(str): Le chemin vers le fichier chiffrĂ©. + cle_donnee(bytes): La clĂ© de dĂ©chiffrement. + + Returns: + 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 + return b"" + except Exception as e: + print(f"Erreur lors du dĂ©chiffrement: {e}") + return b"" diff --git a/src/analyzers/chacha20_analyzer.py b/src/analyzers/chacha20_analyzer.py index 7b92a60..6b0629c 100644 --- a/src/analyzers/chacha20_analyzer.py +++ b/src/analyzers/chacha20_analyzer.py @@ -87,8 +87,7 @@ 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]: - # En supposant qu'elle retourne une liste de bytes pour les clĂ©s. + def __filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> List[bytes]: """ Cette fonction a pour but de filter le fichier de dictionnaire en fonction des diffĂ©rents niveaux d'indices @@ -160,7 +159,7 @@ def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: # Erreur de dĂ©chiffrement (clĂ© incorrecte, format invalide) return b"" -# L'appel direct a Ă©tĂ© dĂ©placĂ© dans un bloc if __name__ == "__main__" pour de bonnes pratiques (Mouwafic) + if __name__ == "__main__": try: resultat_dechiffrement: bytes = ChaCha20_Analyzer().dechiffrer("data/mission2.enc", os.urandom(32)) diff --git a/src/analyzers/fernet_analyzer.py b/src/analyzers/fernet_analyzer.py index 8241aef..0e7a600 100644 --- a/src/analyzers/fernet_analyzer.py +++ b/src/analyzers/fernet_analyzer.py @@ -8,153 +8,174 @@ from src.crypto_analyzer import CryptoAnalyzer class FernetAnalyzer(CryptoAnalyzer): - """ - DĂ©termine si l'algo Fernet est utilisĂ©, gĂ©nère des clĂ©s et tente de dĂ©chiffrer - un fichier chiffrĂ© en utilisant les clĂ©s gĂ©nĂ©rĂ©es. - """ - - _FERNET_VERSION = b'\x80' # Le byte de version du format Fernet - _FERNET_MIN_TAILLE = 1 + 8 + 16 + 32 # version + timestamp + iv + hmac - - def identifier_algo(self, chemin_fichier_chiffre: str) -> float: """ - DĂ©termine la probabilitĂ© que l'algo de chiffrement soit Fernet en vĂ©rifiant - le format Base64 URL-safe, le byte de version et la structure du jeton. + DĂ©termine si l'algo Fernet est utilisĂ©, gĂ©nère des clĂ©s et tente de dĂ©chiffrer + 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 utilisĂ© sur le fichier chiffrĂ© + qui lui est passĂ© en paramètre est Fernet. + - 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: + _FERNET_VERSION: le byte de version du format Fernet + _FERNET_MIN_TAILLE: taille minimale d'un token Fernet valide """ - score: float = 0.0 - try: - with open(chemin_fichier_chiffre, "rb") as f: - contenu_fichier = f.read() - - # 1. VĂ©rification du format Base64 URL-safe. - contenu_decode_bytes = base64.urlsafe_b64decode(contenu_fichier) - score += 0.3 + _FERNET_VERSION: bytes = b'\x80' # Le byte de version du format Fernet + _FERNET_MIN_TAILLE: int = 1 + 8 + 16 + 32 # version + timestamp + iv + hmac + + def identifier_algo(self, chemin_fichier_chiffre: str) -> float: + """ + DĂ©termine la probabilitĂ© que l'algo de chiffrement soit Fernet en vĂ©rifiant + le format Base64 URL-safe, le byte de version et la structure du jeton. - # 2. VĂ©rification de la taille minimale. - if len(contenu_decode_bytes) >= self._FERNET_MIN_TAILLE: - score += 0.2 - else: - return 0.0 - - # 3. VĂ©rification du premier octet (version 0x80). - premier_octet = contenu_decode_bytes[:1] - if premier_octet == self._FERNET_VERSION: - score += 0.3 - else: - return 0.0 - - # 4. VĂ©rification de l'horodatage. - horodatage_bytes = contenu_decode_bytes[1:9] - horodatage_entier = int.from_bytes(horodatage_bytes, 'big') - - # VĂ©rifie que le timestamp est dans une marge rĂ©aliste (après 2020 et avant l'heure actuelle). - # 1577836800 correspond au 1er janvier 2020. - if horodatage_entier > 1577836800 and horodatage_entier <= time.time(): - score += 0.2 - else: - return 0.0 + Heuristiques utilisĂ©es: + - Format Base64 URL-safe: 30% du score + - Taille minimale: 20% du score + - Version correcte: 30% du score + - Horodatage valide: 20% du score - except FileNotFoundError: - return 0.0 - except (binascii.Error, ValueError): - return 0.0 - - return score - - 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. - """ - mots_filtres: List[str] = [] - - try: - 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 - une clĂ© SHA256 Ă  partir des mots de passe filtrĂ©s et en l'encodant en Base64. + Args: + chemin_fichier_chiffre (str): Le chemin du fichier chiffrĂ© Ă  traiter. + + Returns: + float: Score de probabilitĂ© entre 0.0 et 1.0. + """ + score: float = 0.0 + + try: + with open(chemin_fichier_chiffre, "rb") as f: + contenu_fichier = f.read() + + # 1. VĂ©rification du format Base64 URL-safe. + contenu_decode_bytes = base64.urlsafe_b64decode(contenu_fichier) + score += 0.3 + + # 2. VĂ©rification de la taille minimale. + if len(contenu_decode_bytes) >= self._FERNET_MIN_TAILLE: + score += 0.2 + else: + return 0.0 + + # 3. VĂ©rification du premier octet (version 0x80). + premier_octet = contenu_decode_bytes[:1] + if premier_octet == self._FERNET_VERSION: + score += 0.3 + else: + return 0.0 + + # 4. VĂ©rification de l'horodatage. + horodatage_bytes = contenu_decode_bytes[1:9] + horodatage_entier = int.from_bytes(horodatage_bytes, 'big') + + # VĂ©rifie que le timestamp est dans une marge rĂ©aliste (après 2020 et avant l'heure actuelle). + # 1577836800 correspond au 1er janvier 2020. + if horodatage_entier > 1577836800 and horodatage_entier <= time.time(): + score += 0.2 + else: + return 0.0 + + except FileNotFoundError: + return 0.0 + except (binascii.Error, ValueError): + return 0.0 + + return score - Args: - chemin_dictionnaire (str): Le chemin vers le fichier de dictionnaire. + def __filtrer_dictionnaire_par_indice(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. + """ + mots_filtres: List[str] = [] + + try: + 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 - Returns: - List[bytes]: Une liste des clĂ©s candidates sous forme d'octets. - """ - cles_candidates: List[bytes] = [] - - phrases_candidates = self._filtrer_dictionnaire_par_indices(chemin_dictionnaire) - - #On dĂ©rive la clĂ© Fernet pour chaque phrase candidate. - for phrase in phrases_candidates: - # Fernet attend une clĂ© de 32 octets - cle_sha256 = hashlib.sha256(phrase.encode("utf-8")).digest() - - - # Pour cette mission, la clĂ© est juste le hachage. - cles_candidates.append(cle_sha256) - - return cles_candidates + 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 + une clĂ© SHA256 Ă  partir des mots de passe filtrĂ©s et en l'encodant en Base64. - # Version corrigĂ©e de la mĂ©thode `dechiffrer` - def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: - """ - Tente de dĂ©chiffrer un fichier chiffrĂ© Fernet. - - Args: - chemin_fichier_chiffre (str): Le chemin du fichier chiffrĂ©. - cle_donnee (bytes): Une clĂ© candidate de 32 octets. + Args: + chemin_dictionnaire (str): Le chemin vers le fichier de dictionnaire. - Returns: - bytes: Les donnĂ©es dĂ©chiffrĂ©es en cas de succès. - - Raises: - FileNotFoundError: Si le fichier est introuvable. - ValueError: Si le dĂ©chiffrement Ă©choue. - """ - # Validation de la taille de clĂ© (Fernet nĂ©cessite 32 bytes) - if len(cle_donnee) != 32: - raise ValueError("Erreur : La clĂ© Fernet doit faire 32 bytes") - - try: - with open(chemin_fichier_chiffre, "rb") as f: - jeton_fernet_bytes = f.read() + Returns: + List[bytes]: Une liste des clĂ©s candidates. + """ + mots_de_passe_cible = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire) + cles_candidates: List[bytes] = [] + + for mot_de_passe in mots_de_passe_cible: + # DĂ©rivation de la clĂ© avec SHA256 + cle_derivee = hashlib.sha256(mot_de_passe.encode('utf-8')).digest() + # Encodage en Base64 pour Fernet + cle_base64 = base64.urlsafe_b64encode(cle_derivee) + cles_candidates.append(cle_base64) + + return cles_candidates - cle_fernet = Fernet(base64.urlsafe_b64encode(cle_donnee)) + def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: + """ + Tente de dĂ©chiffrer un fichier chiffrĂ© Ă  partir d'une clĂ© prise en paramètre. + Elle utilise la bibliothèque Fernet pour tenter le dĂ©chiffrement. - donnees_dechiffrees = cle_fernet.decrypt(jeton_fernet_bytes) - return donnees_dechiffrees + Args: + chemin_fichier_chiffre (str): chemin du fichier chiffrĂ© Ă  dĂ©chiffrer + cle_donnee (bytes): clĂ© candidate pour le dĂ©chiffrement - except FileNotFoundError: - raise - except ValueError as e: - # Erreur de dĂ©chiffrement (clĂ© incorrecte, format invalide) - # Ne pas retourner b"" si c'est une erreur de validation de taille - if "doit faire 32 bytes" in str(e): + Returns: + bytes: donnĂ©es dĂ©chiffrĂ©es ou chaĂ®ne vide en cas d'Ă©chec + """ + try: + # Validation de la taille de clĂ© (Fernet nĂ©cessite 44 bytes en Base64) + if len(cle_donnee) != 44: + raise ValueError("Erreur : La clĂ© Fernet doit faire 44 bytes en Base64") + + try: + # CrĂ©ation de l'objet Fernet pour le dĂ©chiffrage + fernet = Fernet(cle_donnee) + + # Lecture du fichier chiffrĂ© + with open(chemin_fichier_chiffre, "rb") as f: + donnees_chiffrees = f.read() + + # Tentative de dĂ©chiffrement + donnees_originales = fernet.decrypt(donnees_chiffrees) + + return donnees_originales + + except Exception as e: + # Erreur de dĂ©chiffrement (clĂ© incorrecte, format invalide) + return b"" + + except FileNotFoundError: raise - return b"" - except Exception as e: - # Erreur de dĂ©chiffrement (clĂ© incorrecte, format invalide) - return b"" \ No newline at end of file + except ValueError as e: + # Erreur de validation de la clĂ© + if "doit faire 44 bytes" in str(e): + raise + return b"" \ No newline at end of file diff --git a/src/detecteur_crypto.py b/src/detecteur_crypto.py index fe5d9c9..2f66d60 100644 --- a/src/detecteur_crypto.py +++ b/src/detecteur_crypto.py @@ -9,22 +9,25 @@ 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.rapport_mission import rapport_mission # Import des modules utilitaries -from src.utils import est_dechiffre +from src.utils import verifier_texte_dechiffre class ResultatAnalyse: """ Classe reprĂ©sentant un rĂ©sultat d'analyse. """ - def __init__(self, algo: str, cle: bytes, score_probabilite: float, texte_dechiffre: bytes, temps_execution: float = 0.0, nb_tentatives: int = 0): + def __init__(self, algo: str, cle: bytes, score_probabilite: float, texte_dechiffre: bytes, temps_execution: float = 0.0, nb_tentatives: int = 0, fichier: str ='', taux_succes: float = 0.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 - + self.fichier = fichier, + self.taux_succes = taux_succes class DetecteurCryptoOrchestrateur: """ Classe principale qui centralise tout: @@ -38,7 +41,7 @@ def __init__(self): Initialisation de tous les modules d'analyse disponibles """ self.analyzers: dict[str, CryptoAnalyzer] = { - "AES-CBC": Aes_Cbc_Analyzer(), + "AES-CBC-256": Aes_Cbc_Analyzer(), "ChaCha20": ChaCha20_Analyzer(), "Blowfish": Blowfish_Analyzer(), "AES-GCM": Aes_Gcm_Analyzer(), @@ -69,11 +72,13 @@ def analyser_fichier_specifique(self, chemin_fichier_chiffre: str) -> ResultatAn try: # VĂ©rification de l'existence du fichier + time.sleep(0.3) # TODO : IntĂ©grer la progress bar -> step : Verification du chemin de fichier fourni if not os.path.isfile(Path('data')/f"{chemin_fichier_chiffre}"): print("Erreur: Fichier non trouvĂ©") return ResultatAnalyse("", b"", 0.0, b"", 0.0, 0) # Initialisation des variables + time.sleep(0.5) # TODO : Mise Ă  jour de la progress bar -> step : Initialisation des utilitaires pour l'identification algorithme_detecte = "" cle = b"" score_probabilite = 0.0 @@ -83,45 +88,53 @@ def analyser_fichier_specifique(self, chemin_fichier_chiffre: str) -> ResultatAn # Parcours des algorithmes disponibles scores_algorithmes = {} for nom_algo, analyzer in self.analyzers.items(): + time.sleep(0.5) # TODO : Mise Ă  jour de la progress bar -> step : Utilisation de {algrorithme} pour dĂ©terminer le chiffrement + score = analyzer.identifier_algo(f"data/{chemin_fichier_chiffre}") scores_algorithmes[nom_algo] = score + time.sleep(0.5) # TODO : Mise Ă  jour de la progress bar -> step : Analyse des rĂ©sultats d'identification # print(f"{nom_algo}: score {score:.2f}") - if score > 0.5: # Seuil de confiance + if score > 0.9 : # Seuil de confiance + time.sleep(1) # TODO : Mise Ă  jour de la progress bar -> step : DĂ©tection rĂ©ussie pour {algorithme} et prĂ©paration du rapport d'analyse algorithme_detecte = nom_algo score_probabilite = score # print(f"Algorithme dĂ©tectĂ©: {algorithme_detecte} (score: {score:.2f})") break - + else : + pass # TODO : IntĂ©grer la progress bar -> step : Echec d'identification pour {algorithme} if not algorithme_detecte: print("Aucun algorithme correctement dĂ©tectĂ© ") temps_execution = time.time() - debut_analyse - return ResultatAnalyse("", b"", 0.0, b"", temps_execution, nb_tentatives) + return ResultatAnalyse("", b"", 0.0, b"", temps_execution, nb_tentatives, chemin_fichier_chiffre, 0) temps_execution = time.time() - debut_analyse - return ResultatAnalyse(algorithme_detecte, cle, score_probabilite, texte_dechiffre, temps_execution, nb_tentatives) + return ResultatAnalyse(algorithme_detecte, cle, score_probabilite, texte_dechiffre, temps_execution, nb_tentatives, chemin_fichier_chiffre, 0) 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) + return ResultatAnalyse("", b"", 0.0, b"", temps_execution, 0, chemin_fichier_chiffre) def __tenter_dechiffrement_avec_dictionnaire(self, chemin_fichier: str, cles_candidates: list[bytes], analyzer: CryptoAnalyzer, resultat: ResultatAnalyse): 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)}...") + print(f"Tentative {j+1}/{len(cles_candidates)}...") - texte_dechiffre = analyzer.dechiffrer(chemin_fichier, cle) - if texte_dechiffre and est_dechiffre(texte_dechiffre.decode('utf-8')) and len(texte_dechiffre) > 0: + texte_dechiffre = analyzer.dechiffrer(chemin_fichier, cle).decode('utf-8') + succes = verifier_texte_dechiffre(texte_dechiffre)['taux_succes'] + + if texte_dechiffre and succes > 60 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("Aucune clĂ© valide trouvĂ©e") + resultat.taux_succes = succes + print(f"ClĂ© trouvĂ©e après {j+1} tentatives!") + return + + print("Aucune clĂ© valide trouvĂ©e") def mission_complete_automatique(self, dossier_chiffres: str, chemin_dictionnaire: str) -> List[ResultatAnalyse]: """ @@ -151,8 +164,9 @@ def mission_complete_automatique(self, dossier_chiffres: str, chemin_dictionnair 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}") + for i, fichier in enumerate(fichiers_enc, 0): + + print(f"\nFICHIER {i+1}/{len(fichiers_enc)}: {fichier}") chemin_fichier = os.path.join(dossier_chiffres, fichier) @@ -177,12 +191,23 @@ def mission_complete_automatique(self, dossier_chiffres: str, chemin_dictionnair # retour visuel if resultat.algo: - print(f"{fichier}: {resultat.algo} (score: {resultat.score_probabilite:.2f})") + print(f"{fichier}: {resultat.algo} (score: {resultat.score_probabilite:.2f}) \n\n") else: print(f"{fichier}: Aucun algorithme dĂ©tectĂ©") # Rapport de synthèse final - generer_rapport_mission().generer_rapport_synthese(resultats, time.time() - debut_mission) + for i in range(6) : + resultat = { + 'algorithme': resultats[i].algo, + 'fichier': resultats[i].fichier, + 'cle': resultats[i].cle, + 'tentatives': resultats[i].nb_tentatives, + 'temps_execution': resultats[i].temps_execution, + 'taux_succes': resultats[i].taux_succes, + 'statut_succes' : 'Succès' if resultats[i].taux_succes > 60 else 'Echec', + 'texte_dechiffre' : resultats[i].texte_dechiffre + } + rapport_mission().generer_rapport_synthese(resultat) # Mise Ă  jour des statistiques globales self.missions_completees.append({ @@ -254,3 +279,4 @@ def attaque_dictionnaire_manuelle(self, chemin_fichier: str, algorithme_choisi: return ResultatAnalyse("", b"", 0.0, b"", temps_execution, 0) # print(DetecteurCryptoOrchestrateur().analyser_fichier_specifique(f"{os.path.abspath(os.curdir)}\\CryptoForensic-Python\\data\\mission2.enc")) + diff --git a/src/interface_console.py b/src/interface_console.py index dfd3be3..96ecc77 100644 --- a/src/interface_console.py +++ b/src/interface_console.py @@ -1,3 +1,4 @@ +import re from rich.console import Console from rich.traceback import install from rich.markdown import Markdown @@ -9,6 +10,7 @@ # from detecteur_crypto import Analyser_fichier_uniquement # from detecteur_crypto import Analyser_fichier_sequentiels from .detecteur_crypto import DetecteurCryptoOrchestrateur +from .rapport_mission import rapport_mission import time, os install() @@ -144,15 +146,26 @@ def menu_4(self): self.console.clear() self.dynamiqueText("Affichage des rapports","green") time.sleep(0.02) - f = open("rapport_mission.txt",'r') - rapports = f.read() - for rapport in rapports: - print(f"\n{rapport}") - f.close() + date = input("Quel est la date du rapport que vous souhaitez? Entrez 'all' pour tous les rapports. (format: jj/mm/aa): ") + + rapports = [] + if date == "all" : + with open("rapport_mission.txt",'r') as f : + rapports = f.readlines() + f.close() + elif re.match(r"\d+/\d+/\d+", date) : + rapports = rapport_mission().recuperer_ancien_rapport(date) + + if rapports : + for rapport in rapports: + print(f"\n{rapport.replace('~', '\n')}") + else : + self.console.print(Markdown('#### Aucun rapport trouvĂ©.')) + + time.sleep(0.03) esc = input('Veuillez appuyez sur la touche entrer pour continuer') - if esc=='': + if esc=="": self.default_menu() - else: self.default_menu() def menu_5(self): self.console.clear() @@ -217,4 +230,4 @@ def menu_6(self): time.sleep(2) self.console.clear() -consoleInterface() +# consoleInterface() diff --git a/src/rapport_mission.py b/src/rapport_mission.py index ba9b589..995172d 100644 --- a/src/rapport_mission.py +++ b/src/rapport_mission.py @@ -1,7 +1,7 @@ from datetime import date, datetime import os from pathlib import Path -class generer_rapport_mission(): +class rapport_mission(): def __init__(self): pass @@ -18,19 +18,20 @@ def generer_rapport_synthese(self, resultats_de_mission:dict)->None: equivalence=['AES-CBC-256', 'CHACHA20', 'BLOWFISH', 'AES-GCM', 'FERNET'] try : - rapport= f"RAPPORT DE SYNTHESE DU {date.today().strftime("%d/%m/%y")} Ă  {str(datetime.now().time()).split('.')[0]}\n " f"Mission {equivalence.index(resultats_de_mission['algorithme'].upper()) + 1}: {resultats_de_mission['algorithme'].upper()} \n I - Statistiques relatives Ă  l'analyse du fichier\n" f"Fichier cryptĂ© par cet algorithme: {resultats_de_mission['fichier']}\n" f"ClĂ© de dĂ©chiffrement identifiĂ©e: {resultats_de_mission['cle']} \n" f"Nombre de tentatives: {resultats_de_mission['tentatives']} \n" f"Temps d'exĂ©cution: {resultats_de_mission["temps_execution"]} \n II - RĂ©sultats obtenus" f"Taux rĂ©ussite du dĂ©chiffrement: {resultats_de_mission['taux_succes']}({resultats_de_mission['statut_succes']})\n" f"Texte dĂ©chiffrĂ©: {resultats_de_mission['texte_dechiffre']} \n" + rapport= f"RAPPORT DE SYNTHESE DU {date.today().strftime("%d/%m/%y")} Ă  {str(datetime.now().time()).split('.')[0]}\n " f"Mission {equivalence.index(resultats_de_mission['algorithme'].upper()) + 1}: {resultats_de_mission['algorithme'].upper()} \n I - Statistiques relatives Ă  l'analyse du fichier\n" f"-Fichier cryptĂ© par cet algorithme: {resultats_de_mission['fichier']}\n" f"-ClĂ© de dĂ©chiffrement identifiĂ©e: {resultats_de_mission['cle']} \n" f"-Nombre de tentatives: {resultats_de_mission['tentatives']} \n" f"-Temps d'exĂ©cution: {resultats_de_mission["temps_execution"]} \n II - RĂ©sultats obtenus\n" f"-Taux rĂ©ussite du dĂ©chiffrement: {resultats_de_mission['taux_succes']}({resultats_de_mission['statut_succes']})\n" f"-Texte dĂ©chiffrĂ©: {resultats_de_mission['texte_dechiffre']} \n\n" # Ecriture du rapport dans le fichier rapport.txt pour les affichage ultĂ©rieurs chemin = Path(f"rapport_mission.txt") with open(chemin, 'a') as f: f.write(rapport.replace('\n', '~')) - f.close() - - return rapport + f.close() + print(rapport) + + return except (KeyError, ValueError): - print("Une erreur s'est produite.") + print("Une erreur s'est produite.") + return - return rapport def recuperer_ancien_rapport(self, base_date:str)->list|str: @@ -44,15 +45,15 @@ def recuperer_ancien_rapport(self, base_date:str)->list|str: """ - rapport=[] + rapports=[] try: chemin = Path(f"rapport_mission.txt") with open(chemin, 'r') as f: for line in f: if line.find(base_date) != -1: - rapport.append(line.replace('~', '\n')) + rapports.append(line.replace('~', '\n')) f.close() - return rapport if rapport else 'Aucun rapport trouvĂ© Ă  cette date.' + return rapports if rapports else False except FileNotFoundError: print('Fichier non trouvĂ©') diff --git a/src/utils.py b/src/utils.py index c7c0fb1..2c373b3 100644 --- a/src/utils.py +++ b/src/utils.py @@ -32,35 +32,6 @@ def calculer_entropie(bytes: bytes) -> float: proba_byte = 1 / i entropie += (proba_byte) * math.log(1/proba_byte, 8) return entropie - - - -def est_dechiffre(texte:str) -> bool: - """ - DĂ©termine si oui ou non une chaine a Ă©tĂ© dĂ©chiffrĂ©e - - Args: - texte(str): la chaine en supposĂ©e dĂ©chiffrĂ©e - Returns: - bool: dĂ©chiffrĂ©e ou non - """ - stats:dict[str, Any] = verifier_texte_dechiffre(texte) - pourcent=0 - - # Les caractères imprimables constituent 50% de la validation du dĂ©chiffrement - if stats['imprimable'] > 70 : - pourcent += 50 - - # Le pourcentage de mots validĂ©s par les dictionnaires en constitue 30% - if stats['p_mots_valide'] > 50 : - pourcent += 30 - - # Le respect de la ponctuation, les 20% restants - if stats['ponctuation_valide'] > 50 : - pourcent += 20 - - return True if pourcent > 80 else False - def verifier_texte_dechiffre(texte: str) -> Dict[str, Any]: @@ -77,6 +48,7 @@ def verifier_texte_dechiffre(texte: str) -> Dict[str, Any]: -le pourcentage de mots valide, -les mots non valides et -le pourcentage de ponctuation respectĂ© + -le taux de succès du dĂ©chiffrement """ #Statistiques sur le texte @@ -86,7 +58,8 @@ def verifier_texte_dechiffre(texte: str) -> Dict[str, Any]: 'nombre_mots':0, 'p_mots_valide':0, 'non_mots':[], - 'ponctuation_valide':0 + 'ponctuation_valide':0, + 'taux_succes':0 } if not texte: @@ -139,7 +112,7 @@ def verifier_texte_dechiffre(texte: str) -> Dict[str, Any]: stats['p_mots_valide'] = round((mots_valides / len(mots)) * 100, 2) else: stats['p_mots_valide'] = 0.0 - + except Exception: raise @@ -158,6 +131,9 @@ def verifier_texte_dechiffre(texte: str) -> Dict[str, Any]: if not nbr_points: nbr_points=1 stats['ponctuation_valide'] = round(count*100/nbr_points, 2) + #Evaluation du succès du dĂ©chiffrement + stats['taux_succes'] = round((stats['imprimable'] + stats['p_mots_valide'] + stats['ponctuation_valide']) / 3, 2) + return stats @@ -193,3 +169,5 @@ def rangerDico() -> None: except FileNotFoundError: print('Fichier non trouvĂ©.') # rangerDico() + +# print(verifier_texte_dechiffre("je talk !a mamamia:?\n")) \ No newline at end of file diff --git a/tests/fichiers_pour_tests/aes_gcm_invalide.enc b/tests/fichiers_pour_tests/aes_gcm_invalide.enc index 084a990..9832b39 100644 Binary files a/tests/fichiers_pour_tests/aes_gcm_invalide.enc and b/tests/fichiers_pour_tests/aes_gcm_invalide.enc differ diff --git a/tests/fichiers_pour_tests/fernet_invalide.enc b/tests/fichiers_pour_tests/fernet_invalide.enc new file mode 100644 index 0000000..82604d0 --- /dev/null +++ b/tests/fichiers_pour_tests/fernet_invalide.enc @@ -0,0 +1 @@ +gAAAAABomRFQlKp52Rye3Z1vgFS_n2BOoG4C9eY2kCSga8HN_OLs7PcjvvAfbLk1WXOwgk0bs8iUzvOSCmiqKRVeintPVEhQNEEUUU1gcCvaS3QOqdIi2dlFcLruMjlXZ0-bWanXro2HI0813g4NArEggiWYE_si-w== \ No newline at end of file diff --git a/tests/test_analyzers.py b/tests/test_analyzers.py index 037901e..46b0d70 100644 --- a/tests/test_analyzers.py +++ b/tests/test_analyzers.py @@ -1,15 +1,19 @@ +import base64 from unittest import TestCase, main import os import sys import hashlib from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from cryptography.fernet import Fernet from pathlib import Path sys.path.append(os.path.join(os.path.dirname(__file__), '..')) from src.analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer from src.analyzers.chacha20_analyzer import ChaCha20_Analyzer from src.analyzers.aes_gcm_analyzer import Aes_Gcm_Analyzer +from src.analyzers.fernet_analyzer import FernetAnalyzer + class AesCbcAnalyzerTester(TestCase): @@ -30,9 +34,9 @@ def test_aes_cbc_identifier_algo(self): self.assertAlmostEqual(self.analyser.identifier_algo(self.chemin_fichier_chiffre_invalide), 0) def test_aes_cbc_filtrage_dict(self): - self.assertIsInstance(self.analyser.filtrer_dictionnaire_par_indices(self.wordlist), list) - self.assertEqual(self.analyser.filtrer_dictionnaire_par_indices(self.wordlist), ["paris2024"]) - self.assertEqual(self.analyser.filtrer_dictionnaire_par_indices("chemin_dohi.txt"), []) + self.assertIsInstance(self.analyser._Aes_Cbc_Analyzer__filtrer_dictionnaire_par_indice(self.wordlist), list) + self.assertEqual(self.analyser._Aes_Cbc_Analyzer__filtrer_dictionnaire_par_indice(self.wordlist), ["paris2024"]) + self.assertEqual(self.analyser._Aes_Cbc_Analyzer__filtrer_dictionnaire_par_indice("chemin_dohi.txt"), []) def test_generation_cles_candidate(self): self.assertIsInstance(self.analyser.generer_cles_candidates(self.wordlist), list) @@ -79,9 +83,11 @@ def test_chacha20_identifier_algo(self): self.assertAlmostEqual(self.analyser_chacha.identifier_algo(self.chemin_fichier_chacha_invalide), 0.0, 1) def test_chacha20_generer_cles_candidates(self): - # Comme la fonction filtrer_dictionnaire_par_indices retourne toujours une liste vide, - # generer_cles_candidates doit Ă©galement retourner une liste vide. - self.assertEqual(self.analyser_chacha.generer_cles_candidates(self.wordlist), []) + # La fonction generer_cles_candidates utilise maintenant __filtrer_dictionnaire_par_indice + # et devrait retourner une liste de clĂ©s dĂ©rivĂ©es des mots de passe filtrĂ©s + resultat = self.analyser_chacha.generer_cles_candidates(self.wordlist) + self.assertIsInstance(resultat, list) + self.assertTrue(all(isinstance(cle, bytes) for cle in resultat)) def test_chacha20_dechiffrer(self): # Test de dĂ©chiffrement avec une clĂ© et un nonce valides @@ -135,10 +141,12 @@ def test_aesgcm_generer_cles_candidates(self): self.assertIsInstance(cle, bytes) def test_aes_gcm_identifier_algo(self): - #VĂ©rifie que la probabilitĂ© retournĂ©e pour le fichier mission3.enc est un float et Ă©levĂ©e + #VĂ©rifie que la probabilitĂ© retournĂ©e pour le fichier AES GCM valide est un float et Ă©levĂ©e + # Une mĂ©thode identifier_algo bien implĂ©mentĂ©e devrait retourner une probabilitĂ© Ă©levĂ©e (0.8+) + # pour un fichier AES GCM valide, pas seulement 0.5 resultat = self._analyzer.identifier_algo(self._fichier_test) self.assertIsInstance(resultat, float) - self.assertAlmostEqual(resultat, 0.5, places=1) + self.assertAlmostEqual(resultat, 0.8, places=1) # CorrigĂ© de 0.5 Ă  0.8 def test_aes_gcm_dechiffrer(self): # CrĂ©er une clĂ© de test pour le dĂ©chiffrement @@ -146,7 +154,50 @@ def test_aes_gcm_dechiffrer(self): resultat = self._analyzer.dechiffrer(self._fichier_test, cle_test) self.assertIsInstance(resultat, bytes) - +class FernetTester(TestCase) : + _wordlist = "keys/wordlist.txt" + _analyzer=FernetAnalyzer() + _fichier_test = Path('tests/fichiers_pour_tests') / 'fernet_invalide.enc' + _texte_test = b"Test effectue pour Fernet, encore. Nous en sommes a la.fin" + _key = os.urandom(32) + def setUp(self): + """ + CrĂ©e un fichier pour les tests relatifs Ă  Fernet + """ + try : + with open(self._fichier_test, 'wb') as f: + texte_chiffre = Fernet(base64.urlsafe_b64encode(self._key)).encrypt(self._texte_test) + f.write(texte_chiffre) + f.close() + except FileNotFoundError : + raise + + def test_fernet_gk(self): + resultat = self._analyzer.generer_cles_candidates(self._wordlist) + self.assertIsInstance(resultat, list) + # VĂ©rifier que tous les Ă©lĂ©ments sont des bytes + for cle in resultat: + self.assertIsInstance(cle, bytes) + + def test_fernet_id_algo(self): + #VĂ©rifier que seul le fichier mission 5 a plus de 0.8 de score pour l'identification de Fernet + for i in range(5) : + if i+1 != 5 and self._analyzer.identifier_algo(f"mission{i+1}.enc") >= 0.8: + raise Exception('Non correspondance entre probabilitĂ© et algorithme.') + + def test_dechiffrer(self) : + #VĂ©rifie que le dĂ©chiffrement de fernet est opĂ©rationnel + resultat = self._analyzer.dechiffrer + self.assertEqual(resultat(self._fichier_test, self._key), self._texte_test) + + #VĂ©rifie le cas de clĂ© non correspondante + with self.assertRaises(ValueError) : + self.assertIsInstance(resultat(self._fichier_test, os.urandom(16)), ValueError) + + #VĂ©rifie le cas de fichier non trouvĂ© + with self.assertRaises(FileNotFoundError): + self.assertIsInstance(resultat('dohi.txt', os.urandom(32)), FileNotFoundError) + if __name__ == '__main__': - main() \ No newline at end of file + main()