ITSkillsCenter
Cybersécurité

Chiffrer en AES-256-GCM pas-à-pas — OpenSSL et Python avec script de fichier

17 min de lecture

L’AES (Advanced Encryption Standard) est l’algorithme de chiffrement symétrique le plus utilisé au monde en 2026 — du chiffrement TLS de votre navigateur au coffre BitLocker de votre laptop, en passant par les VPN, les conteneurs Cryptomator et le chiffrement de bases de données. Mais entre comprendre que « AES-256 c’est sûr » et savoir l’utiliser correctement dans son code, il y a un fossé que beaucoup de développeurs traversent en collant la première recette Stack Overflow venue — souvent buggée. Ce tutoriel propose une démarche en huit étapes pour chiffrer et déchiffrer des données avec AES-256 en mode GCM (Galois/Counter Mode, recommandé en 2026), en ligne de commande avec OpenSSL et en Python avec la bibliothèque cryptography. À la fin, on a un script reproductible et on comprend ce qui se passe à chaque ligne.

Pour la vue d’ensemble, voir le guide principal : Cryptographie pratique pour développeurs et sysadmins.

Prérequis

  • Un poste Linux, macOS ou Windows avec un terminal
  • OpenSSL 3.0+ installé (présent par défaut sur Linux et macOS récents, à installer via Win32 OpenSSL ou WSL sur Windows)
  • Python 3.10+ et la bibliothèque cryptography (pip install cryptography)
  • Notions de ligne de commande
  • 30 minutes

Étape 1 — Comprendre AES, le mode GCM et pourquoi pas ECB

AES est un chiffrement par blocs de 128 bits, opérant avec des clés de 128, 192 ou 256 bits. AES-256 est aujourd’hui le choix par défaut — l’écart de coût avec AES-128 est négligeable, l’écart de marge de sécurité ne l’est pas, et c’est le standard exigé par la majorité des réglementations. Mais l’algorithme seul ne suffit pas : il faut un mode opératoire qui décrit comment enchaîner les blocs.

Quatre modes circulent encore dans le code legacy. ECB (Electronic Codebook) chiffre chaque bloc indépendamment — résultat catastrophique : deux blocs identiques produisent deux ciphertexts identiques, et l’image chiffrée d’un logo reste visible. À bannir absolument. CBC (Cipher Block Chaining) chaîne les blocs avec un IV, mais ne protège pas contre l’altération du chiffré (attaques par padding oracle). CTR (Counter) transforme AES en chiffrement par flux, sans authentification non plus. GCM (Galois/Counter Mode) ajoute une authentification automatique du chiffré et des données associées — c’est le mode recommandé par le NIST en 2026 et utilisé par TLS 1.3, WireGuard et la plupart des protocoles modernes.

Règle pratique : pour tout nouveau code en 2026, utiliser AES-256-GCM. Si la plateforme ne le propose pas, utiliser AES-256-CBC avec un HMAC séparé (chiffrement-puis-MAC). Ne jamais utiliser ECB, jamais.

Étape 2 — Générer une clé aléatoire

Une clé AES-256 fait 32 octets (256 bits). Elle doit être générée par un générateur cryptographique sûr — jamais par random.randint, jamais par un mot de passe brut. Sous Linux et macOS, la source officielle est /dev/urandom, accessible via openssl rand :

openssl rand -hex 32

Cette commande renvoie 64 caractères hexadécimaux — chaque paire représentant un octet, soit 32 octets totaux. Exemple de sortie : 9f3c4b1e8a7d6c5f2e1a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e. À copier dans un gestionnaire de mots de passe (Bitwarden, KeePass) immédiatement — perdre la clé signifie perdre les données chiffrées avec elle.

Pour une clé que l’on dérive d’un mot de passe humain (cas d’un fichier protégé par phrase secrète), il faut passer par une fonction de dérivation lente comme PBKDF2, scrypt ou argon2 — jamais hash direct du mot de passe. OpenSSL le fait automatiquement avec l’option -pbkdf2 qu’on verra à l’étape suivante.

Étape 3 — Chiffrer un fichier avec OpenSSL

Pour un usage rapide en ligne de commande, OpenSSL fait l’affaire avec une dérivation par mot de passe. Préparer un fichier test :

echo "Données ultra-sensibles à chiffrer" > secret.txt
openssl enc -aes-256-cbc -salt -pbkdf2 -iter 600000 -in secret.txt -out secret.enc

