15. Cryptographie pratique#

La cryptographie est le socle de la sécurité des communications et du stockage. Ce chapitre aborde les concepts fondamentaux puis leurs implémentations concrètes sous Linux : OpenSSL, PKI, Let’s Encrypt, TLS, GPG, LUKS et la gestion des secrets.


Rappels de cryptographie#

Chiffrement symétrique#

Le même secret (clé) est utilisé pour chiffrer et déchiffrer. Très rapide, adapté aux grands volumes de données.

AES (Advanced Encryption Standard) est le standard actuel :

  • Tailles de clé : 128, 192 ou 256 bits

  • Modes d’opération : CBC (déprécié pour TLS), GCM (authentifié, recommandé), CTR

  • AES-256-GCM est le mode privilégié en 2026

Problème fondamental : comment partager le secret initial de façon sécurisée ?

Chiffrement asymétrique#

Deux clés mathématiquement liées : une clé publique (diffusée librement) et une clé privée (gardée secrète). Ce qui est chiffré avec l’une ne peut être déchiffré qu’avec l’autre.

Algorithme

Base mathématique

Taille recommandée

RSA

Factorisation de grands entiers

4096 bits (2026)

ECDSA/ECDH

Courbes elliptiques

P-256, P-384 ou Curve25519

Ed25519

Courbe de Bernstein

256 bits (≈ RSA 3072)

Le chiffrement asymétrique est lent. En pratique, on l’utilise pour échanger une clé symétrique (protocole hybride).

Fonctions de hachage cryptographique#

Une fonction de hachage transforme une entrée de taille quelconque en une empreinte (digest) de taille fixe. Elle est à sens unique et résistante aux collisions.

Algorithme

Taille du digest

Statut

MD5

128 bits

Obsolète (collisions connues)

SHA-1

160 bits

Déprécié (collisions démontrées)

SHA-256

256 bits

Standard actuel

SHA-3-256

256 bits

Algorithme alternatif (Keccak)

BLAKE2b

512 bits

Rapide, recommandé pour les fichiers

HMAC#

Le HMAC (Hash-based Message Authentication Code) combine une clé secrète et une fonction de hachage pour authentifier un message : HMAC(clé, message) = H(clé opad || H(clé ipad || message)).

Un HMAC garantit à la fois l”intégrité (le message n’a pas été modifié) et l”authenticité (seul le détenteur de la clé peut le produire).

Note

Un hash seul ne protège pas contre la falsification : un attaquant peut recalculer le hash d’un message modifié. Le HMAC nécessite la clé secrète, ce qui rend ce recalcul impossible sans elle.


OpenSSL en pratique#

Génération de clés#

# Clé RSA 4096 bits
openssl genrsa -out cle_privee.pem 4096

# Clé RSA chiffrée (demande une passphrase)
openssl genrsa -aes256 -out cle_privee_chiffree.pem 4096

# Clé EC (Curve P-384, recommandée)
openssl ecparam -name secp384r1 -genkey -noout -out cle_ec.pem

# Clé Ed25519
openssl genpkey -algorithm ed25519 -out cle_ed25519.pem

# Extraire la clé publique depuis la clé privée
openssl rsa -in cle_privee.pem -pubout -out cle_publique.pem

Certificat auto-signé#

# Générer clé + certificat auto-signé en une commande
openssl req -x509 -newkey rsa:4096 -keyout cle.pem -out cert.pem \
  -days 365 -nodes \
  -subj "/C=FR/ST=IDF/L=Paris/O=Mon Org/CN=monserveur.local"

# Avec Subject Alternative Names (SANs) — obligatoire depuis 2017
openssl req -x509 -newkey rsa:4096 -keyout cle.pem -out cert.pem \
  -days 365 -nodes \
  -subj "/CN=monserveur.local" \
  -addext "subjectAltName=DNS:monserveur.local,DNS:www.monserveur.local,IP:192.168.1.10"

CSR (Certificate Signing Request)#

# Générer un CSR pour soumission à une CA
openssl req -new -key cle_privee.pem -out demande.csr \
  -subj "/C=FR/O=Mon Org/CN=www.example.com"

# Vérifier le contenu du CSR
openssl req -in demande.csr -noout -text

Inspection et conversion#

# Inspecter un certificat
openssl x509 -in cert.pem -noout -text

# Vérifier les dates de validité
openssl x509 -in cert.pem -noout -dates

# Vérifier l'empreinte SHA-256
openssl x509 -in cert.pem -noout -fingerprint -sha256

# Convertir PEM → DER
openssl x509 -in cert.pem -outform DER -out cert.der

