Skip to content

Commit bc2dd44

Browse files
committed
first version of article on email decryption
1 parent a7737b7 commit bc2dd44

File tree

4 files changed

+332
-0
lines changed

4 files changed

+332
-0
lines changed

_data/authors.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,16 @@ christophesejourne:
193193
socials:
194194
linkedin: "christophesejourne"
195195

196+
197+
quentinretourne:
198+
name: "Quentin Retourné"
199+
bio: "Quentin Retourné a rejoint SCIAM en mars 2018 avec une envie : utiliser la Data Science pour résoudre des problèmes complexes le plus simplement possible. Il a suivi des études de Data Science et Génie Industriel et à CentraleSupélec et Columbia University à New York.Quentin a grandi à l'étranger pendant 11 ans, ce qui l'a baigné dans la diversité et lui a donné une grande faculté d'adaptation. Féru de sport, il a pratiqué la natation en compétition, et s'adonne maintenant à l'escalade et au volleyball. C'est aussi un grand fan d'énigmes !"
200+
job: "Consultant Sénior Data/IA"
201+
pagesciam: "https://www.sciam.fr/equipe/quentin-retourne"
202+
picture: quentinretourne.jpg
203+
socials:
204+
linkedin: "quentinretourne"
205+
196206
pierrelepagnol:
197207
name: "Pierre Lepagnol"
198208
bio: "PhD Student in CS @LISN/Paris-Saclay University & Data Scientist"
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
= Déchiffrement d'Emails avec Python
2+
:showtitle:
3+
:page-navtitle: Déchiffrement d'Emails avec Python
4+
:page-excerpt: Découvrez comment déchiffrer des emails chiffrés avec Python en utilisant `openssl`, `asn1crypto` et `cryptography`.
5+
:layout: post
6+
:author: quentinretourne
7+
:page-tags: [Tutoriel, Python, Cryptographie]
8+
:page-vignette: email_decryption.png
9+
:page-categories: software
10+
11+
Le déchiffrement d'emails chiffrés est une tâche essentielle pour garantir la confidentialité des
12+
communications. Dans cet article, nous présentons différentes solutions pour déchiffrer des emails
13+
chiffrés en utilisant Python, en particulier avec l'exécutable `openssl` et les librairies
14+
`asn1crypto` et `cryptography`. Nous terminons en présentant une nouvelle méthode de déchiffrement
15+
de mails chiffrés, développée par nos soins, qui vient d'être intégrée à `cryptography` en Novembre
16+
2024.
17+
18+
== Quelques prérequis
19+
20+
Avant de plonger dans le déchiffrement des emails, il est important de comprendre quelques concepts
21+
de base en cryptographie.
22+
23+
=== Cryptographie et chiffrement
24+
25+
La cryptographie est l'art de sécuriser les communications en les transformant de manière à ce
26+
qu'elles ne puissent être lues que par les destinataires prévus. Un message chiffré n'aura pas de
27+
sens pour un observateur externe. En cryptographie, il s'agit donc de chiffrer et de déchiffrer des
28+
messages. Pour ce faire, plusieurs algorithmes de chiffrement sont utilisés; nous allons parcourir
29+
ceux qui nous seront utiles dans cet article.
30+
31+
Le https://fr.wikipedia.org/wiki/Chiffrement_RSA[chiffrement RSA] est un algorithme de cryptographie
32+
asymétrique qui utilise une paire de clés : une clé publique pour le chiffrement et une clé privée
33+
pour le déchiffrement. RSA est largement utilisé pour sécuriser les communications sur Internet,
34+
notamment pour les certificats SSL/TLS et les connexions SSH.
35+
36+
Par opposition au chiffrement asymétrique (dont RSA fait partie), on peut aussi chiffrer des
37+
messages de manière symétrique. Dans ce cas, on utilise la même clé pour chiffrer et déchiffrer les
38+
messages. Cela est utile pour d'autres cas d'usages : le chiffrement symétrique est généralement
39+
plus rapide que le chiffrement asymétrique (même si cela dépend des algorithmes utilisés).
40+
41+
Ces deux types de chiffrement sont utilisés dans le format PKCS #7 pour chiffrer des emails, que
42+
nous allons étudier ci-dessous.
43+
44+
=== Le format PKCS #7
45+
46+
Les spécifications https://en.wikipedia.org/wiki/PKCS_7[PKCS #7] (Public Key Cryptography Standards
47+
#7) définissent un format standard pour sécuriser des messages, et notamment des emails. PKCS #7 est
48+
un format très vaste, mais il permet notamment de signer / vérifier des messages, ainsi que les
49+
chiffrer / déchiffrer. Dans cet article, nous allons nous appesantir sur le spécifications de "chiffrement /
50+
déchiffrement" de PKCS #7 par opposition à la partie "signature". On la désigne aussi par
51+
"encapsulation" ou "enveloped data" en anglais.
52+
53+
Dans le cas de chiffrement d'un message PKCS #7, deux chiffrements ont lieu:
54+
1. Le chiffrement du contenu du message, selon un algorithme de chiffrement symétrique nécessitant
55+
une clé. Cette clé est générée de manière aléatoire.
56+
2. Le(s) chiffrement(s) de la clé ci-dessus, cette fois-ci de façon asymétrique, pour chaque destinataire
57+
du message. Cela est fait avec la/les clé(s) RSA publique(s) du ou des destinataires, qui doivent
58+
être mises à disposition de l'expéditeur du message.
59+
60+
Ainsi, lors du déchiffrement, chaque destinataire va :
61+
- Inspecter chaque clé chiffrée dans le message sous format PKCS #7, et trouver celle qui
62+
correspond à leur clé RSA publique.
63+
- Déchiffrer la clé symétrique avec leur clé RSA privée.
64+
- Déchiffrer le contenu du message avec la clé symétrique fraîchement déchiffrée
65+
66+
L'avantage de cette méthode permet d'économiser du temps de calcul et de réduire la taille du
67+
message à transporter. En effet, le contenu (qui n'a pas de limite de taille) est chiffré une seule
68+
fois avec un algorithme symétrique, beaucoup plus rapide que ses homologues asymétriques. De plus,
69+
c'est uniquement la clé symétrique (entre 128 et 256 bits selon les algorithmes) qui est chiffrée
70+
plusieurs fois pour chaque destinataire par un algorithme asymétrique et stockée dans la structure
71+
du message.
72+
73+
Maintenant qu'on a vu les bases, passons à la pratique.
74+
75+
== Analyse d'un message chiffré
76+
77+
La structure https://fr.wikipedia.org/wiki/Abstract_Syntax_Notation_One[ASN.1] (Abstract Syntax
78+
Notation One) est un standard de notation utilisé pour représenter des données, couramment utilisé
79+
dans les protocoles de communication et les certificats numériques.
80+
81+
Nous allons étudier un message chiffré, stocké dans le fichier `enveloped.der`. Les messages PKCS#7
82+
sont toujours encodés dans une structure ASN.1. Ici, elle est sous format DER, un format binaire
83+
classique pour ce type de structure.
84+
85+
=== Avec OpenSSL
86+
87+
OpenSSL est une bibliothèque logicielle open-source qui fournit des implémentations d'énormément de
88+
protocoles de sécurité. Elle est fournie notamment sous forme d'un exécutable en ligne de commande
89+
qui permet de manipuler des certificats, des clés et des messages chiffrés. Cet exécutable est
90+
généralement installé par défaut dans les environnements Conda pour Python, ce qui est très
91+
pratique. Ainsi, on peut l'utiliser tel quel via la bibliothèque built-in `subprocess` :
92+
93+
[source, python]
94+
----
95+
import subprocess
96+
97+
instructions = [
98+
"openssl",
99+
"asn1parse",
100+
"-in",
101+
"vectors/enveloped.der",
102+
"-inform",
103+
"der",
104+
]
105+
output = subprocess.run(instructions, check=True, capture_output=True)
106+
print(output.stdout.decode())
107+
----
108+
109+
Résultat :
110+
[source, cmd]
111+
----
112+
0:d=0 hl=4 l= 667 cons: SEQUENCE
113+
4:d=1 hl=2 l= 9 prim: OBJECT :pkcs7-envelopedData
114+
15:d=1 hl=4 l= 652 cons: cont [ 0 ]
115+
19:d=2 hl=4 l= 648 cons: SEQUENCE
116+
23:d=3 hl=2 l= 1 prim: INTEGER :00
117+
26:d=3 hl=4 l= 579 cons: SET
118+
30:d=4 hl=4 l= 575 cons: SEQUENCE
119+
34:d=5 hl=2 l= 1 prim: INTEGER :00
120+
37:d=5 hl=2 l= 39 cons: SEQUENCE
121+
39:d=6 hl=2 l= 26 cons: SEQUENCE
122+
41:d=7 hl=2 l= 24 cons: SET
123+
43:d=8 hl=2 l= 22 cons: SEQUENCE
124+
45:d=9 hl=2 l= 3 prim: OBJECT :commonName
125+
50:d=9 hl=2 l= 15 prim: UTF8STRING :cryptography CA
126+
67:d=6 hl=2 l= 9 prim: INTEGER :E712D3A0A56ED6C9
127+
78:d=5 hl=2 l= 13 cons: SEQUENCE
128+
80:d=6 hl=2 l= 9 prim: OBJECT :rsaEncryption
129+
91:d=6 hl=2 l= 0 prim: NULL
130+
93:d=5 hl=4 l= 512 prim: OCTET STRING [HEX DUMP]:08086584F1DD436CDA1FB527B243FA02
131+
609:d=3 hl=2 l= 60 cons: SEQUENCE
132+
611:d=4 hl=2 l= 9 prim: OBJECT :pkcs7-data
133+
622:d=4 hl=2 l= 29 cons: SEQUENCE
134+
624:d=5 hl=2 l= 9 prim: OBJECT :aes-128-cbc
135+
635:d=5 hl=2 l= 16 prim: OCTET STRING [HEX DUMP]:2CD7875912507DFC7E65EA7CB86C73BB
136+
653:d=4 hl=2 l= 16 prim: cont [ 0 ]
137+
----
138+
139+
Dans cette structure, on observe bien les différents morceaux identifiés précédemment, comme :
140+
- Le type de message (pkcs7-envelopedData).
141+
- Les clés chiffrées et l'algorithme utilisé (RSA) pour chaque destinataire (ici, il n'y en a qu'un).
142+
- Le contenu chiffré et l'algorithme utilisé (AES 128 CBC)
143+
144+
=== Avec `asn1crypto`
145+
146+
On peut faire le même exercice en utilisant la librairie `asn1crypto`. Cette bibliothèque permet de
147+
manipuler des structures ASN.1 de manière plus aisée que `openssl`, en offrant des classes Python
148+
qui peuvent être sérialisées et désérialisées facilement en dictionnaires. Dans notre cas, prenons
149+
l'exemple de la lecture d'une partie de la structure du message chiffré :
150+
151+
[source, python]
152+
----
153+
from asn1crypto import cms
154+
155+
with open("vectors/enveloped.der", "rb") as file:
156+
enveloped = file.read()
157+
158+
# Load the structure
159+
content_info = cms.ContentInfo.load(enveloped)
160+
content_type: cms.ContentType = content_info["content_type"]
161+
enveloped_data: cms.EnvelopedData = content_info["content"]
162+
163+
# Encrypted content info
164+
encrypted_content_info: cms.EncryptedContentInfo = enveloped_data["encrypted_content_info"]
165+
dict(encrypted_content_info.native)
166+
----
167+
168+
Résultat :
169+
170+
[source, python]
171+
----
172+
{
173+
"content_encryption_algorithm": {
174+
"algorithm": "aes128_cbc",
175+
"parameters": b",\xd7\x87Y\x12P}\xfc~e\xea|\xb8ls\xbb",
176+
},
177+
"content_type": "data",
178+
"encrypted_content": b"[tN\xcb\xdd]\x0b\xa2\xa2\x98T\xf8[t_`",
179+
}
180+
----
181+
182+
== Déchiffrement
183+
184+
Nous allons maintenant déchiffrer le message chiffré. Pour cela, nous avons besoin des différentes
185+
informations du destinataire, notamment son certificat X.509 et sa clé privée RSA. Ensuite, nous
186+
verrons 3 méthodes pour déchiffrer le message : avec `openssl`, avec `asn1crypto` et avec
187+
`cryptography`.
188+
189+
=== Lecture du certificat et de la clé privée
190+
191+
Nous lisons le certificat X.509 et la clé privée RSA, avec la librairie `cryptography` :
192+
193+
[source, python]
194+
----
195+
from cryptography.hazmat.primitives.serialization import load_pem_private_key
196+
from cryptography.x509 import load_pem_x509_certificate
197+
198+
# Clé publique : certificat RSA
199+
with open("vectors/rsa_ca.pem", "rb") as file:
200+
certificate = load_pem_x509_certificate(file.read())
201+
202+
# Clé privée : RSA
203+
with open("vectors/rsa_key.pem", "rb") as file:
204+
private_key = load_pem_private_key(file.read(), password=None)
205+
----
206+
207+
=== Avec OpenSSL
208+
Utilisons `openssl` pour déchiffrer le message :
209+
210+
[source, python]
211+
----
212+
import subprocess
213+
214+
instructions = [
215+
"openssl",
216+
"cms",
217+
"-decrypt",
218+
"-in",
219+
"vectors/enveloped.der",
220+
"-inkey",
221+
"vectors/rsa_key.pem",
222+
"-inform",
223+
"der",
224+
]
225+
output = subprocess.run(instructions, capture_output=True)
226+
output.stdout.decode()
227+
----
228+
229+
=== Avec `asn1crypto`
230+
Nous utilisons `asn1crypto` pour analyser et déchiffrer le message :
231+
232+
[source, python]
233+
----
234+
from asn1crypto import cms
235+
from cryptography.hazmat.primitives import padding
236+
from cryptography.hazmat.primitives.asymmetric import padding as asymmetric_padding
237+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
238+
239+
# Load the message
240+
with open("vectors/enveloped.der", "rb") as file:
241+
enveloped = file.read()
242+
243+
# On lie les clés privées avec les numéros de série des certificats
244+
# Cela permet au destinataire de retrouver sa clé privée
245+
private_keys = {certificate.serial_number: private_key}
246+
247+
# Load the structure
248+
content_info = cms.ContentInfo.load(enveloped)
249+
content_type = content_info["content_type"]
250+
enveloped_data = content_info["content"]
251+
252+
# Decrypt the keys of recipient information, if possible
253+
decrypted_keys = {}
254+
for recipient_info in enveloped_data["recipient_infos"].native:
255+
serial_number = recipient_info["rid"]["serial_number"]
256+
if serial_number in private_keys:
257+
decrypted_keys[serial_number] = private_keys[serial_number].decrypt(
258+
recipient_info["encrypted_key"], asymmetric_padding.PKCS1v15()
259+
)
260+
261+
# Decrypt the content (AES 128 CBC)
262+
def decrypt(ciphertext, key, initialization_vector) -> str:
263+
cipher = Cipher(algorithms.AES(key), modes.CBC(initialization_vector))
264+
decryptor = cipher.decryptor()
265+
padded_data = decryptor.update(ciphertext) + decryptor.finalize()
266+
unpadder = padding.PKCS7(algorithms.AES128.block_size).unpadder()
267+
plaintext = unpadder.update(padded_data) + unpadder.finalize()
268+
return plaintext.decode()
269+
270+
encrypted = enveloped_data["encrypted_content_info"].native
271+
decrypted_content = decrypt(
272+
encrypted["encrypted_content"],
273+
decrypted_keys[certificate.serial_number],
274+
encrypted["content_encryption_algorithm"]["parameters"],
275+
)
276+
print("Decrypted content:", decrypted_content)
277+
----
278+
279+
=== Avec `cryptography`
280+
281+
Enfin, nous utilisons la nouvelle méthode de déchiffrement de messages PKCS #7 intégrée en Novembre
282+
2024 à version 44.0.0 de `cryptography` (pas encore sortie) :
283+
284+
[source, python]
285+
----
286+
from cryptography.hazmat.primitives.serialization import pkcs7
287+
288+
with open("vectors/enveloped.der", "rb") as file:
289+
enveloped = file.read()
290+
291+
decrypted = pkcs7.decrypt_der(enveloped, certificate, private_key, [])
292+
print("Decrypted content:", decrypted_content)
293+
----
294+
295+
== Conclusion
296+
Nous avons présenté différentes méthodes pour déchiffrer des emails chiffrés en utilisant Python.
297+
Chacune de ces méthodes ont leurs avantages et leurs inconvénients.
298+
299+
`openssl` est un exécutable facile d'accès contenant toutes les fonctionnalités cryptographiques à
300+
portée de la ligne de commande. Cependant, pour des raisons de sécurité, il est déconseillé
301+
d'utiliser OpenSSL directement, car il a déjà exposé des vulnérabilités par le passé. De plus, c'est
302+
une dépendance supplémentaire, bien qu'elle soit facile à régler via Conda.
303+
304+
La méthode utilisant `asn1crypto` permet de réaliser le déchiffrement 100% en Python, sans
305+
dépendance particulière. Néanmoins, elle demande des connaissances plus avancées en cryptographie et
306+
ne constitue pas une solution clé en main. En effet, la gestion d'autres cas d'usages de PKCS #7
307+
(signature, autres algorithmes) demande de la réflexion. De plus, `asn1crypto` est n'est plus
308+
maintenue depuis fin 2022.
309+
310+
Enfin, la méthode utilisant `cryptography` est la plus simple et la plus sécurisée. Elle est
311+
installée dans une librairie maintenue, et est fiable grâce à l'utilisation de Python & Rust. C'est
312+
la méthode que nous recommandons pour déchiffrer des emails chiffrés en Python.
313+
314+
A noter néanmoins que l'intégration de cette nouvelle fonctionnalité à `cryptography` a pris du
315+
temps à l'équipe SCIAM, entre développement et revues de code. Une contribution open-source prend du
316+
temps, mais c'est un investissement qui en vaut la peine !
317+
318+
319+
== Liens utiles
320+
* https://cryptography.io/en/latest/[Cryptography Documentation]
321+
* https://github.com/wbond/asn1crypto[ASN.1 Crypto Documentation]
322+
* https://www.baeldung.com/cs/public-key-cryptography-standards[Article sur PKCS #7]

images/authors/quentinretourne.jpg

60.6 KB
Loading

images/vignettes/email-decryption.png

472 KB
Loading

0 commit comments

Comments
 (0)