Décortiquons les options. -aes-256-cbc sélectionne AES-256 en mode CBC (OpenSSL CLI ne propose pas GCM directement sur la commande enc, on passera par Python pour cela). -salt ajoute un sel aléatoire de 8 octets stocké dans le fichier de sortie — sans lui, le même mot de passe produirait toujours le même chiffré. -pbkdf2 impose la dérivation moderne (par défaut OpenSSL utilise une fonction MD5-based obsolète). -iter 600000 fixe le nombre d’itérations PBKDF2 à 600 000, conforme aux recommandations OWASP 2026 pour SHA-256.

OpenSSL demande un mot de passe interactivement, le confirme, puis produit secret.enc. Le fichier chiffré est binaire et inutilisable sans le mot de passe.

Pour déchiffrer :

openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -in secret.enc -out secret.dec

L’option -d bascule en mode déchiffrement. Mêmes algorithme et paramètres que pour le chiffrement — sinon le déchiffrement échoue avec bad decrypt. Vérifier avec cat secret.dec que l’original est restauré.

Étape 4 — Bascule sur AES-GCM en Python

Pour le chiffrement authentifié (qui détecte les altérations), Python avec la bibliothèque cryptography est le bon outil. Installer :

pip install cryptography

Le code minimal pour chiffrer une donnée avec AES-256-GCM :

import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def chiffrer(donnees: bytes, cle: bytes, donnees_associees: bytes = b'') -> bytes:
    nonce = os.urandom(12)  # 96 bits, taille standard pour GCM
    aesgcm = AESGCM(cle)
    ciphertext = aesgcm.encrypt(nonce, donnees, donnees_associees)
    return nonce + ciphertext

cle = AESGCM.generate_key(bit_length=256)  # 32 octets aléatoires
chiffre = chiffrer(b"Donnees ultra-sensibles", cle, b"contexte:facturation:2026")
print(chiffre.hex())

Trois éléments cruciaux dans ce code. La clé est générée par AESGCM.generate_key, qui appelle os.urandom en interne — source cryptographique fiable. Le nonce de 96 bits est aléatoire et différent à chaque chiffrement avec la même clé — réutiliser un nonce avec la même clé en GCM brise la confidentialité et l’authentification, c’est l’erreur la plus dangereuse en pratique. Les données associées (paramètre donnees_associees) ne sont pas chiffrées mais sont authentifiées — utile pour lier le chiffré à un contexte (id utilisateur, type de message) sans le révéler.

Le résultat concatène le nonce et le ciphertext (qui inclut un tag d’authentification de 16 octets en fin). Pour stocker, on garde toute la chaîne ensemble.

Étape 5 — Déchiffrer en Python avec vérification

Le déchiffrement extrait le nonce, déchiffre le ciphertext et vérifie le tag d’authentification. Si le ciphertext a été modifié d’un seul bit, ou si les données associées ne correspondent pas, une exception est levée. C’est le contrat fondamental de GCM.

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.exceptions import InvalidTag

def dechiffrer(donnees_chiffrees: bytes, cle: bytes, donnees_associees: bytes = b'') -> bytes:
    nonce = donnees_chiffrees[:12]
    ciphertext = donnees_chiffrees[12:]
    aesgcm = AESGCM(cle)
    try:
        return aesgcm.decrypt(nonce, ciphertext, donnees_associees)
    except InvalidTag:
        raise ValueError("Donnees alterees ou cle incorrecte")

# Test
clair = dechiffrer(chiffre, cle, b"contexte:facturation:2026")
print(clair.decode())

Tester ce qui se passe en cas d’altération : modifier un octet du ciphertext (par exemple chiffre = chiffre[:-1] + b’\\x00′) puis appeler dechiffrer. L’exception InvalidTag est levée — c’est exactement ce qu’on veut. Avec AES-CBC sans HMAC, l’altération aurait produit du déchiffré silencieusement corrompu, ouvrant la porte aux attaques par padding oracle.

Étape 6 — Dériver une clé d’un mot de passe

Stocker la clé brute dans le code source ou en variable d’environnement n’est pas toujours possible — parfois on veut juste qu’un utilisateur tape une phrase secrète. La dérivation passe par Argon2 ou PBKDF2. Avec cryptography :

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
import os