# Convertir DER → PEM
openssl x509 -in cert.der -inform DER -outform PEM -out cert.pem

# Créer un PKCS#12 (pour Windows/Java)
openssl pkcs12 -export -out certif.p12 \
  -inkey cle_privee.pem -in cert.pem -certfile ca_chain.pem

# Extraire depuis PKCS#12
openssl pkcs12 -in certif.p12 -out cert_extrait.pem -nodes

Tester une connexion TLS#

# Tester un serveur HTTPS
openssl s_client -connect monserveur.com:443 -servername monserveur.com

# Afficher seulement le certificat
echo | openssl s_client -connect monserveur.com:443 2>/dev/null \
  | openssl x509 -noout -text

# Vérifier la chaîne de certificats
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt cert.pem

Infrastructure PKI#

Une PKI (Public Key Infrastructure) est l’ensemble des composants nécessaires pour émettre, gérer et révoquer des certificats numériques.

Hiérarchie de confiance#

Root CA (hors ligne, stockée en coffre-fort)
  └── CA intermédiaire 1 (en ligne, signe les certificats finaux)
        ├── Certificat serveur A (*.example.com)
        ├── Certificat serveur B (api.example.com)
        └── Certificat client (vpn-alice)
  └── CA intermédiaire 2 (dédiée aux clients VPN)
        └── Certificat client (vpn-bob)

La CA racine est conservée hors ligne pour limiter le risque de compromission. Si une CA intermédiaire est compromise, seul son sous-arbre est affecté.

Créer une CA racine simple avec OpenSSL#

# Structure répertoires
mkdir -p /srv/pki/{ca_racine,ca_inter}/{certs,crl,newcerts,private}
chmod 700 /srv/pki/ca_racine/private
echo 1000 > /srv/pki/ca_racine/serial
touch /srv/pki/ca_racine/index.txt

# Clé CA racine (très longue durée, 20 ans)
openssl genrsa -aes256 -out /srv/pki/ca_racine/private/ca_racine.key 4096
chmod 400 /srv/pki/ca_racine/private/ca_racine.key

# Certificat CA racine auto-signé
openssl req -x509 -new -nodes -sha256 -days 7300 \
  -key /srv/pki/ca_racine/private/ca_racine.key \
  -out /srv/pki/ca_racine/certs/ca_racine.crt \
  -subj "/C=FR/O=Mon Org/CN=Mon Org Root CA"

Révocation — CRL et OCSP#

CRL (Certificate Revocation List) : liste signée par la CA contenant les numéros de série des certificats révoqués. Téléchargée périodiquement.

OCSP (Online Certificate Status Protocol) : requête en temps réel au répondeur OCSP de la CA pour vérifier le statut d’un certificat.

# Révoquer un certificat
openssl ca -revoke cert_compromis.pem -config openssl.cnf

# Générer une CRL
openssl ca -gencrl -out crl.pem -config openssl.cnf

# Interroger un répondeur OCSP
openssl ocsp -issuer ca_inter.crt -cert cert.pem \
  -url http://ocsp.example.com -resp_text

Let’s Encrypt et certbot#

Let’s Encrypt est une CA publique gratuite qui automatise l’émission de certificats via le protocole ACME (Automated Certificate Management Environment).

Fonctionnement ACME#

  1. Le client certbot génère une paire de clés et envoie une demande au serveur ACME

  2. Le serveur propose un challenge pour prouver la possession du domaine

  3. Le client résout le challenge

  4. Le serveur ACME vérifie et émet le certificat (valide 90 jours)

Types de challenges#

HTTP-01 : le client place un fichier token sur http://domaine.com/.well-known/acme-challenge/. Simple, nécessite le port 80.

DNS-01 : le client crée un enregistrement DNS TXT _acme-challenge.domaine.com. Permet les certificats wildcard (*.domaine.com), ne nécessite pas le port 80.

Tip

Le challenge DNS-01 est le seul permettant d’émettre des certificats wildcard et de valider des domaines non accessibles depuis Internet (réseaux internes). Il nécessite l’accès API à votre registraire DNS ou un plugin certbot compatible.

Certbot en pratique#

# Installation
apt install certbot python3-certbot-nginx

# Obtenir un certificat (challenge HTTP-01 avec Nginx)
certbot --nginx -d example.com -d www.example.com

# Certificat standalone (sans serveur web en fonctionnement)
certbot certonly --standalone -d example.com

# Certificat wildcard (challenge DNS-01)
certbot certonly --manual --preferred-challenges dns \
  -d "*.example.com" -d example.com

