|
| 1 | +from ..detecteur_crypto import CryptoAnalyzer |
| 2 | +from ..utils import calculer_entropie |
| 3 | +import hashlib |
| 4 | +from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes |
| 5 | +from cryptography.hazmat.primitives.padding import PKCS7 |
| 6 | +class Blowfish_Analyzer(CryptoAnalyzer): |
| 7 | + '''Détermine si l'algo blowfish est utilisé, génère des clés et tente de de déchffrer un fichier chiffré en utilisant les clés générées. |
| 8 | + |
| 9 | + Cette classe a trois méthodes: |
| 10 | + - identifier_algo: Détermine si l'algo de chiffrement utilsé sur le fichier chiffré qui lui est passé en paramètre est blowfish. |
| 11 | + - generer_cles_candidates: Génère une liste de clés candidates pour le déchiffrement du fichier chiffré |
| 12 | + - dechiffrer: fait le déchiffrement proprement dit sur la base de la liste des clés générées |
| 13 | + |
| 14 | + Attributes: |
| 15 | + __BLOWFISH_TAILLE_BLOC : taille à considérer pour les blocs de données chiffrés que le PKCS7 doit prendre en compte (8 bits) |
| 16 | + __BLOWFISH_TAILLE_IV : taille du vecteur d'initialisation en début de fichier (8 bits) |
| 17 | +
|
| 18 | + ''' |
| 19 | + |
| 20 | + __BLOWFISH_TAILLE_BLOC = 8 |
| 21 | + __BLOWFISH_TAILLE_IV = 8 |
| 22 | + |
| 23 | + def identifier_algo(self, chemin_fichier_chiffre: str) -> float: |
| 24 | + ''' |
| 25 | + Détermine la probabilité que l'algo de chiffrement utilisé soit blowfish en: |
| 26 | + |
| 27 | + - vérifiant la présence d'un IV à l'en-tête (taille fichier > 8 octets) et que la taille du fichier est un multiple de 8 (blocs de 8 octets pour l'algo blowfish) |
| 28 | + - calculant l'entropie des données chiffrées |
| 29 | + - calculant l'entropie des sous blocs |
| 30 | + |
| 31 | + Args: |
| 32 | + chemin_fichier_chiffre(str): Le chemin du fichier chiffré à traiter (mission1.enc). |
| 33 | + |
| 34 | + Returns: |
| 35 | + float: probabilité calculée. |
| 36 | + ''' |
| 37 | + |
| 38 | + score = 0.0 |
| 39 | + try: |
| 40 | + with open(chemin_fichier_chiffre, "rb") as f: |
| 41 | + contenu_fichier: bytes = f.read() |
| 42 | + taille_totale = len(contenu_fichier) |
| 43 | + TAILLE_IV = 8 |
| 44 | + |
| 45 | + # Heuristique 1 : Vérification de la taille (le critère le plus important) |
| 46 | + if taille_totale > TAILLE_IV and taille_totale % 8 == 0: |
| 47 | + score += 0.4 |
| 48 | + |
| 49 | + donnees_chiffrees = contenu_fichier[TAILLE_IV:] |
| 50 | + |
| 51 | + # Heuristique 2 : Vérification de l'entropie globale |
| 52 | + entropie_globale = calculer_entropie(donnees_chiffrees) |
| 53 | + if entropie_globale > 7.5: |
| 54 | + score += 0.3 |
| 55 | + |
| 56 | + # Heuristique 3 : Vérification du "pattern Blowfish" (entropie par sous-blocs) |
| 57 | + taille_donnees = len(donnees_chiffrees) |
| 58 | + moitie = taille_donnees // 2 |
| 59 | + |
| 60 | + entropie_moitie1 = calculer_entropie(donnees_chiffrees[:moitie]) |
| 61 | + entropie_moitie2 = calculer_entropie(donnees_chiffrees[moitie:]) |
| 62 | + |
| 63 | + if entropie_moitie1 > 7.5 and entropie_moitie2 > 7.5: |
| 64 | + score += 0.3 |
| 65 | + |
| 66 | + except FileNotFoundError: |
| 67 | + return 0.0 |
| 68 | + |
| 69 | + return score |
| 70 | + |
| 71 | + |
| 72 | + def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> list[str]: |
| 73 | + """ |
| 74 | + Filtre le dictionnaire en se basant sur les indices de la mission 3. |
| 75 | + L'indice pointe vers un format de clé "sha + nombre + chiffres simples". |
| 76 | + |
| 77 | + Args: |
| 78 | + chemin_dictionnaire(str): Le chemin vers le fichier de dictionnaire. |
| 79 | + |
| 80 | + Returns: |
| 81 | + list[str]: Une liste de mots de passe filtrés. |
| 82 | + """ |
| 83 | + mots_filtres: list[str] = [] |
| 84 | + |
| 85 | + # Indices pour le préfixe et le suffixe |
| 86 | + prefixes = ("sha256", "sha384", "sha512", "sha1") |
| 87 | + suffixes = ("123", "456", "789") |
| 88 | + |
| 89 | + try: |
| 90 | + with open(chemin_dictionnaire, "r", encoding="utf-8") as f: |
| 91 | + for ligne in f: |
| 92 | + mot = ligne.strip() |
| 93 | + |
| 94 | + # Vérifie si le mot commence par un préfixe et se termine par un suffixe |
| 95 | + if mot.startswith(prefixes) and mot.endswith(suffixes): |
| 96 | + mots_filtres.append(mot) |
| 97 | + |
| 98 | + except FileNotFoundError: |
| 99 | + print(f"Erreur : Le fichier de dictionnaire '{chemin_dictionnaire}' est introuvable.") |
| 100 | + return [] |
| 101 | + |
| 102 | + return mots_filtres |
| 103 | + |
| 104 | + def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]: |
| 105 | + """ |
| 106 | + Génère une liste de clés candidates pour le déchiffrement. |
| 107 | + Les candidats incluent les mots de passe directs, leur hash MD5 et leur hash SHA1. |
| 108 | + |
| 109 | + Args: |
| 110 | + chemin_dictionnaire(str): Le chemin vers le fichier de dictionnaire. |
| 111 | + |
| 112 | + Returns: |
| 113 | + list[bytes]: Une liste des clés candidates sous forme d'octets. |
| 114 | + """ |
| 115 | + cles_candidates: list[bytes] = [] |
| 116 | + # Utilisation de la méthode privée pour filtrer les mots |
| 117 | + mots_de_passe_cible = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire) |
| 118 | + |
| 119 | + for mot in mots_de_passe_cible: |
| 120 | + mot_en_bytes = mot.encode("utf-8") |
| 121 | + |
| 122 | + # 1. Ajouter le mot de passe direct comme clé candidate |
| 123 | + cles_candidates.append(mot_en_bytes) |
| 124 | + |
| 125 | + # 2. Hachage MD5 et ajout à la liste (en bytes) |
| 126 | + hash_md5 = hashlib.md5(mot_en_bytes).digest() |
| 127 | + cles_candidates.append(hash_md5) |
| 128 | + |
| 129 | + # 3. Hachage SHA1 et ajout à la liste (en bytes) |
| 130 | + hash_sha1 = hashlib.sha1(mot_en_bytes).digest() |
| 131 | + cles_candidates.append(hash_sha1) |
| 132 | + |
| 133 | + return cles_candidates |
| 134 | + |
| 135 | + def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes: |
| 136 | + """ |
| 137 | + Déchiffre le fichier supposé crypté par l'algorithme blowfish avec la clé donnée en respectant les critères de |
| 138 | + - récupération de l'IV |
| 139 | + - suppression de padding |
| 140 | + |
| 141 | + Args: |
| 142 | + chemin_fichier_chiffre (str): le chemin vers le fichier chiffré |
| 143 | + clee_donnee (bytes): La clé à utiliser pour le déchiffrement |
| 144 | + Returns: |
| 145 | + bytes: les données originales |
| 146 | + """ |
| 147 | + |
| 148 | + #La taille de clé est dans l'intervalle 32-448bits et est multiple de 8 |
| 149 | + if len(cle_donnee) not in range(32, 448, 8): |
| 150 | + raise ValueError('Taille de clé invalide.') |
| 151 | + |
| 152 | + try: |
| 153 | + |
| 154 | + algorithm_blowfish = algorithms.Blowfish(cle_donnee) |
| 155 | + texte_chiffre = '' |
| 156 | + |
| 157 | + #Récupération de l'IV et des texte chiffré das le fichier |
| 158 | + with open(chemin_fichier_chiffre, 'rb') as f: |
| 159 | + initialization_vector = f.read(self.__BLOWFISH_TAILLE_IV) |
| 160 | + texte_chiffre = f.read() |
| 161 | + f.close() |
| 162 | + |
| 163 | + #Initialisation du cipher |
| 164 | + cipher = Cipher(algorithm_blowfish, modes.CBC(initialization_vector)) |
| 165 | + decrypteur = cipher.decryptor() |
| 166 | + |
| 167 | + #Suppresseur de padding |
| 168 | + supresseur_padding = PKCS7(self.__BLOWFISH_TAILLE_BLOC).unpadder() |
| 169 | + |
| 170 | + #Décriptage des données avec le padding(remplissage aléatoire) |
| 171 | + donnees_chiffrees_avec_padding = decrypteur.update(texte_chiffre) + decrypteur.finalize() |
| 172 | + |
| 173 | + #Suppression du padding et récupération de la donnée finale |
| 174 | + donnees_originales = supresseur_padding.update(donnees_chiffrees_avec_padding) + supresseur_padding.finalize() |
| 175 | + return donnees_originales |
| 176 | + |
| 177 | + except FileNotFoundError: |
| 178 | + raise |
| 179 | + |
| 180 | + |
0 commit comments