def deriver_cle(phrase: str, sel: bytes = None) -> tuple[bytes, bytes]:
    if sel is None:
        sel = os.urandom(16)
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=sel,
        iterations=600_000,
    )
    cle = kdf.derive(phrase.encode())
    return cle, sel

Trois paramètres importants. Le sel de 16 octets aléatoire est différent par mot de passe — empêche les rainbow tables. Le nombre d’itérations à 600 000 suit la recommandation OWASP 2026 pour PBKDF2-SHA256. La longueur de 32 octets correspond à AES-256.

Au stockage, conserver le sel à côté du chiffré (en clair, ce n’est pas un secret). Au déchiffrement, lire le sel, redériver la clé avec la même phrase et le même nombre d’itérations, puis déchiffrer normalement avec AESGCM.

Étape 7 — Cas pratique : chiffrer un fichier complet

Mettons tout ensemble dans un script utilisable. Sauvegarder dans un fichier aes_fichier.py :

import os, sys, getpass
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes

ITER = 600_000

def deriver(phrase: str, sel: bytes) -> bytes:
    return PBKDF2HMAC(hashes.SHA256(), 32, sel, ITER).derive(phrase.encode())

def chiffrer_fichier(chemin_in: str, chemin_out: str, phrase: str):
    sel = os.urandom(16)
    nonce = os.urandom(12)
    cle = deriver(phrase, sel)
    with open(chemin_in, 'rb') as f:
        clair = f.read()
    chiffre = AESGCM(cle).encrypt(nonce, clair, None)
    with open(chemin_out, 'wb') as f:
        f.write(sel + nonce + chiffre)

def dechiffrer_fichier(chemin_in: str, chemin_out: str, phrase: str):
    with open(chemin_in, 'rb') as f:
        donnees = f.read()
    sel, nonce, chiffre = donnees[:16], donnees[16:28], donnees[28:]
    cle = deriver(phrase, sel)
    clair = AESGCM(cle).decrypt(nonce, chiffre, None)
    with open(chemin_out, 'wb') as f:
        f.write(clair)

if __name__ == '__main__':
    action, src, dst = sys.argv[1], sys.argv[2], sys.argv[3]
    phrase = getpass.getpass("Phrase secrete: ")
    if action == 'enc':
        chiffrer_fichier(src, dst, phrase)
    elif action == 'dec':
        dechiffrer_fichier(src, dst, phrase)

Usage en ligne de commande : python aes_fichier.py enc rapport.pdf rapport.enc pour chiffrer, python aes_fichier.py dec rapport.enc rapport.pdf pour déchiffrer. La phrase secrète est demandée sans écho à l’écran (getpass). Le format de sortie concatène sel || nonce || chiffré — autodescriptif, déchiffrable avec uniquement la phrase.

Les mathématiques en clair

Comprendre ce qui se passe sous le capot d’AES n’est pas indispensable pour l’utiliser correctement, mais c’est la différence entre un développeur qui colle une recette et un développeur qui sait pourquoi il fait ce qu’il fait. AES repose sur deux mondes mathématiques imbriqués : l’algèbre des corps finis et l’art de la diffusion-confusion énoncé par Claude Shannon en 1949. Pas besoin d’un master de maths pour saisir l’essentiel — on va aller pas-à-pas.

Le bloc, vu comme une matrice 4×4

AES travaille toujours sur des blocs de 128 bits, soit 16 octets. Ces 16 octets sont arrangés dans une matrice carrée de 4 lignes par 4 colonnes appelée state. Si le bloc clair contient les octets b0, b1, b2, …, b15, ils sont placés colonne par colonne : b0 en haut à gauche, b1 en dessous, b2 encore en dessous, b3 en bas à gauche, puis b4 au sommet de la deuxième colonne, et ainsi de suite. Toutes les opérations qui suivent transforment cette matrice 4×4 en une autre matrice 4×4. À la fin, on relit la matrice colonne par colonne pour reconstituer les 16 octets chiffrés.

Le corps fini GF(2⁸), où chaque octet est un polynôme

L’astuce mathématique majeure d’AES : chaque octet n’est pas vu comme un nombre de 0 à 255, mais comme un polynôme à coefficients dans {0, 1}. L’octet 10110001 en binaire devient le polynôme x⁷ + x⁵ + x⁴ + 1 — chaque bit à 1 correspond à un terme de degré égal à sa position. On gagne ainsi une structure d’algèbre : on peut additionner et multiplier ces polynômes, à condition de réduire modulo un polynôme irréductible fixé. Pour AES, ce polynôme est x⁸ + x⁴ + x³ + x + 1. L’ensemble des octets muni de ces opérations forme ce qu’on appelle le corps de Galois GF(2⁸), qui contient 256 éléments — exactement le nombre de valeurs possibles d’un octet.