# Tester le renouvellement (dry-run)
certbot renew --dry-run

# Renouvellement automatique (via systemd timer)
systemctl status certbot.timer

Emplacements des fichiers#

/etc/letsencrypt/live/example.com/
  ├── cert.pem        — Certificat du serveur
  ├── chain.pem       — Certificats intermédiaires
  ├── fullchain.pem   — cert.pem + chain.pem (à utiliser dans Nginx/Apache)
  └── privkey.pem     — Clé privée

Configuration TLS moderne#

TLS 1.3#

TLS 1.3 (RFC 8446, 2018) apporte des améliorations majeures :

  • Handshake réduit à 1 round-trip (1-RTT) vs 2 sous TLS 1.2

  • Support du 0-RTT (reprise de session, attention au replay)

  • Suppression des algorithmes faibles : RC4, DES, 3DES, MD5, SHA-1 dans les handshakes

  • Perfect Forward Secrecy obligatoire (Diffie-Hellman éphémère)

Configuration Nginx A+ SSL Labs#

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Protocoles
    ssl_protocols TLSv1.2 TLSv1.3;

    # Cipher suites TLS 1.2 (TLS 1.3 gère les siennes automatiquement)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;  # Laisser le client choisir sous TLS 1.3

    # Paramètres DH (Perfect Forward Secrecy TLS 1.2)
    ssl_dhparam /etc/nginx/dhparam.pem;  # openssl dhparam -out dhparam.pem 4096

    # Cache de session
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    # HSTS (forcer HTTPS pour 2 ans, incluant les sous-domaines)
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # Autres en-têtes de sécurité
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
}

# Redirection HTTP → HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

GPG#

GPG (GNU Privacy Guard) est l’implémentation libre du standard OpenPGP. Il permet le chiffrement, la signature et la gestion d’une toile de confiance décentralisée.

Génération d’une paire de clés#

# Génération interactive (recommandée)
gpg --full-generate-key
# Choisir : (1) RSA and RSA, 4096 bits, 2 ans d'expiration

# Lister les clés
gpg --list-keys
gpg --list-secret-keys

Chiffrement et déchiffrement#

# Chiffrer pour alice (elle peut déchiffrer avec sa clé privée)
gpg --encrypt --recipient alice@example.com document.txt
# Produit document.txt.gpg

# Chiffrer pour plusieurs destinataires
gpg -e -r alice@example.com -r bob@example.com rapport.pdf

# Déchiffrer
gpg --decrypt document.txt.gpg > document_dechiffre.txt

# Chiffrement symétrique (passphrase sans clé GPG)
gpg --symmetric --cipher-algo AES256 fichier.txt

Signatures#

# Signer un fichier (signature détachée)
gpg --detach-sign --armor script.sh
# Produit script.sh.asc

# Vérifier une signature
gpg --verify script.sh.asc script.sh

# Signer + chiffrer en une commande
gpg --sign --encrypt --recipient alice@example.com rapport.pdf

Gestion des clés publiques#

# Exporter la clé publique
gpg --armor --export alice@example.com > alice_pub.asc

# Importer une clé publique reçue
gpg --import bob_pub.asc

# Publier sur un serveur de clés
gpg --keyserver keys.openpgp.org --send-keys FINGERPRINT

# Récupérer une clé depuis un serveur
gpg --keyserver keys.openpgp.org --recv-keys FINGERPRINT

# Signer la clé de bob (toile de confiance)
gpg --sign-key bob@example.com

gpg-agent#

# Voir le cache de l'agent
gpg-connect-agent "keyinfo --list" /bye

# Vider le cache (re-demande la passphrase)
gpg-connect-agent reloadagent /bye

# Configuration ~/.gnupg/gpg-agent.conf
default-cache-ttl 600
max-cache-ttl 7200

LUKS — chiffrement de partition#

LUKS (Linux Unified Key Setup) est le standard de chiffrement de disque sous Linux. Il utilise dm-crypt (module noyau) avec une couche de gestion des clés.

Architecture LUKS#

Un volume LUKS comporte :

  • Un en-tête (header) : métadonnées, paramètres de chiffrement, jusqu’à 8 keyslots

  • Un keyslot : version chiffrée de la clé maître, déverrouillable par une passphrase ou un fichier clé

  • Les données chiffrées : le contenu réel, chiffré avec AES-256-XTS

Formater et ouvrir un volume LUKS#

# Formater une partition (ATTENTION : efface toutes les données)
cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 \
  --key-size 512 --hash sha256 /dev/sdb1

# Vérifier l'en-tête
cryptsetup luksDump /dev/sdb1

