-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaes_cbc_analyzer.py
More file actions
167 lines (127 loc) · 6.85 KB
/
aes_cbc_analyzer.py
File metadata and controls
167 lines (127 loc) · 6.85 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
from src.crypto_analyzer import CryptoAnalyzer
from src.utils import calculer_entropie
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.padding import PKCS7
class Aes_Cbc_Analyzer(CryptoAnalyzer):
'''Détermine si l'algo aes_cbc 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:
- identifier_algo: Détermine si l'algo de chiffrement utilsé sur le fichier chiffré qui lui est passé en paramètre est l'aes_cbc.
- 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_CBC_SALT_2024" #Fourni
_PBKDF2_ITERATIONS = 10000 #Fourni
_PBKDF2_LONGUEUR_CLE = 32 #Longueur de la clé
def identifier_algo(self, chemin_fichier_chiffre: str) -> float:
'''
Détermine la probabilité que l'algo de chiffrement utilisé soit l'aes cbc en:
- recherchant l'IV en tête
- vérifiant si le reste du fichier en dehors de l'IV a une taille multiple de 16 octets
- déterminant si l'entropie est assez élevée dans le fichier chiffré (>7.5)
Args:
chemin_fichier_chiffre(str): Le chemin du fichier chiffré à traiter (mission1.enc).
Returns:
float: probabilité calculée.
'''
try:
with open(chemin_fichier_chiffre, "rb") as f:
contenu_fichier = f.read()
if len(contenu_fichier) < 16: #Heuristique IV probable en début de fichier (Vérifie si le fichier est assez grand pour contenir déjà l'IV)
return 0.0
initialization_vector = contenu_fichier[0:16] # type: ignore
donnees_chiffres = contenu_fichier[16:]
if len(donnees_chiffres) % 16 == 0: #Heuristique taille multipe de 16 bytes (Vérifie si les donnéese chiffrés sont en bloc de 16 octets, caractéristique de l'aes cbc)
probabilite = 0.5
else:
probabilite = 0.0
entropie = calculer_entropie(donnees_chiffres)
if entropie > 7.5: #Heuristique entropie élevée (L'entropie doit être supérieur à 7.5 pour confirmer le chiffrement robuste caractéristique des algos de chiffrement)
probabilite += 0.5
except FileNotFoundError:
return 0.0
return probabilite
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.
Args:
chemin_dictonnaire(str): chemin du dictionnaire
Returns:
list[str]: liste des mots retenus
'''
mots_de_passe_cible: list[str] = []
annees_olympiques: list[str] = ["1900", "1924", "2024"] #Annees où Paris a acceuili les JO
try:
with open(chemin_dictionnaire, "r") as f:
for ligne in f:
mot_propre:str = ligne.strip()
if mot_propre.startswith("paris") and mot_propre.endswith(tuple(annees_olympiques)): #"paris" car Paris = Ville Lumière = Capitale francaise comme l'indiquent les indices
mots_de_passe_cible.append(mot_propre)
return mots_de_passe_cible
except FileNotFoundError:
return []
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_indices(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
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 retire d'abord l'IV puis tente de décrypter le reste du fichier à l'aide de la clé en retirant le padding et retournes les données originales (idéalement non chiffrées).
Args:
chemin_fichier_chiffre(str): chemin du fichier chiffre à déchiffrer
cle_donnee(bytes): clé candidate pour le déchiffrement
Returns:
bytes: données déchiffrées
'''
try:
with open(chemin_fichier_chiffre, "rb") as f:
initialization_vector = f.read(16)
donnees_chiffrees = f.read()
# Validation de la taille de clé (AES-256 nécessite 32 bytes)
if len(cle_donnee) != 32:
raise ValueError("Erreur : La clé AES-256 doit faire 32 bytes")
try:
#Création de l'objet Cipher pour le déchiffrage
algorithm_aes = algorithms.AES256(cle_donnee)
mode_cbc = modes.CBC(initialization_vector)
cipher = Cipher(algorithm_aes, mode_cbc)
#Inistanciation du dechiffreur à partir du cipher
decrypteur = cipher.decryptor()
#Instanciation du supresseur de padding
supresseur_padding = PKCS7(algorithm_aes.block_size).unpadder()
donnees_chiffrees_avec_padding = decrypteur.update(donnees_chiffrees) + decrypteur.finalize()
donnees_originales = supresseur_padding.update(donnees_chiffrees_avec_padding) + supresseur_padding.finalize()
return donnees_originales
except ValueError as e:
# Erreur de déchiffrement (clé incorrecte, padding invalide)
# Ne pas retourner b"" si c'est une erreur de validation de taille
if "doit faire 32 bytes" in str(e):
raise
return b""
except Exception as e:
# Erreur critique inattendue
raise RuntimeError(f"Erreur critique lors du déchiffrement AES-CBC: {e}")
except FileNotFoundError:
raise