L’addition dans GF(2⁸) est extraordinairement simple : c’est le XOR bit à bit. 0xA5 ⊕ 0x3C = 0x99. La multiplication est plus subtile mais reste calculable rapidement par des tables précalculées dans toutes les implémentations. Cette structure de corps fini est ce qui permet à AES d’être à la fois inversible (chaque opération a une opération inverse mathématique exacte) et résistant aux attaques algébriques connues.

Les quatre étapes par round

Un round AES enchaîne quatre transformations sur la matrice state. Chaque étape vise un objectif précis dans le couple confusion-diffusion de Shannon — la confusion rend la relation entre clé et chiffré complexe, la diffusion répand l’influence d’un seul bit du clair sur de nombreux bits du chiffré.

SubBytes remplace chaque octet par un autre via la S-box, une table de 256 entrées soigneusement construite. Mathématiquement, chaque octet b est remplacé par l’inverse multiplicatif de b dans GF(2⁸), suivi d’une transformation affine. C’est l’étape de confusion non-linéaire — sans elle, AES serait soluble par algèbre linéaire élémentaire.

ShiftRows décale chaque ligne de la matrice. La première ligne ne bouge pas, la deuxième décale d’une position vers la gauche, la troisième de deux, la quatrième de trois. C’est une étape de diffusion simple qui mélange les colonnes entre elles.

MixColumns opère sur chaque colonne indépendamment. Chaque colonne (vue comme un polynôme de degré 3 à coefficients dans GF(2⁸)) est multipliée par un polynôme fixe 3x³ + x² + x + 2 modulo x⁴ + 1. Le résultat est une nouvelle colonne où chaque octet de sortie dépend des quatre octets de la colonne d’entrée. C’est l’étape de diffusion majeure : un changement d’un bit en entrée modifie en moyenne la moitié des bits en sortie.

AddRoundKey XORe la matrice state avec une sous-clé de 128 bits dérivée de la clé principale. C’est le seul endroit où la clé entre directement en jeu. Le XOR est l’opération inverse d’elle-même, donc le déchiffrement utilisera la même AddRoundKey avec les mêmes sous-clés en ordre inverse.

Combien de rounds, et pourquoi ?

AES-128 enchaîne 10 rounds, AES-192 en fait 12, AES-256 en fait 14. Plus la clé est longue, plus on ajoute de rounds — pour conserver une marge de sécurité homogène contre les attaques par cryptanalyse différentielle et linéaire connues. Les concepteurs Daemen et Rijmen ont démontré qu’à partir de 4 rounds, l’influence d’un seul bit clair se répand sur l’ensemble du bloc (propriété de pleine diffusion). Les rounds supplémentaires sont une marge de sécurité contre des attaques futures non encore publiées.

Le premier round est précédé d’un AddRoundKey supplémentaire (round 0), et le dernier round omet MixColumns. Ces deux ajustements rendent l’algorithme inversible avec une structure symétrique élégante.

Pourquoi GCM, mathématiquement ?

Le mode GCM combine deux composants. CTR mode transforme AES (chiffrement par blocs) en chiffrement par flux : on chiffre un compteur qui incrémente bloc après bloc, puis on XORe la sortie avec le clair. C’est la partie qui assure la confidentialité. GHASH est une fonction d’authentification basée sur la multiplication dans le corps fini GF(2¹²⁸) — encore un corps de Galois, mais cette fois sur 128 bits. GHASH calcule un MAC universel sur le chiffré et les données associées, en multipliant successivement chaque bloc par une clé de hachage H (qui est elle-même AES(K, 0)).

La résistance de GCM repose sur deux faits mathématiques. Premièrement, la multiplication dans GF(2¹²⁸) est très rapide en logiciel comme en matériel grâce à l’instruction PCLMULQDQ sur x86. Deuxièmement, la probabilité qu’un attaquant fabrique un faux tag d’authentification valide est de l’ordre de 2⁻¹²⁸, soit pratiquement nulle — à condition de ne jamais réutiliser un nonce. Réutiliser un nonce avec la même clé permet à l’attaquant de récupérer la clé de hachage H par soustraction de deux ciphertexts, brisant à la fois la confidentialité et l’authentification. C’est la raison pour laquelle os.urandom(12) à chaque chiffrement n’est pas un détail mais un invariant absolu.