# Ouvrir le volume (crée /dev/mapper/donnees_chiffrees)
cryptsetup luksOpen /dev/sdb1 donnees_chiffrees

# Créer un système de fichiers sur le volume déchiffré
mkfs.ext4 /dev/mapper/donnees_chiffrees

# Monter
mount /dev/mapper/donnees_chiffrees /mnt/donnees

# Démonter et fermer
umount /mnt/donnees
cryptsetup luksClose donnees_chiffrees

Gestion des keyslots#

# Ajouter un second moyen de déverrouillage (passphrase de secours)
cryptsetup luksAddKey /dev/sdb1

# Ajouter un fichier clé (pour déverrouillage automatique)
dd if=/dev/urandom of=/root/cle_luks.bin bs=4096 count=1
chmod 400 /root/cle_luks.bin
cryptsetup luksAddKey /dev/sdb1 /root/cle_luks.bin

# Supprimer un keyslot
cryptsetup luksKillSlot /dev/sdb1 1

# Voir les keyslots utilisés
cryptsetup luksDump /dev/sdb1 | grep "Key Slot"

Sauvegarde de l’en-tête#

# CRITIQUE : sauvegarder l'en-tête LUKS
# La destruction de l'en-tête rend les données inaccessibles à jamais
cryptsetup luksHeaderBackup /dev/sdb1 \
  --header-backup-file /srv/backup/sdb1_luks_header.bin

# Restaurer l'en-tête
cryptsetup luksHeaderRestore /dev/sdb1 \
  --header-backup-file /srv/backup/sdb1_luks_header.bin

Important

Toujours sauvegarder l’en-tête LUKS sur un support séparé (et chiffré) immédiatement après la création du volume. La perte de l’en-tête rend le déchiffrement impossible, même avec la passphrase correcte.

Montage automatique au démarrage#

# /etc/crypttab
donnees_chiffrees  /dev/sdb1  none  luks

# /etc/fstab
/dev/mapper/donnees_chiffrees  /mnt/donnees  ext4  defaults  0  2

Gestion des secrets#

Variables d’environnement vs fichiers#

Méthode

Avantages

Inconvénients

Variable d’env

Simple, pas de fichier

Visible dans /proc/PID/environ, héritée par les sous-processus

Fichier .env

Centralisé

Risque de commit accidentel dans git, permissions à contrôler

Fichier de config

Permissions fines (chmod 600)

Gestion manuelle, pas d’audit

Vault/gestionnaire

Audit, rotation, accès granulaire

Complexité d’infrastructure

# Ne JAMAIS faire :
export DB_PASSWORD="monmotdepasse"  # Visible dans history, ps aux

# Plutôt : lire depuis un fichier sécurisé
export DB_PASSWORD="$(cat /run/secrets/db_password)"
# Ou depuis un gestionnaire de secrets
export DB_PASSWORD="$(vault kv get -field=password secret/db)"

pass — gestionnaire de mots de passe en ligne de commande#

pass est un gestionnaire de mots de passe qui stocke chaque secret dans un fichier chiffré GPG.

# Initialiser le store avec sa clé GPG
pass init alice@example.com

# Ajouter un secret
pass insert prod/database/password
pass insert -m prod/api/credentials  # Entrée multi-lignes

# Récupérer un secret
pass prod/database/password

# Copier dans le presse-papiers (efface après 45s)
pass -c prod/database/password

# Générer un mot de passe aléatoire et le stocker
pass generate prod/service/api_key 32

# Structure stockée dans ~/.password-store/
# Chaque entrée est un fichier .gpg chiffré

HashiCorp Vault — présentation#

Vault est une solution de gestion des secrets d’entreprise :

Architecture Vault :
  ┌─────────────────────────────────────┐
  │  Vault Server                       │
  │  ┌──────────┐  ┌──────────────────┐ │
  │  │ Auth     │  │ Secrets Engines  │ │
  │  │ methods  │  │ kv, pki, aws,    │ │
  │  │ token    │  │ database, ssh…   │ │
  │  │ approle  │  └──────────────────┘ │
  │  │ k8s      │  ┌──────────────────┐ │
  │  └──────────┘  │ Audit backends   │ │
  │                │ file, syslog     │ │
  │                └──────────────────┘ │
  └─────────────────────────────────────┘
# Interactions basiques avec Vault
export VAULT_ADDR="https://vault.example.com:8200"
export VAULT_TOKEN="$(cat ~/.vault-token)"

# Stocker un secret
vault kv put secret/prod/db password="s3cr3t" user="app"

# Lire un secret
vault kv get secret/prod/db
vault kv get -field=password secret/prod/db

