Skip to content

Commit 4472e76

Browse files
authored
Add/tests unitaires cha cha20 (#33)
* fix: Correction des imports non utilisés * fix: Correction des problèmes de typage * fix: Correction des imports dans Aes_Cbc_Analyzer * add: Tests unitaires pour ChaCha20_Analyzer
1 parent 96d533a commit 4472e76

File tree

3 files changed

+138
-116
lines changed

3 files changed

+138
-116
lines changed

src/analyzers/aes_cbc_analyzer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from crypto_analyzer import CryptoAnalyzer
2-
from utils import calculer_entropie
1+
from ..crypto_analyzer import CryptoAnalyzer
2+
from ..utils import calculer_entropie
33
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
44
from cryptography.hazmat.primitives import hashes
55
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

src/analyzers/chacha20_analyzer.py

Lines changed: 70 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Import des modules
22
import hashlib
3-
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
3+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
44
from rich import print
5-
import os, struct
6-
import math
5+
import os
76
import sys
7+
from typing import List
8+
89
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
910
from crypto_analyzer import CryptoAnalyzer
1011
from utils import calculer_entropie
@@ -26,12 +27,10 @@ class ChaCha20_Analyzer(CryptoAnalyzer):
2627
_CHACHA20_LONGUEUR_BLOC: la taille du bloc de chiffrement (64 bits)
2728
"""
2829

29-
_CHACHA20_LONGUEUR_CLE = 32
30-
_CHACHA20_LONGUEUR_NONCE = 12 #fourni
31-
_CHACHA20_LONGUEUR_TAG = 16
32-
_CHACHA20_LONGUEUR_BLOC = 64
33-
34-
30+
_CHACHA20_LONGUEUR_CLE: int = 32
31+
_CHACHA20_LONGUEUR_NONCE: int = 12
32+
_CHACHA20_LONGUEUR_TAG: int = 16
33+
_CHACHA20_LONGUEUR_BLOC: int = 64
3534

3635
def identifier_algo(self, chemin_fichier_chiffre: str) -> float:
3736
"""
@@ -41,142 +40,104 @@ def identifier_algo(self, chemin_fichier_chiffre: str) -> float:
4140
- vérifiant l'absence de padding (pas de contrainte de taille)
4241
- vérifiant que la taille du fichier est suffisante pour contenir un nonce
4342
44-
Retourne une probabilité entre 0 et 1(Pour connaitre la probabilité que l'algo de chiffrement utilisé soit l'ChaCha20).
43+
Retourne une probabilité entre 0 et 1 (Pour connaitre la probabilité que l'algo de chiffrement utilisé soit l'ChaCha20).
4544
4645
Args:
47-
chemin_fichier_chiffre(str): le chemin du fichier chiffré à traiter .
46+
chemin_fichier_chiffre(str): le chemin du fichier chiffré à traiter.
4847
4948
Returns:
50-
float: La probabilité que l'algo de chiffrement utilisé soit l'ChaCha20 apres le calcul.
49+
float: La probabilité que l'algo de chiffrement utilisé soit l'ChaCha20 après le calcul.
5150
"""
5251
try:
5352
with open(chemin_fichier_chiffre, 'rb') as f:
54-
donnees = f.read()
53+
donnees: bytes = f.read()
5554

5655
if len(donnees) < self._CHACHA20_LONGUEUR_NONCE:
57-
return 0.0 # Fichier trop petit pour contenir un nonce
56+
return 0.0
5857

59-
# Extraire le nonce présumé (12 premiers bytes)
60-
nonce = donnees[:self._CHACHA20_LONGUEUR_NONCE]
61-
donnees_chiffrees = donnees[self._CHACHA20_LONGUEUR_NONCE:]
58+
nonce: bytes = donnees[:self._CHACHA20_LONGUEUR_NONCE]
59+
donnees_chiffrees: bytes = donnees[self._CHACHA20_LONGUEUR_NONCE:]
6260

6361
if len(donnees_chiffrees) == 0:
64-
return 0.0 # Pas de données chiffrées
62+
return 0.0
6563

66-
# Critère 1: Vérifier la taille minimale
64+
taille_min: float = 0.0
6765
if len(donnees) >= self._CHACHA20_LONGUEUR_NONCE + 16:
6866
taille_min = 1.0
69-
else:
70-
taille_min = 0.0
7167

72-
# Critère 2: Vérifier l'entropie des données chiffrées (doit être très élevée)
73-
entropie = calculer_entropie(donnees_chiffrees)
74-
# L'entropie d'un chiffrement ChaCha20 devrait être proche de 8 bits/octet
75-
if entropie / 8.0 > 1.0:
76-
entropie_max = 1.0
77-
else:
78-
entropie_max = entropie / 8.0
68+
entropie: float = calculer_entropie(donnees_chiffrees)
69+
entropie_max: float = min(entropie / 8.0, 1.0)
7970

80-
# Critère 3: Vérifier l'absence de padding (pas de contrainte de taille)
81-
# ChaCha20 est un chiffrement de flux, donc pas de padding
82-
# On vérifie que la taille des données chiffrées n'est pas un multiple d'une taille de bloc commune
83-
taille_donnees = len(donnees_chiffrees)
71+
padding_max: float = 1.0
72+
taille_donnees: int = len(donnees_chiffrees)
8473
if taille_donnees % 16 == 0 or taille_donnees % 8 == 0:
8574
padding_max = 0.5
86-
else:
87-
padding_max = 1.0
8875

89-
# Critère 4: Vérifier l'entropie du nonce (doit être élevée aussi)
90-
entropie_nonce = calculer_entropie(nonce)
91-
if entropie_nonce / 8.0 > 1.0:
92-
nonce_max = 1.0
93-
else:
94-
nonce_max = entropie_nonce / 8.0
76+
entropie_nonce: float = calculer_entropie(nonce)
77+
nonce_max: float = min(entropie_nonce / 8.0, 1.0)
9578

96-
# Calcul de la probabilité finale (moyenne pondérée des scores)
97-
probabilite = (taille_min * 0.1 +
79+
probabilite: float = (taille_min * 0.1 +
9880
entropie_max * 0.4 +
9981
padding_max * 0.3 +
10082
nonce_max * 0.2)
10183

102-
return min(probabilite, 1.0)
84+
return probabilite
10385

10486
except Exception as e:
10587
print(f"Erreur lors de l'identification de l'algorithme: {e}")
10688
return 0.0
10789

90+
def filtrer_dictionnaire_par_indices(self, chemin_fichier_chiffre: str) -> List[bytes]:
91+
# En supposant qu'elle retourne une liste de bytes pour les clés.
92+
return []
10893

109-
def filtrer_dictionnaire_par_indices(self, chemin_fichier_chiffre):
110-
pass
111-
112-
def generer_cles_candidates(self, chemin_fichier_chiffre):
113-
114-
'''
115-
Cette fonction se charge de générer les clés candidates pour le déchiffremment du fichier chiffré en utilisant
116-
la dérivation sha256 pour renforcer les clées de chiffrement.
94+
def generer_cles_candidates(self, chemin_dictionnaire: str) -> List[bytes]:
95+
"""
96+
Cette fonction se charge de générer les clés candidates pour le déchifremment du fichier chiffré en utilisant
97+
la dérivation sha256 pour renforcer les clées de chiffrement.
11798
118-
119-
Args:
120-
chemin_fichier_chiffre(str) : Le chemin vers le fichier chiffré
121-
122-
Returns:
123-
cles_candidates (list[bytes]) : Un tableau de clés, chaque clé étant une séquence d'octets
124-
'''
99+
Args:
100+
chemin_dictionnaire(str) : Le chemin vers le dictionnaire.
125101
126-
donnees_fichier_filtre = self.filtrer_dictionnaire_par_indices(chemin_fichier_chiffre)
127-
128-
cle_candidates: list[bytes] = []
102+
Returns:
103+
cles_candidates (List[bytes]) : Un tableau de clés, chaque clé étant une séquence d'octets.
104+
"""
105+
donnees_fichier_filtre: List[bytes] = self.filtrer_dictionnaire_par_indices(chemin_dictionnaire)
106+
cles_candidates: List[bytes] = []
129107
for cle in donnees_fichier_filtre:
130-
cle_candidates.append(hashlib.sha256(cle).digest())
131-
132-
return cle_candidates
108+
cles_candidates.append(hashlib.sha256(cle).digest())
109+
return cles_candidates
133110

134111
def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes:
135112
if len(cle_donnee) != 32:
136113
raise ValueError("Erreur : La clé n'a pas la taille correcte")
137-
else:
138-
try:
139-
# Utiliser le chemin complet si c'est un chemin absolu, sinon ajouter le préfixe data/
140-
if os.path.isabs(chemin_fichier_chiffre):
141-
fichier_path = chemin_fichier_chiffre
142-
else:
143-
fichier_path = f"data/{chemin_fichier_chiffre}"
144-
145-
with open(fichier_path, 'rb') as f:
146-
nonce = f.read(self._CHACHA20_LONGUEUR_NONCE)
147-
texte_chiffre = f.read()
148-
149-
algorithm_chacha20 = algorithms.ChaCha20(cle_donnee, nonce)
150-
cipher = Cipher(algorithm_chacha20, mode=None)
151-
decrypteur = cipher.decryptor()
152-
resultat = decrypteur.update(texte_chiffre)
153-
154-
# Retourner les bytes bruts comme attendu par l'interface
155-
return resultat
156-
157-
except Exception as e:
158-
print(f"Une erreur est survenue : {e}")
159-
return b""
160-
cle_candidates.append(hashlib.sha256(cle).encode(encoding="utf-8"))
161-
162-
return cle_candidates
163-
164-
def dechiffrer(self,chemin_fichier_chiffer : str ,clef :bytes)->str:
165-
if len(clef) != 32 : return ValueError("Erreur : La clé a pas la taille correcte ")
166-
else:
167-
try:
168-
with open(f"data/{chemin_fichier_chiffer}",'rb') as f:
169-
nonce = f.read(16)
170-
texte_chiffrer = f.read()
171-
172-
counter=0
173-
algorithm_chacha20 = algorithms.ChaCha20(clef,nonce)
174-
cipher = Cipher(algorithm_chacha20,mode=None)
175-
decrypteur = cipher.decryptor()
176-
return decrypteur.update(texte_chiffrer)
177-
except Exception as e:
178-
print(f"Une erreur est survenu : {e}")
179-
180-
114+
115+
try:
116+
fichier_path: str = chemin_fichier_chiffre
117+
if not os.path.isabs(chemin_fichier_chiffre):
118+
fichier_path = f"data/{chemin_fichier_chiffre}"
119+
120+
with open(fichier_path, 'rb') as f:
121+
nonce: bytes = f.read(self._CHACHA20_LONGUEUR_NONCE)
122+
texte_chiffre: bytes = f.read()
123+
124+
algorithm_chacha20 = algorithms.ChaCha20(cle_donnee, nonce)
125+
cipher = Cipher(algorithm_chacha20, mode=None)
126+
decrypteur = cipher.decryptor()
127+
resultat: bytes = decrypteur.update(texte_chiffre)
128+
129+
return resultat
181130

182-
print(ChaCha20_Analyzer().dechiffrer("mission2.enc",os.urandom(32)))
131+
except Exception as e:
132+
print(f"Une erreur est survenue : {e}")
133+
return b""
134+
135+
# L'appel direct a été déplacé dans un bloc if __name__ == "__main__" pour de bonnes pratiques (Mouwafic)
136+
if __name__ == "__main__":
137+
try:
138+
resultat_dechiffrement: bytes = ChaCha20_Analyzer().dechiffrer("mission2.enc", os.urandom(32))
139+
print(f"Résultat du déchiffrement : {resultat_dechiffrement.decode('utf-8')}")
140+
except ValueError as ve:
141+
print(ve)
142+
except FileNotFoundError:
143+
print("Erreur: Le fichier 'mission2.enc' est introuvable.")

tests/test_analyzers.py

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
from unittest import TestCase, main
2+
import os
23
import sys
3-
sys.path.append('.')
4-
sys.path.append('..')
4+
import hashlib
5+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
6+
7+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
8+
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
9+
510
from src.analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer
11+
from src.analyzers.chacha20_analyzer import ChaCha20_Analyzer
612

7-
class AnalyzersTester(TestCase):
813

14+
class AesCbcAnalyzerTester(TestCase):
915
"""
10-
Cette classe est principalement destinée à recueillir toutes les fonctions de test des analyseurs d'algorithme
11-
de chiffrement.
16+
Cette classe est principalement destinée à recueillir toutes les fonctions de test des analyseurs d'algorithme
17+
de chiffrement.
1218
"""
1319

1420
def setUp(self):
@@ -40,6 +46,61 @@ def test_exception_dechiffrer(self):
4046

4147
with self.assertRaises(FileNotFoundError):
4248
self.analyser.dechiffrer("no_file_dohi.txt", premiere_cle)
49+
50+
class ChaCha20AnalyzerTester(TestCase):
51+
52+
def setUp(self):
53+
# Chemins pour ChaCha20_Analyzer
54+
self.wordlist = "keys/wordlist.txt"
55+
self.analyser_chacha = ChaCha20_Analyzer()
56+
57+
# Données de test pour ChaCha20
58+
self.cle_test_chacha = hashlib.sha256(b"cle_test").digest()
59+
self.nonce_test_chacha = b"\x00" * 12
60+
self.texte_clair_test_chacha = b"Bonjour le monde, ceci est un test de chiffrement ChaCha20"
61+
self.chemin_fichier_chacha_valide = "tests/fichiers_pour_tests/chacha20_valide.enc"
62+
self.chemin_fichier_chacha_invalide = "tests/fichiers_pour_tests/chacha20_invalide.enc"
4363

64+
# Générer un fichier chiffré valide pour les tests de ChaCha20
65+
cipher_chacha = Cipher(algorithms.ChaCha20(self.cle_test_chacha, self.nonce_test_chacha), mode=None)
66+
encryptor = cipher_chacha.encryptor()
67+
texte_chiffre_test = encryptor.update(self.texte_clair_test_chacha)
68+
with open(self.chemin_fichier_chacha_valide, "wb") as f:
69+
f.write(self.nonce_test_chacha)
70+
f.write(texte_chiffre_test)
71+
72+
# Ajout des tests pour ChaCha20_Analyzer
73+
def test_chacha20_identifier_algo(self):
74+
self.assertAlmostEqual(self.analyser_chacha.identifier_algo(self.chemin_fichier_chacha_valide), 1.0, 1)
75+
self.assertAlmostEqual(self.analyser_chacha.identifier_algo(self.chemin_fichier_chacha_invalide), 0.0, 1)
76+
77+
def test_chacha20_generer_cles_candidates(self):
78+
# Comme la fonction filtrer_dictionnaire_par_indices retourne toujours une liste vide,
79+
# generer_cles_candidates doit également retourner une liste vide.
80+
self.assertEqual(self.analyser_chacha.generer_cles_candidates(self.wordlist), [])
81+
82+
def test_chacha20_dechiffrer(self):
83+
# Test de déchiffrement avec une clé et un nonce valides
84+
resultat_dechiffrement = self.analyser_chacha.dechiffrer(self.chemin_fichier_chacha_valide, self.cle_test_chacha)
85+
self.assertEqual(resultat_dechiffrement, self.texte_clair_test_chacha)
86+
87+
# Test de déchiffrement avec une clé incorrecte
88+
cle_incorrecte = hashlib.sha256(b"mauvaise_cle").digest()
89+
resultat_incorrect = self.analyser_chacha.dechiffrer(self.chemin_fichier_chacha_valide, cle_incorrecte)
90+
self.assertNotEqual(resultat_incorrect, self.texte_clair_test_chacha)
91+
92+
def test_chacha20_dechiffrer_mauvaise_cle(self):
93+
# Test de l'exception pour une clé de taille incorrecte
94+
cle_mauvaise_taille = b"a" * 16 # La bonne taille est 32
95+
with self.assertRaises(ValueError):
96+
self.analyser_chacha.dechiffrer(self.chemin_fichier_chacha_valide, cle_mauvaise_taille)
97+
98+
def test_chacha20_dechiffrer_fichier_non_existant(self):
99+
# Test de l'exception si le fichier n'existe pas
100+
cle_valide = self.cle_test_chacha
101+
with self.assertRaises(FileNotFoundError):
102+
self.analyser_chacha.dechiffrer("chemin_invalide.enc", cle_valide)
103+
104+
44105
if __name__ == '__main__':
45106
main()

0 commit comments

Comments
 (0)