-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaes_gcm_analyzer.py
More file actions
205 lines (165 loc) · 7.95 KB
/
aes_gcm_analyzer.py
File metadata and controls
205 lines (165 loc) · 7.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
from src.crypto_analyzer import CryptoAnalyzer
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from typing import List
import re
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: 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_indices(self, chemin_dictionnaire: str) -> List[str]:
"""
Filtre le dictionnaire en se basant sur les indices de la mission 4.
L'indice pointe vers le format de clé "Acronyme en majuscules + 4 chiffres".
"""
mots_filtres: List[str] = []
annee_courante: str = "2024" # Normalement 2025 mais on considère 2024 pour se conformer à la wordlist
motif_acronyme = re.compile(r"^[A-Z]{4}$")
try:
with open(chemin_dictionnaire, "r", encoding="utf-8") as f:
for ligne in f:
mot: str = ligne.strip()
if mot.endswith(annee_courante):
acronyme: str = mot[:-4]
if motif_acronyme.match(acronyme):
mots_filtres.append(mot)
except FileNotFoundError:
print(f"Erreur : Le fichier de dictionnaire '{chemin_dictionnaire}' est introuvable.")
return []
return mots_filtres
def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]:
'''
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: List[str] = self.__filtrer_dictionnaire_par_indices(chemin_dictionnaire)
clees_candidates: List[bytes] = []
for mot_de_passe in mots_de_passe_cible:
kdf = PBKDF2HMAC(
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)
clees_candidates.append(cle_derivee)
return clees_candidates
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
# Normalisation du score dans [0.0, 1.0]
if probabilite > 1.0:
probabilite = 1.0
if probabilite < 0.0:
probabilite = 0.0
return probabilite
except FileNotFoundError:
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:
# Validation taille de clé: AES-256 => 32 octets
if len(cle_donnee) != self._PBKDF2_LONGUEUR_CLE:
raise ValueError("Erreur : La clé AES-256 doit faire 32 bytes")
# Lecture du fichier: nonce (12B) + données + tag (16B)
with open(chemin_fichier_chiffre, "rb") as f:
donnees = f.read()
if len(donnees) < 12 + 16:
return b""
nonce = donnees[:12]
ciphertext_tag = donnees[12:]
if len(ciphertext_tag) < 16:
return b""
ciphertext = ciphertext_tag[:-16]
tag = ciphertext_tag[-16:]
# Déchiffrement AES-GCM
cipher = Cipher(algorithms.AES(cle_donnee), modes.GCM(nonce, tag))
decryptor = cipher.decryptor()
try:
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
return plaintext
except Exception:
# Tag invalide / clé incorrecte
return b""
except FileNotFoundError:
raise
except ValueError as e:
# Erreur de validation de clé
if "doit faire 32 bytes" in str(e):
raise
return b""
except Exception as e:
# Erreur générique
return b""