# Rotation automatique des credentials base de données
vault write database/rotate-root/ma-bdd

# Créer des credentials temporaires (TTL 1h)
vault read database/creds/mon-role

Fonctionnalités clés de Vault :

  • Secrets dynamiques : génération de credentials temporaires (DB, AWS, SSH)

  • PKI intégrée : émission de certificats à la volée

  • Audit complet : toutes les opérations sont journalisées

  • Unsealing : protection cryptographique au démarrage (Shamir’s Secret Sharing)


Démonstrations Python#

Hachage et vérification d’intégrité avec hashlib#


import hashlib
import os
import time

def hacher_fichier(chemin, algorithme="sha256"):
    """Calcule l'empreinte d'un fichier de façon efficace (par blocs)."""
    h = hashlib.new(algorithme)
    try:
        with open(chemin, "rb") as f:
            while chunk := f.read(65536):
                h.update(chunk)
        return h.hexdigest()
    except (FileNotFoundError, PermissionError) as e:
        return f"Erreur : {e}"

# Démo sur des fichiers système réels
fichiers_test = ["/etc/passwd", "/etc/hostname", "/etc/os-release"]
algorithmes = ["md5", "sha1", "sha256", "sha3_256", "blake2b"]

print("=== Comparaison des algorithmes de hachage ===\n")
fichier = "/etc/passwd"
print(f"Fichier : {fichier}\n")

for algo in algorithmes:
    debut = time.perf_counter()
    empreinte = hacher_fichier(fichier, algo)
    duree = (time.perf_counter() - debut) * 1000
    longueur = len(empreinte) * 4  # bits (hex : 4 bits par caractère)
    print(f"  {algo:12s} | {longueur:4d} bits | {duree:.3f} ms | {empreinte[:40]}…")

# Vérification d'intégrité simulée
print("\n=== Simulation de vérification d'intégrité ===\n")
contenu_original = b"Fichier de configuration critique v1.0\nclef=valeur_secrete\n"
contenu_modifie  = b"Fichier de configuration critique v1.0\nclef=VALEUR_MODIFIEE\n"

empreinte_ref = hashlib.sha256(contenu_original).hexdigest()
empreinte_actuelle = hashlib.sha256(contenu_modifie).hexdigest()

print(f"Empreinte de référence : {empreinte_ref}")
print(f"Empreinte actuelle     : {empreinte_actuelle}")
print(f"\nFichier intègre : {empreinte_ref == empreinte_actuelle}")
print("→ Le fichier a été modifié !" if empreinte_ref != empreinte_actuelle else "→ Fichier intact.")

# Collision partielle pour illustrer la longueur
print("\n=== Propriété d'avalanche (changement d'un octet) ===\n")
msg1 = b"Bonjour le monde"
msg2 = b"Bonjour le Monde"  # M majuscule seulement
h1 = hashlib.sha256(msg1).hexdigest()
h2 = hashlib.sha256(msg2).hexdigest()
bits_diff = sum(bin(int(a, 16) ^ int(b, 16)).count('1') for a, b in zip(h1, h2))
print(f"SHA-256('{msg1.decode()}') = {h1}")
print(f"SHA-256('{msg2.decode()}') = {h2}")
print(f"Bits différents : {bits_diff}/256 ({bits_diff/256*100:.1f}%) — effet avalanche")
=== Comparaison des algorithmes de hachage ===

Fichier : /etc/passwd

  md5          |  128 bits | 0.433 ms | 27ef66926622a4846a063fa0cc8a5f49…
  sha1         |  160 bits | 0.077 ms | 205f226ac363b89a9011918657c5125019ed3ccf…
  sha256       |  256 bits | 0.055 ms | f0c4662d2073ee70d0a0d811e8590105270d7cb7…
  sha3_256     |  256 bits | 0.089 ms | cd8f05800b3ac28443788ef12578447e6d7a0663…
  blake2b      |  512 bits | 0.086 ms | f3a8b7afc7cfc4e8b8babf51479de6271cf6afdd…

=== Simulation de vérification d'intégrité ===

Empreinte de référence : bee3d344c543df2276211e258b647edfaba5ff50f6390aef0c367daf400dbacd
Empreinte actuelle     : d361b9f25b98f2391ab1dd30255ff6f6dbec06119436dcda5c193e20091f6471

Fichier intègre : False
→ Le fichier a été modifié !

=== Propriété d'avalanche (changement d'un octet) ===