Pourquoi PBKDF2 600 000 itérations, mathématiquement ?

PBKDF2 itère une fonction HMAC-SHA-256 un grand nombre de fois sur la phrase secrète plus le sel. Mathématiquement, chaque itération i calcule U_i = HMAC(phrase, U_{i-1}), où U_0 = HMAC(phrase, sel || 1). Le résultat est U_1 ⊕ U_2 ⊕ … ⊕ U_n. Pour un attaquant qui essaie chaque mot de passe candidat, chaque tentative coûte n évaluations HMAC, soit 600 000 évaluations en 2026. Sur un GPU moderne capable d’environ 10 milliards de SHA-256 par seconde, tester un mot de passe coûte environ 60 microsecondes — multiplié par les milliards de candidats d’un dictionnaire, on passe en jours ou années plutôt qu’en minutes.

L’augmentation du nombre d’itérations au fil des années (de 1 000 en 2000 à 600 000 en 2026) suit la loi de Moore inversée : à mesure que les GPU deviennent plus rapides, on doit ralentir PBKDF2 d’autant pour conserver le même temps d’attaque. C’est pourquoi argon2id, qui ajoute la consommation mémoire dans l’équation et résiste mieux aux GPU, prend progressivement le relais.

Étape 8 — Tester la robustesse

Cinq tests valident l’implémentation. Le test aller-retour identique : chiffrer un fichier, le déchiffrer, comparer avec l’original au binaire (diff -q rapport.pdf rapport.dec). Le test mauvaise phrase : tenter le déchiffrement avec une phrase erronée — doit lever InvalidTag. Le test altération : modifier un octet du fichier chiffré, déchiffrer — doit lever InvalidTag. Le test nonce différent : chiffrer le même fichier deux fois, comparer les sorties — doivent être différentes (sels et nonces aléatoires). Le test performance : chiffrer un fichier de 100 Mo, mesurer — doit prendre quelques secondes selon le matériel.

Si un test échoue, ne pas mettre l’implémentation en production. La cryptographie est un domaine où une erreur silencieuse coûte plus cher qu’un bug visible.

Vérification du livrable

  • Le script chiffre et déchiffre correctement des fichiers
  • Le mode utilisé est AES-256-GCM (authentifié)
  • La dérivation utilise PBKDF2 avec 600 000 itérations
  • Sel et nonce sont aléatoires à chaque chiffrement
  • Une altération du chiffré est détectée par InvalidTag
  • La phrase secrète est saisie sans écho et non journalisée

Erreurs fréquentes

ErreurConséquenceSolution
Mode ECBPatterns visibles dans le chiffréGCM ou CBC+HMAC, jamais ECB
Nonce GCM réutiliséConfidentialité et authenticité briséesos.urandom(12) à chaque chiffrement
Hash direct du mot de passeBrute force facilePBKDF2/scrypt/argon2 avec sel
Itérations PBKDF2 trop bassesBrute force possible sur GPU600 000 minimum pour SHA-256 en 2026
Clé dans le code sourceFuite via Git publicVariable d’environnement ou Vault
Random Python (random.X)Pas cryptographiqueos.urandom ou secrets.token_bytes

Cas concrets en PME ouest-africaine

Trois contextes locaux où ce script appliqué change la donne. Le transfert de fichiers comptables entre cabinets et clients à Dakar : chiffrer le bilan PDF avec une phrase partagée hors-bande (par téléphone ou en main propre) avant l’envoi par mail évite que le contenu soit lu sur les serveurs intermédiaires, particulièrement utile quand la chaîne mail passe par plusieurs relais. Le backup d’une base de données avant upload sur un cloud : passer le dump SQL au script avec une phrase forte stockée dans Bitwarden, puis pousser le fichier .enc vers Google Drive ou Backblaze — la nuit ouest-africaine est un bon moment pour synchroniser sans saturer la fibre. La protection d’un fichier de configuration contenant des clés API : chiffrer en local, déchiffrer au démarrage de l’application avec une phrase saisie au démarrage du serveur ; la phrase reste en mémoire, jamais sur disque, ce qui ferme la voie aux fuites en cas de vol physique du serveur.

Tutoriels frères

Pour aller plus loin

Sources et références

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité