---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.0
kernelspec:
  name: python3
  display_name: Python 3
---

# 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).

```{admonition} Note
:class: 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

```bash
# 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é

```bash
# 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)

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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.

```bash
# 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.

```{admonition} Tip
:class: 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

```bash
# 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

```nginx
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

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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
```

```{admonition} Important
:class: 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

```bash
# /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 |

```bash
# 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.

```bash
# 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     │ │
  │                └──────────────────┘ │
  └─────────────────────────────────────┘
```

```bash
# 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

<br>

```{code-cell} python3
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")
```

### HMAC — authentification de message

<br>

```{code-cell} python3
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")
```

### Force brute vs longueur de clé

<br>

```{code-cell} python3
:tags: [hide-input]

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()
```

### Chaîne de confiance PKI

<br>

```{code-cell} python3
:tags: [hide-input]

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()
```

---

## 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