SHA-256('Bonjour le monde') = 4dc45b5ed3de202a5693c926caf95bd9710bef4fe5fb16a1c24a08ed428e8ae0
SHA-256('Bonjour le Monde') = 224f26a05fdaf472f4cdf5c93063214b7afdebb8799605af614587510f55a42f
Bits différents : 141/256 (55.1%) — effet avalanche

HMAC — authentification de message#


import hmac
import hashlib
import secrets
import base64

# Génération d'une clé secrète partagée
cle_secrete = secrets.token_bytes(32)
print(f"Clé secrète (base64) : {base64.b64encode(cle_secrete).decode()}")
print(f"Longueur : {len(cle_secrete) * 8} bits\n")

def calculer_hmac(cle, message):
    """Calcule un HMAC-SHA256."""
    if isinstance(message, str):
        message = message.encode("utf-8")
    return hmac.new(cle, message, hashlib.sha256).hexdigest()

def verifier_hmac(cle, message, hmac_recu):
    """Vérifie un HMAC en temps constant (résistant aux timing attacks)."""
    if isinstance(message, str):
        message = message.encode("utf-8")
    hmac_calcule = hmac.new(cle, message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(hmac_calcule, hmac_recu)

# Scénario : serveur envoie un token signé à un client
payload = '{"user": "alice", "role": "admin", "exp": 1711360000}'
mac = calculer_hmac(cle_secrete, payload)

print("=== Émission du token signé ===")
print(f"Payload  : {payload}")
print(f"HMAC-256 : {mac}\n")

# Vérification côté serveur — message intact
print("=== Vérification côté serveur ===")
print(f"Payload intact    → Valide : {verifier_hmac(cle_secrete, payload, mac)}")

# Tentative de falsification
payload_falsifie = '{"user": "alice", "role": "superadmin", "exp": 1711360000}'
print(f"Payload falsifié  → Valide : {verifier_hmac(cle_secrete, payload_falsifie, mac)}")

# Vérification avec une mauvaise clé
mauvaise_cle = secrets.token_bytes(32)
print(f"Mauvaise clé      → Valide : {verifier_hmac(mauvaise_cle, payload, mac)}\n")

# Démonstration compare_digest (timing constant)
print("=== compare_digest vs == (protection timing attack) ===")
mac_legitime = calculer_hmac(cle_secrete, "message test")
mac_faux = "a" * 64  # HMAC incorrect de même longueur
# hmac.compare_digest toujours le même temps, == peut court-circuiter
resultat = hmac.compare_digest(mac_legitime, mac_faux)
print(f"compare_digest (timing constant) : {resultat}")
print("→ Toujours préférer hmac.compare_digest pour comparer des MACs")
Clé secrète (base64) : PjzUa54a/aAMjtVzVYOHa1OU2BqhIZhFPH9DWLzCijo=
Longueur : 256 bits

=== Émission du token signé ===
Payload  : {"user": "alice", "role": "admin", "exp": 1711360000}
HMAC-256 : 7ce13ef06e46196ca078efbe65c2b6b7b69a7dcc5a5400a674880c556f2e6291

=== Vérification côté serveur ===
Payload intact    → Valide : True
Payload falsifié  → Valide : False
Mauvaise clé      → Valide : False

=== compare_digest vs == (protection timing attack) ===
compare_digest (timing constant) : False
→ Toujours préférer hmac.compare_digest pour comparer des MACs

Force brute vs longueur de clé#


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# Paramètres de la simulation
# GPU moderne (2026) : ~10^13 tentatives/seconde pour AES
tentatives_par_seconde = 1e13

longueurs_bits = np.arange(40, 260, 10)

# Nombre de clés possibles : 2^n
nb_cles = 2.0 ** longueurs_bits

# Temps moyen pour forcer (en secondes) : 2^(n-1) / vitesse
temps_secondes = (2.0 ** (longueurs_bits - 1)) / tentatives_par_seconde

# Conversion en unités lisibles
secondes_par_annee = 365.25 * 24 * 3600
secondes_par_univers = 13.8e9 * secondes_par_annee  # âge de l'univers

fig, ax = plt.subplots(figsize=(12, 6))

ax.semilogy(longueurs_bits, temps_secondes / secondes_par_annee,
            color="#4c9be8", linewidth=2.5, label="Temps moyen (années)")

# Lignes de référence
refs = [
    (1,                        "#2ecc71", "1 seconde"),
    (3600,                     "#f0c040", "1 heure"),
    (secondes_par_annee,       "#ff9800", "1 an"),
    (100 * secondes_par_annee, "#e84c4c", "100 ans"),
    (secondes_par_univers,     "#9b59b6", "Âge de l'univers"),
]
for valeur_s, couleur, label in refs:
    valeur_an = valeur_s / secondes_par_annee
    ax.axhline(y=valeur_an, color=couleur, linestyle="--",
               linewidth=1.2, alpha=0.7, label=label)

# Marquer les longueurs standards
standards = {
    56:  ("DES (obsolète)",    "#e84c4c"),
    80:  ("Limite min. 2020",  "#ff9800"),
    128: ("AES-128",           "#4c9be8"),
    192: ("AES-192",           "#2980b9"),
    256: ("AES-256",           "#1a252f"),
}
for bits, (nom, couleur) in standards.items():
    t_an = (2.0 ** (bits - 1)) / tentatives_par_seconde / secondes_par_annee
    ax.scatter([bits], [t_an], s=100, color=couleur, zorder=5)
    ax.annotate(f"{nom}\n({bits} bits)", (bits, t_an),
                textcoords="offset points", xytext=(8, -5),
                fontsize=8, color=couleur, fontweight="bold")

ax.set_xlabel("Longueur de clé (bits)", fontsize=11)
ax.set_ylabel("Temps de force brute moyen (années, échelle log)", fontsize=11)
ax.set_title(
    f"Temps de force brute vs longueur de clé\n"
    f"(GPU 2026 : {tentatives_par_seconde:.0e} tentatives/s)",
    fontsize=13
)
ax.set_xlim(40, 256)
ax.legend(loc="upper left", fontsize=9, ncol=2)

# Zone verte (sécurisé)
ax.axvspan(128, 256, alpha=0.05, color="#2ecc71")
ax.text(185, 1e-5, "Zone sécurisée", fontsize=9,
        color="#2e7d32", style="italic", alpha=0.7)

plt.show()
_images/1a409017c66dfe0f34738eaf449d47e1f1fdf71cf4ca90fdf290bed8fed802ff.png

Chaîne de confiance PKI#


import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyArrowPatch
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# Définition de la hiérarchie PKI
noeuds = [
    # (id, label, x, y, couleur, description)
    ("root_ca",     "Root CA\n(hors ligne)",       0.50, 0.88, "#c0392b", "Auto-signé\n10 ans\nRSA 4096"),
    ("inter_ca1",   "CA Intermédiaire\nServeurs",   0.25, 0.65, "#8e44ad", "Signé par Root CA\n5 ans\nRSA 4096"),
    ("inter_ca2",   "CA Intermédiaire\nClients VPN", 0.75, 0.65, "#2980b9", "Signé par Root CA\n5 ans\nRSA 4096"),
    ("cert_web",    "*.example.com",                0.10, 0.38, "#27ae60", "Wildcard\n90 jours\nEC P-384"),
    ("cert_api",    "api.example.com",              0.30, 0.38, "#27ae60", "SAN\n1 an\nRSA 2048"),
    ("cert_mail",   "mail.example.com",             0.50, 0.38, "#27ae60", "SAN\n1 an\nRSA 2048"),
    ("cert_vpn1",   "vpn-alice",                    0.68, 0.38, "#1abc9c", "Client\n1 an\nEd25519"),
    ("cert_vpn2",   "vpn-bob",                      0.82, 0.38, "#1abc9c", "Client\n1 an\nEd25519"),
    ("cert_vpn3",   "vpn-charlie",                  0.96, 0.38, "#1abc9c", "Client\n1 an\nEd25519"),
]

# Arêtes (parent → enfant)
aretes = [
    ("root_ca",   "inter_ca1"),
    ("root_ca",   "inter_ca2"),
    ("inter_ca1", "cert_web"),
    ("inter_ca1", "cert_api"),
    ("inter_ca1", "cert_mail"),
    ("inter_ca2", "cert_vpn1"),
    ("inter_ca2", "cert_vpn2"),
    ("inter_ca2", "cert_vpn3"),
]

pos = {n[0]: (n[2], n[3]) for n in noeuds}
couleurs = {n[0]: n[4] for n in noeuds}
labels = {n[0]: n[1] for n in noeuds}
descriptions = {n[0]: n[5] for n in noeuds}

fig, ax = plt.subplots(figsize=(14, 8))
ax.set_xlim(-0.05, 1.05)
ax.set_ylim(0.20, 1.02)
ax.axis("off")

# Tracer les arêtes
for src, dst in aretes:
    x1, y1 = pos[src]
    x2, y2 = pos[dst]
    arrow = FancyArrowPatch(
        (x1, y1 - 0.045), (x2, y2 + 0.045),
        arrowstyle="-|>",
        color="#888888",
        linewidth=1.5,
        mutation_scale=12,
        zorder=1
    )
    ax.add_patch(arrow)

# Tracer les nœuds
for nid, label, x, y, couleur, desc in noeuds:
    # Boîte principale
    largeur = 0.155 if "Intermédiaire" in label or nid == "root_ca" else 0.125
    hauteur = 0.075
    rect = mpatches.FancyBboxPatch(
        (x - largeur / 2, y - hauteur / 2),
        largeur, hauteur,
        boxstyle="round,pad=0.015",
        facecolor=couleur,
        edgecolor="white",
        linewidth=2,
        zorder=2
    )
    ax.add_patch(rect)
    ax.text(x, y, label, ha="center", va="center",
            fontsize=8, color="white", fontweight="bold", zorder=3,
            multialignment="center")

    # Descriptions en dessous
    ax.text(x, y - hauteur / 2 - 0.025, desc,
            ha="center", va="top",
            fontsize=6.5, color="#555555", style="italic",
            multialignment="center", zorder=3)

# Badge "hors ligne" pour Root CA
ax.text(0.50, 0.955,
        "⚠ Stockée en coffre-fort physique, déconnectée du réseau",
        ha="center", va="center", fontsize=8.5, color="#c0392b",
        bbox=dict(boxstyle="round,pad=0.3", facecolor="#ffeaa7",
                  edgecolor="#c0392b", linewidth=1.2, alpha=0.9))

# Niveaux
niveaux = [(0.88, "Niveau 0 — Autorité racine"), (0.65, "Niveau 1 — CA intermédiaires"),
           (0.38, "Niveau 2 — Certificats finaux")]
for y_lev, titre in niveaux:
    ax.axhline(y=y_lev - 0.06, color="#dddddd", linestyle=":", linewidth=1, zorder=0)
    ax.text(0.02, y_lev, titre, ha="left", va="center",
            fontsize=8, color="#888888", style="italic")

# Légende
legende = [
    mpatches.Patch(color="#c0392b", label="CA racine (auto-signée)"),
    mpatches.Patch(color="#8e44ad", label="CA intermédiaire — serveurs"),
    mpatches.Patch(color="#2980b9", label="CA intermédiaire — clients"),
    mpatches.Patch(color="#27ae60", label="Certificats serveurs"),
    mpatches.Patch(color="#1abc9c", label="Certificats clients"),
]
ax.legend(handles=legende, loc="lower center", ncol=5,
          fontsize=8, bbox_to_anchor=(0.5, 0.0))

ax.set_title("Infrastructure PKI — chaîne de confiance hiérarchique",
             fontsize=13, pad=12)
plt.show()
_images/a75d98a11aee1177cdfcf7b4f49cc6420453d0409fdeee4fc3283b62a6f6c15b.png

Résumé#

Ce chapitre a présenté les fondements et les outils pratiques de la cryptographie sous Linux :

Fondamentaux

  • Le chiffrement symétrique (AES-GCM) est rapide et adapté aux données ; l’asymétrique (RSA, EC) sert à l’échange de clés et aux signatures

  • Les fonctions de hachage (SHA-256, SHA-3) garantissent l’intégrité ; le HMAC y ajoute l’authenticité

  • La longueur de clé est le paramètre fondamental : AES-128 est suffisant aujourd’hui, AES-256 pour les données à longue durée de vie

OpenSSL et PKI

  • openssl req, genrsa, x509, verify couvrent l’essentiel de la gestion des certificats

  • Une PKI hiérarchique (CA racine hors ligne + CA intermédiaires) limite l’impact d’une compromission

  • La révocation (CRL/OCSP) est indispensable pour une PKI opérationnelle

TLS

  • TLS 1.3 est le standard : forward secrecy obligatoire, handshake simplifié, algorithmes faibles supprimés

  • HSTS, OCSP stapling et la sélection des cipher suites permettent d’atteindre un score A+ SSL Labs

GPG

  • Chiffrement et signature de fichiers sans infrastructure centralisée

  • La toile de confiance est le modèle décentralisé ; l’utilisation d’un serveur de clés facilite la distribution

LUKS

  • Chiffrement de partition standard sous Linux (dm-crypt + LUKS2)

  • Les keyslots permettent plusieurs modes de déverrouillage

  • La sauvegarde de l’en-tête LUKS est critique : sa perte rend les données irrécupérables

Gestion des secrets

  • Éviter les secrets dans les variables d’environnement ou les fichiers .env commitables

  • pass est simple et sécurisé pour un usage personnel ou une petite équipe

  • HashiCorp Vault est la référence pour les environnements de production : secrets dynamiques, audit, rotation automatique