Skip to content

Commit ec71247

Browse files
mouwaficbdre-mandyseathiel-12
authored
Hotfix/problèmes globaux (#48)
* Documentation de la fonction d'entropie * Essai de fusion (1/2) * Implementation de la fonction de validation du texte déchiffrer * Récupération du main (1/2) * Mise en place des tests unitaires (1/4) * Revert "Mise en place des tests unitaires (1/4)" This reverts commit e965801. * Mise en place des tests unitaires (1/4) * Définition de la classe de gestion des rapports de ission et implémentatioon de la fonction de génération des rapport de synthèse * Implémentation de la fonction de recherche d'anciens rapports * Mise en place des tests liés à l'analyzer aes cbc * Corrections de typage et de logique * Correction du comportement à la levée de l'exception * Correction de la lgoque de test de test_exception_déchiffrer * Corection de la logique de test_verification_texte_dechiffre * fix: Utilisation de pathlib pour une gestion portable des chemins. * merge réussi * Validation des tests et corrections du rapport mission * chekpoint: Blowfish _Analyzer.identifier_algo() * add: Intiialisation de Blowfish_Analyzer et implémentation de Blowfish_Analyzer.identifier_algo() * Implémentation de Blowfish_Analyzer.filtrer_dictionnaire_par_indice() * Correction de bug * Implementer de Blowfish déchiffrer * Integration de blowfish dans le detecteur crypto * Gestion des bugs de chemin et d'import * Update gitignore * add: Fichier run_tests.py qui run tous les tests et donne le feedback * fix: Standardisation uniforme de tous les imports pour uniformité * tests aes_gcm * fix: Correction de la validation de la taille de clé dans Blowfish_Analyzer pour respecter l'intervalle de 4 à 56 bytes. * fix: Amélioration et uniformisation de la gestion des erreurs pour les différents cas. * fix: Correction des erreurs de logique * merged * test de l'analyzer Fernet * Correction de l'orchestrateur et ajustements correspondants * Lancement du système de stepping pour la progress bar * fix: Corrections mineures * add: Script de test pour les identifier_algo() * add: Script pour afficher les résultats de décryptage * add: Script pour tester les déchiffrer() * fix: Implémentation des méthodes manquantes et correction des bugs * fix: Retrait du fallback poly-1305 pour l'analyzer ChaCha20 --------- Co-authored-by: e-mandy <andymfrd02@gmail.com> Co-authored-by: Seathiel <ogoudedjimonde@gmail.com>
1 parent 31f6516 commit ec71247

File tree

7 files changed

+488
-79
lines changed

7 files changed

+488
-79
lines changed

scripts/show_decrypted_texts.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Affiche les textes déchiffrés pour chaque mission en utilisant l'analyzer correspondant.
4+
"""
5+
import sys
6+
from pathlib import Path
7+
from typing import Dict, Tuple, Type
8+
9+
sys.path.append('.')
10+
11+
from src.analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer
12+
from src.analyzers.chacha20_analyzer import ChaCha20_Analyzer
13+
from src.analyzers.blowfish_analyzer import Blowfish_Analyzer
14+
from src.analyzers.aes_gcm_analyzer import Aes_Gcm_Analyzer
15+
from src.analyzers.fernet_analyzer import FernetAnalyzer
16+
from src.utils import verifier_texte_dechiffre
17+
18+
MISSIONS: Dict[str, Tuple[str, Type]] = {
19+
'AES-CBC': ('data/mission1.enc', Aes_Cbc_Analyzer),
20+
'ChaCha20': ('data/mission2.enc', ChaCha20_Analyzer),
21+
'Blowfish': ('data/mission3.enc', Blowfish_Analyzer),
22+
'AES-GCM': ('data/mission4.enc', Aes_Gcm_Analyzer),
23+
'Fernet': ('data/mission5.enc', FernetAnalyzer),
24+
}
25+
26+
WORDLIST = 'keys/wordlist.txt'
27+
28+
29+
def main() -> None:
30+
if not Path(WORDLIST).exists():
31+
print(f"Wordlist manquante: {WORDLIST}")
32+
sys.exit(1)
33+
34+
for algo, (mission_path, AnalyzerCls) in MISSIONS.items():
35+
print("=" * 70)
36+
print(f"{algo} -> {mission_path}")
37+
if not Path(mission_path).exists():
38+
print(f"Fichier introuvable: {mission_path}")
39+
continue
40+
41+
analyzer = AnalyzerCls()
42+
try:
43+
cles = analyzer.generer_cles_candidates(WORDLIST)
44+
except Exception as e:
45+
print(f"Erreur génération clés: {e}")
46+
cles = []
47+
48+
meilleure_stat = -1.0
49+
meilleur_texte_b = b''
50+
meilleure_cle = None
51+
52+
for cle in cles:
53+
try:
54+
res = analyzer.dechiffrer(mission_path, cle)
55+
except Exception:
56+
continue
57+
if not res:
58+
continue
59+
# Sanitize and score
60+
texte = res.decode('utf-8', errors='ignore').replace('\x00', ' ')
61+
try:
62+
taux = float(verifier_texte_dechiffre(texte).get('taux_succes', 0.0))
63+
except Exception:
64+
taux = 0.0
65+
if taux > meilleure_stat:
66+
meilleure_stat = taux
67+
meilleur_texte_b = res
68+
meilleure_cle = cle
69+
# Early stop on good plaintext
70+
if taux >= 60.0:
71+
break
72+
73+
if meilleure_cle is None:
74+
print("❌ Aucun texte déchiffré trouvé")
75+
else:
76+
texte = meilleur_texte_b.decode('utf-8', errors='ignore')
77+
print(f"✅ Meilleur taux: {meilleure_stat:.2f}%")
78+
print("--- TEXTE DÉCHIFFRÉ ---")
79+
print(texte.strip())
80+
print("------------------------")
81+
82+
if __name__ == '__main__':
83+
main()

src/analyzers/aes_gcm_analyzer.py

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from src.crypto_analyzer import CryptoAnalyzer
22
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
33
from cryptography.hazmat.primitives import hashes
4+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
45
from typing import List
56
import re
67

@@ -23,43 +24,30 @@ class Aes_Gcm_Analyzer(CryptoAnalyzer):
2324
_PBKDF2_ITERATIONS: int = 10000 #Fourni
2425
_PBKDF2_LONGUEUR_CLE: int = 32 #Longueur de la clé
2526

26-
def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> List[str]:
27+
def __filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> List[str]:
2728
"""
2829
Filtre le dictionnaire en se basant sur les indices de la mission 4.
2930
L'indice pointe vers le format de clé "Acronyme en majuscules + 4 chiffres".
30-
31-
Args:
32-
chemin_dictionnaire(str): Le chemin vers le fichier de dictionnaire.
33-
34-
Returns:
35-
list[str]: Une liste de mots de passe filtrés.
3631
"""
3732
mots_filtres: List[str] = []
38-
39-
# L'année courante
40-
annee_courante: str = "2024" #Normalement 2025 mais on considère 2024 pour se conformer à la wordlist
41-
42-
# Définition du motif d'acronyme de 4 lettres en majuscules
43-
# On utilise une expression régulière pour plus de robustesse
33+
annee_courante: str = "2024" # Normalement 2025 mais on considère 2024 pour se conformer à la wordlist
4434
motif_acronyme = re.compile(r"^[A-Z]{4}$")
45-
35+
4636
try:
4737
with open(chemin_dictionnaire, "r", encoding="utf-8") as f:
4838
for ligne in f:
4939
mot: str = ligne.strip()
50-
51-
# Vérifie si le mot de passe correspond au format de l'indice
52-
# ex: NATO2024, UN2024, etc.
5340
if mot.endswith(annee_courante):
54-
acronyme: str = mot[:-4] # Extrait la partie acronyme
41+
acronyme: str = mot[:-4]
5542
if motif_acronyme.match(acronyme):
5643
mots_filtres.append(mot)
57-
5844
except FileNotFoundError:
5945
print(f"Erreur : Le fichier de dictionnaire '{chemin_dictionnaire}' est introuvable.")
6046
return []
61-
47+
6248
return mots_filtres
49+
50+
6351

6452
def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]:
6553
'''
@@ -72,7 +60,7 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]:
7260
list[bytes]: liste des clés candidates.
7361
'''
7462

75-
mots_de_passe_cible: List[str] = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire)
63+
mots_de_passe_cible: List[str] = self.__filtrer_dictionnaire_par_indices(chemin_dictionnaire)
7664

7765
clees_candidates: List[bytes] = []
7866

@@ -151,6 +139,11 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float:
151139
if probabilite >= 0.5:
152140
probabilite += 0.1
153141

142+
# Normalisation du score dans [0.0, 1.0]
143+
if probabilite > 1.0:
144+
probabilite = 1.0
145+
if probabilite < 0.0:
146+
probabilite = 0.0
154147
return probabilite
155148

156149
except FileNotFoundError:
@@ -172,8 +165,41 @@ def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes:
172165
bytes: Le contenu déchiffré ou une chaîne vide en cas d'échec.
173166
"""
174167
try:
175-
# TODO: Implémenter la logique de déchiffrement AES GCM
168+
# Validation taille de clé: AES-256 => 32 octets
169+
if len(cle_donnee) != self._PBKDF2_LONGUEUR_CLE:
170+
raise ValueError("Erreur : La clé AES-256 doit faire 32 bytes")
171+
172+
# Lecture du fichier: nonce (12B) + données + tag (16B)
173+
with open(chemin_fichier_chiffre, "rb") as f:
174+
donnees = f.read()
175+
176+
if len(donnees) < 12 + 16:
177+
return b""
178+
179+
nonce = donnees[:12]
180+
ciphertext_tag = donnees[12:]
181+
if len(ciphertext_tag) < 16:
182+
return b""
183+
ciphertext = ciphertext_tag[:-16]
184+
tag = ciphertext_tag[-16:]
185+
186+
# Déchiffrement AES-GCM
187+
cipher = Cipher(algorithms.AES(cle_donnee), modes.GCM(nonce, tag))
188+
decryptor = cipher.decryptor()
189+
try:
190+
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
191+
return plaintext
192+
except Exception:
193+
# Tag invalide / clé incorrecte
194+
return b""
195+
196+
except FileNotFoundError:
197+
raise
198+
except ValueError as e:
199+
# Erreur de validation de clé
200+
if "doit faire 32 bytes" in str(e):
201+
raise
176202
return b""
177203
except Exception as e:
178-
print(f"Erreur lors du déchiffrement: {e}")
204+
# Erreur générique
179205
return b""

src/analyzers/blowfish_analyzer.py

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
import hashlib
44
import base64
55
import re
6-
from cryptography.hazmat.primitives.ciphers import Cipher, modes
6+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
77
from cryptography.hazmat.primitives.padding import PKCS7
8-
from cryptography.hazmat.decrepit.ciphers.algorithms import Blowfish
98
class Blowfish_Analyzer(CryptoAnalyzer):
109
'''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.
1110
@@ -72,36 +71,25 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float:
7271
return score
7372

7473

75-
def __filtrer_dictionnaire_par_indice(self, chemin_dictionnaire: str) -> list[str]:
74+
def __filtrer_dictionnaire_par_indices(self, chemin_dictionnaire: str) -> list[str]:
7675
"""
7776
Filtre le dictionnaire en se basant sur les indices de la mission 3.
7877
L'indice pointe vers un format de clé "sha + nombre + chiffres simples".
79-
80-
Args:
81-
chemin_dictionnaire(str): Le chemin vers le fichier de dictionnaire.
82-
83-
Returns:
84-
list[str]: Une liste de mots de passe filtrés.
8578
"""
8679
mots_filtres: list[str] = []
87-
88-
# Indices pour le préfixe et le suffixe
89-
prefixes = ("sha256", "sha384", "sha512", "sha1")
80+
prefixes = ("sha256", "sha384", "sha512", "sha1")
9081
suffixes = ("123", "456", "789")
91-
82+
9283
try:
9384
with open(chemin_dictionnaire, "r", encoding="utf-8") as f:
9485
for ligne in f:
9586
mot = ligne.strip()
96-
97-
# Vérifie si le mot commence par un préfixe et se termine par un suffixe
9887
if mot.startswith(prefixes) and mot.endswith(suffixes):
9988
mots_filtres.append(mot)
100-
10189
except FileNotFoundError:
10290
print(f"Erreur : Le fichier de dictionnaire '{chemin_dictionnaire}' est introuvable.")
10391
return []
104-
92+
10593
return mots_filtres
10694

10795
def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]:
@@ -117,7 +105,7 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]:
117105
"""
118106
cles_candidates: list[bytes] = []
119107
# Utilisation de la méthode privée pour filtrer les mots
120-
mots_de_passe_cible = self.__filtrer_dictionnaire_par_indice(chemin_dictionnaire)
108+
mots_de_passe_cible = self.__filtrer_dictionnaire_par_indices(chemin_dictionnaire)
121109

122110
for mot in mots_de_passe_cible:
123111
mot_en_bytes = mot.encode("utf-8")
@@ -165,23 +153,22 @@ def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes:
165153
raise ValueError('Taille de clé invalide.')
166154

167155
try:
168-
169-
algorithm_blowfish = Blowfish(self.decode_base64(cle_donnee))
170-
texte_chiffre = ''
156+
# Use the key directly, not base64 decoded
157+
algorithm_blowfish = algorithms.Blowfish(cle_donnee)
171158

172159
#Récupération de l'IV et du texte chiffré dans le fichier
173160
with open(chemin_fichier_chiffre, 'rb') as f:
174161
donnees = f.read()
175-
f.close()
162+
176163
initialization_vector = donnees[:self.__BLOWFISH_TAILLE_IV]
177164
texte_chiffre = donnees[self.__BLOWFISH_TAILLE_IV:]
178165

179166
#Initialisation du cipher
180167
cipher = Cipher(algorithm_blowfish, modes.CBC(initialization_vector))
181168
decrypteur = cipher.decryptor()
182169

183-
#Suppresseur de padding
184-
supresseur_padding = PKCS7(self.__BLOWFISH_TAILLE_BLOC).unpadder()
170+
#Suppresseur de padding - PKCS7 uses bits, not bytes
171+
supresseur_padding = PKCS7(64).unpadder() # 64 bits = 8 bytes
185172

186173
#Décriptage des données avec le padding(remplissage aléatoire)
187174
donnees_chiffrees_avec_padding = decrypteur.update(texte_chiffre) + decrypteur.finalize()

0 commit comments

Comments
 (0)