Skip to content

Commit 5c9e9bb

Browse files
committed
Merge branch 'main' of https://github.com/mouwaficbdr/CryptoForensic-Python into feature/DetecteurCryptoOrchestrateur
2 parents b74715c + 0c76cf2 commit 5c9e9bb

9 files changed

Lines changed: 1335 additions & 1251 deletions

File tree

dicoEn/i.txt

Lines changed: 0 additions & 1148 deletions
Large diffs are not rendered by default.

dicoEn/y.txt

Lines changed: 1148 additions & 0 deletions
Large diffs are not rendered by default.

rapport_mission.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
RAPPORT DE SYNTHESE DU 05/08/25 � 00:01:06~ Mission 2: CHACHA20 ~ I - Statistiques relatives � l'analyse du fichier~Fichier crypt� par cet algorithme: mission1.enc~Cl� de d�chiffrement identifi�e: PK7 ~Nombre de tentatives: 127 ~Temps d'ex�cution: 368s ~ II - R�sultats obtenusTaux r�ussite du d�chiffrement: 97%(Succ�s)~Texte d�chiffr�: Je suis l�! ~

src/analyzers/aes_cbc_analyzer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def generer_cles_candidates(self, chemin_dictionnaire: str) -> list[bytes]:
9999
chemin_dictionnaire(str): le chemin du dictionnaire de mots de passes pour l'attaque par dictionnaire.
100100
101101
Returns:
102-
list[bytes]: liste des clés candidates.
102+
list[bytes]: liste des clés candidates.
103103
'''
104104

105105
mots_de_passe_cible = self.filtrer_dictionnaire_par_indices(chemin_dictionnaire)
@@ -155,4 +155,4 @@ def dechiffrer(self, chemin_fichier_chiffre: str, cle_donnee: bytes) -> bytes:
155155
return b""
156156

157157
except FileNotFoundError:
158-
return b""
158+
raise

src/rapport_mission.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import date, datetime
22
import os
3-
3+
from pathlib import Path
44
class generer_rapport_mission():
55

66
def __init__(self):
@@ -10,27 +10,19 @@ def generer_rapport_synthese(self, resultats_de_mission:dict)->None:
1010
"""
1111
Retourne le rapport de mission effectué.
1212
Args:
13-
resultats_de_mission(dict): les résultats de l'opération de déchiffrement du fichier
13+
resultats_de_mission(dict{algorithme, fichier, cle, tentatives, temps_execution, taux_succes, texte_dechiffre}): les résultats de l'opération de déchiffrement du fichier
1414
Returns:
1515
str: le rapport
1616
"""
1717

1818
equivalence=['AES-CBC-256', 'CHACHA20', 'BLOWFISH', 'AES-GCM', 'FERNET']
1919

2020
try :
21-
rapport= f"RAPPORT DE SYNTHESE DU {date.today().strftime("%d/%m/%y")} à {str(datetime.now().time()).split('.')[0]}\n "
22-
+ f"Mission {equivalence.index(resultats_de_mission['algorithme'].toupper()) + 1}: {resultats_de_mission['algorithme'].toupper()} \n"
23-
+ "I - Statistiques relatives à l'analyse du fichier\n"
24-
+ f"Fichier crypté par cet algorithme: {resultats_de_mission['fichier']}\n"
25-
+ f"Clé de déchiffrement identifiée: {resultats_de_mission['cle']} \n"
26-
+ f"Nombre de tentatives: {resultats_de_mission['tentatives']} \n"
27-
+ f"Temps d'exécution: {resultats_de_mission["temps_execution"]} \n"
28-
+ "II - Résultats obtenus"
29-
+ f"Taux réussite du déchiffrement: {resultats_de_mission['taux_succes']}({resultats_de_mission['statut_succes']})\n"
30-
+ f"Texte déchiffré: {resultats_de_mission['texte_dechiffre']} \n"
21+
rapport= f"RAPPORT DE SYNTHESE DU {date.today().strftime("%d/%m/%y")} à {str(datetime.now().time()).split('.')[0]}\n " f"Mission {equivalence.index(resultats_de_mission['algorithme'].upper()) + 1}: {resultats_de_mission['algorithme'].upper()} \n I - Statistiques relatives à l'analyse du fichier\n" f"Fichier crypté par cet algorithme: {resultats_de_mission['fichier']}\n" f"Clé de déchiffrement identifiée: {resultats_de_mission['cle']} \n" f"Nombre de tentatives: {resultats_de_mission['tentatives']} \n" f"Temps d'exécution: {resultats_de_mission["temps_execution"]} \n II - Résultats obtenus" f"Taux réussite du déchiffrement: {resultats_de_mission['taux_succes']}({resultats_de_mission['statut_succes']})\n" f"Texte déchiffré: {resultats_de_mission['texte_dechiffre']} \n"
3122

3223
# Ecriture du rapport dans le fichier rapport.txt pour les affichage ultérieurs
33-
with open(f"{os.path.abspath(os.curdir)}\\CryptoForensic-Python\\rapport_mission.txt", 'a') as f:
24+
chemin = Path(f"rapport_mission.txt")
25+
with open(chemin, 'a') as f:
3426
f.write(rapport.replace('\n', '~'))
3527
f.close()
3628

@@ -54,13 +46,25 @@ def recuperer_ancien_rapport(self, base_date:str)->list|str:
5446
"""
5547
rapport=[]
5648
try:
57-
with open(f"{os.path.abspath(os.curdir)}\\rapport.txt", 'r') as f:
58-
line=f.readline()
59-
while line != "" :
49+
chemin = Path(f"rapport_mission.txt")
50+
with open(chemin, 'r') as f:
51+
for line in f:
6052
if line.find(base_date) != -1:
61-
rapport.append(line)
53+
rapport.append(line.replace('~', '\n'))
6254
f.close()
63-
return rapport if rapport.count() > 0 else 'Aucun rapport trouvé à cette date.'
55+
return rapport if rapport else 'Aucun rapport trouvé à cette date.'
6456
except FileNotFoundError:
6557
print('Fichier non trouvé')
6658

59+
print(generer_rapport_mission().generer_rapport_synthese({
60+
'algorithme':'CHACHA20',
61+
'fichier': 'mission1.enc',
62+
'cle':'PK7',
63+
'tentatives':'127',
64+
'temps_execution':"368s",
65+
'taux_succes': "97%",
66+
'statut_succes':'Succès',
67+
'texte_dechiffre':'Je suis là!'
68+
}))
69+
70+
print(generer_rapport_mission().recuperer_ancien_rapport("05/08/25")[0])

src/utils.py

Lines changed: 73 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
11
import math
22
import string
3-
import sys
4-
import os
3+
from pathlib import Path
4+
from typing import Any, Dict, List, TypedDict
5+
6+
class StatsDict(TypedDict):
7+
imprimable: float
8+
nombre_mots: int
9+
p_mots_valide: float
10+
non_mots: List[str]
11+
ponctuation_valide: int
512

613
def calculer_entropie(bytes: bytes) -> float:
14+
'''
15+
Calcul l'entropie (le désordre dans une suite de données) afin de déterminer le degré d'improbabilité d'une chaine de données.
16+
17+
Args:
18+
bytes(bytes): La donnée brute contenue dans le fichier crypté.
19+
20+
Returns:
21+
float: l'entropie calculée.
22+
'''
723
entropie = 0
824
proba_byte = 0
925
for specifique_byte in bytes:
1026
i = 1
1127
for chaque_byte in bytes:
12-
if(chaque_byte == specifique_byte):
13-
i += 1
28+
if(chaque_byte == specifique_byte):
29+
i += 1
30+
1431
proba_byte = 1 / i
1532
entropie += (proba_byte) * math.log(1/proba_byte, 8)
1633
return entropie
1734

1835

36+
1937
def est_dechiffre(texte:str) -> bool:
2038
"""
2139
Détermine si oui ou non une chaine a été déchiffrée
@@ -25,7 +43,7 @@ def est_dechiffre(texte:str) -> bool:
2543
Returns:
2644
bool: déchiffrée ou non
2745
"""
28-
stats:dict=verifier_texte_dechiffre(texte)
46+
stats:dict[str, Any] = verifier_texte_dechiffre(texte)
2947
pourcent=0
3048

3149
# Les caractères imprimables constituent 50% de la validation du déchiffrement
@@ -41,11 +59,10 @@ def est_dechiffre(texte:str) -> bool:
4159
pourcent += 20
4260

4361
return True if pourcent > 70 else False
44-
45-
46-
4762

48-
def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]:
63+
64+
65+
def verifier_texte_dechiffre(texte: str) -> Dict[str, Any]:
4966
"""
5067
Verifie que le dechiffrement d'un message a bien été effectué sur la base de certains critères.
5168
@@ -63,86 +80,83 @@ def verifier_texte_dechiffre(texte: str) -> dict[int, int, int, list, int]:
6380

6481
#Statistiques sur le texte
6582

66-
stats: dict = {
83+
stats: dict[str, Any] = {
6784
'imprimable':0,
6885
'nombre_mots':0,
6986
'p_mots_valide':0,
7087
'non_mots':[],
7188
'ponctuation_valide':0
7289
}
7390

91+
if not texte:
92+
return stats
93+
7494
#Verifier le pourcentage de caractères imprimables.
75-
76-
for lettre in texte:
77-
if lettre.isprintable():
78-
stats['imprimable']+= 100/len(texte)
79-
95+
stats['imprimable'] = int(sum(1 for char in texte if char.isprintable()) / len(texte) * 100)
96+
8097
# Traitement du texte brut pour obtenir une séquence distincte de pseudo-mot à cette étape séparé par des espaces
8198

82-
tab='./:!\\}{_%*$£&#;,~"()[]=§|`^@'
99+
tab='./:!\\}{_%*$£&#;,~"()[]=§|`^@?'
83100
copy=texte
84101
for lettre in tab:
85102
copy=copy.replace(lettre, ' ')
86-
copy=copy.strip().split(' ')
87-
stats['nombre_mots']=len(copy)
103+
mots = [mot for mot in copy.strip().split(' ') if mot]
104+
stats['nombre_mots']=len(mots)
88105

89106
# Verifier que le chaque mot du texte est un mot anglais/francais
90107

91108
try:
92-
for mot in copy:
109+
mots_valides = 0
110+
for mot in mots:
93111
trouve=False
94-
if mot == '': continue
112+
if not mot: continue
113+
114+
first_char = mot[0].lower()
115+
95116
for syl in ['Fr', 'En']:
96-
chemin=f"{os.curdir}\\CryptoForensic-Python\\dico{syl}\\{mot[0]}.txt"
97-
98-
with open(chemin, 'r') as f:
99-
ligne=f.readline()
100-
ligne=ligne.removesuffix('\n')
101-
102-
while not trouve and ligne != "":
103-
104-
if ligne == mot:
105-
stats['p_mots_valide']+=100/len(copy)
106-
print(stats['p_mots_valide'], mot)
107-
trouve=True
108-
break
109-
110-
ligne=f.readline()
111-
ligne=ligne.removesuffix('\n')
112-
113-
f.close()
117+
118+
chemin = Path(f"dico{syl}")/f"{first_char}.txt"
119+
try:
120+
with open(chemin, 'r', encoding='latin-1') as f:
121+
for ligne in f:
122+
if ligne.strip() == mot:
123+
mots_valides += 1
124+
trouve=True
125+
break
126+
except FileNotFoundError:
127+
continue
114128

115129
if trouve : break
116130

117131
if not trouve :
118132
stats['non_mots'].append(mot)
133+
if mots:
134+
stats['p_mots_valide'] = round((mots_valides / len(mots)) * 100, 2)
135+
else:
136+
stats['p_mots_valide'] = 0.0
119137

120138
except Exception:
121-
tb=sys.exception().__traceback__
122-
raise Exception().with_traceback(tb)
139+
raise
123140

124141

125142
#Verifier la structure de ponctuation.
126143

127144
points='.?!;,'
128-
nbr_ponct=0
129-
for point in points :
130-
nbr_ponct+=texte.count(point)
131-
for point in points :
132-
partition= texte.partition(point)
133-
if partition[2].startswith(' ') :
134-
if (point in '?!.' and partition[2].lstrip()[0].isupper()) or (point in ';,' and partition[2].lstrip()[0].islower()):
135-
stats['ponctuation_valide']+=100/nbr_ponct
136-
137-
for key in stats:
138-
print(key)
139-
if isinstance(stats[key], float):
140-
stats[key]=round(stats[key], 2)
145+
count = 0
146+
nbr_points = 0
147+
for i, char in enumerate(texte):
148+
if char in points:
149+
nbr_points += 1
150+
if (i == len(texte) - 1) or (texte[i+1] == ' '):
151+
count += 1
152+
153+
if not nbr_points: nbr_points=1
154+
stats['ponctuation_valide'] = round(count*100/nbr_points, 2)
141155

142156
return stats
143157

144158

145-
def rangerDico():
159+
def rangerDico() -> None:
146160
"""
147161
Fonction utilitaire de rangement du dictionnaire anglais téléchargé
148162
Pour effectuer des tests
@@ -151,11 +165,13 @@ def rangerDico():
151165
compte = 0
152166
# Ouverture du grand dictionnaire.
153167
try :
154-
with open(f"{os.path.abspath(os.curdir)}\\words_alpha.txt",'r') as f:
168+
# Utilisation de Path pour un chemin portable
169+
words_path = Path.cwd() / "words_alpha.txt"
170+
with open(words_path,'r') as f:
155171
while i<26:
156172
# Définition du chemin vers le fichier de chaque mot en fonction de l'alphabet.
157-
chemin=f"{os.curdir}\\dicoEn\\{string.ascii_lowercase[i]}.txt"
158-
with open(chemin, 'a') as fichier:
173+
dico_path = Path.cwd() / "dicoEn" / f"{string.ascii_lowercase[i]}.txt"
174+
with open(dico_path, 'a') as fichier:
159175
#Ecriture dans le fichier.
160176
fichier.write(string.ascii_lowercase[i]+'\n')
161177
while 1 :
@@ -172,5 +188,3 @@ def rangerDico():
172188
except FileNotFoundError:
173189
print('Fichier non trouvé.')
174190
# rangerDico()
175-
176-
print(verifier_texte_dechiffre('neither#nor avec, ded_caractère a'))

tests/fichiers_pour_tests/mission1_invalide.enc

Whitespace-only changes.

tests/test_analyzers.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from unittest import TestCase, main
2+
import sys
3+
sys.path.append('.')
4+
sys.path.append('..')
5+
from src.analyzers.aes_cbc_analyzer import Aes_Cbc_Analyzer
6+
7+
class AnalyzersTester(TestCase):
8+
9+
"""
10+
Cette classe est principalement destinée à recueillir toutes les fonctions de test des analyseurs d'algorithme
11+
de chiffrement.
12+
"""
13+
14+
def setUp(self):
15+
self.chemin_fichier_chiffre = "data/mission1.enc"
16+
self.chemin_fichier_chiffre_invalide = "tests/fichiers_pour_tests/mission1_invalide.enc"
17+
self.wordlist = "keys/wordlist.txt"
18+
self.analyser = Aes_Cbc_Analyzer()
19+
20+
21+
def test_aes_cbc_identifier_algo(self):
22+
self.assertAlmostEqual(self.analyser.identifier_algo(self.chemin_fichier_chiffre), 1)
23+
self.assertAlmostEqual(self.analyser.identifier_algo(self.chemin_fichier_chiffre_invalide), 0)
24+
25+
def test_aes_cbc_filtrage_dict(self):
26+
self.assertIsInstance(self.analyser.filtrer_dictionnaire_par_indices(self.wordlist), list)
27+
self.assertEqual(self.analyser.filtrer_dictionnaire_par_indices(self.wordlist), ["paris2024"])
28+
self.assertEqual(self.analyser.filtrer_dictionnaire_par_indices("chemin_dohi.txt"), [])
29+
30+
def test_generation_cles_candidate(self):
31+
self.assertIsInstance(self.analyser.generer_cles_candidates(self.wordlist), list)
32+
33+
def test_exception_dechiffrer(self):
34+
cles_candidates = self.analyser.generer_cles_candidates(self.wordlist)
35+
36+
if not cles_candidates:
37+
self.fail("La liste des clés candidates ne devrait pas être vide.")
38+
39+
premiere_cle = cles_candidates[0]
40+
41+
with self.assertRaises(FileNotFoundError):
42+
self.analyser.dechiffrer("no_file_dohi.txt", premiere_cle)
43+
44+
if __name__ == '__main__':
45+
main()

0 commit comments

Comments
 (0)