| name | post-quantum-crypto |
| description | Guide complet de la cryptographie post-quantique — Kyber (ML-KEM), Dilithium (ML-DSA), FALCON, SPHINCS+, NIST standards, réseaux euclidiens, isogenies, code-based, hash-based, et implémentations. |
| category | cybersecurite |
| tags | ["post-quantum","pqc","kyber","dilithium","falcon","sphincs","lattice","nist","quantum","cryptography"] |
Cryptographie Post-Quantique — Guide Approfondi
Sommaire
- Le Problème Quantique
- NIST PQC Standardization
- Lattice-Based Crypto (Réseaux Euclidiens)
- ML-KEM (Kyber) — Key Encapsulation Mechanism
- ML-DSA (Dilithium) — Digital Signatures
- FALCON — Signatures Compactes
- SLH-DSA (SPHINCS+) — Signatures Stateless Hash-Based
- FN-DSA (FALCON-based) et Autres Finalistes
- Implémentations et Migration
1. Le Problème Quantique
1.1 Algorithme de Shor (1994)
L'algorithme de Shor factorise un entier n en temps O((log n)³) sur un ordinateur quantique :
|0⟩ → H⊗ⁿ → |ψ₁⟩ → U_a^2⁰ → U_a^2¹ → ... → |ψ₂⟩ → QFT → |ψ₃⟩ → mesure
Étapes :
1. Superposition : préparer ∑|x⟩|0⟩
2. Exponentiation modulaire : ∑|x⟩|aˣ mod n⟩
3. Transformée de Fourier Quantique (QFT)
4. Mesure → période r de aˣ mod n
5. Si r pair, factorisation : pgcd(a^{r/2} ± 1, n)
Impact direct :
- RSA : cassé (factorisation)
- ECC (ECDSA, EdDSA, X25519) : cassé (logarithme discret)
- DSA (FIPS 186) : cassé (logarithme discret)
- Pairings (BLS, zk-SNARKs) : cassé
- Résistants : AES, SHA-2/3, BLAKE2/3 (Grover seulement)
1.2 Algorithme de Grover (1996)
Recherche non-structurée en O(√N) au lieu de O(N).
Impact sur la cryptographie symétrique :
- AES-128 : réduction de 128 à 64 bits de sécurité
- AES-256 : réduction de 256 à 128 bits de sécurité (toujours sûr)
- SHA-256 : réduction de 128 à 64 bits de résistance aux collisions
Mitigation : doubler la taille des clés symétriques.
1.3 Échéances Quantiques
| Année | Estimation | Source |
|---|
| 2030-2035 | QuBits logiques : ~2000 (casser ECC-256) | Mosca (2018) |
| 2035-2045 | QuBits physiques : ~10⁷ (casser RSA-2048) | NIST (2023) |
| 2035 | 50% de chance de casser RSA-2048 | Global Risk Institute (2023) |
Mosca's Theorem : le temps nécessaire pour migrer doit être < (année d'arrivée quantique - année actuelle).
2. NIST PQC Standardization
2.1 Processus NIST (2017-2024)
Round 1 (2017) : 69 candidats
Round 2 (2019) : 26 candidats
Round 3 (2022) : 7 finalistes + 8 alternates
Round 4 (2023) : 4 candidates supplémentaires
Sélectionnés (2024) :
ML-KEM (Kyber) → KEM standard
ML-DSA (Dilithium) → Signature standard
SLH-DSA (SPHINCS+) → Signature (hash-based, backup)
FN-DSA (FALCON) → Signature (compacte, optionnelle)
2.2 Standards NIST (FIPS)
| Standard | Algorithme | Type | Taille clé publique | Taille signature | Sécurité |
|---|
| FIPS 203 | ML-KEM (Kyber) | KEM | 800-1568 B | — | 128-256 bits |
| FIPS 204 | ML-DSA (Dilithium) | Signature | 1184-2592 B | 2420-4595 B | 128-256 bits |
| FIPS 205 | SLH-DSA (SPHINCS+) | Signature | 32-64 B | ~17-50 KB | 128-256 bits |
| FIPS 206 | FN-DSA (FALCON) | Signature | 897-1793 B | 666-1280 B | 128-256 bits |
3. Lattice-Based Crypto (Réseaux Euclidiens)
3.1 Modules Learning With Errors (MLWE)
La sécurité des algorithmes comme Kyber et Dilithium repose sur le problème Module Learning With Errors (M-LWE) :
Étant donné :
A ∈ R_q^{k×k} (matrice aléatoire)
t = A·s + e (où s, e sont petits)
Trouver s est difficile, même pour un ordinateur quantique.
R_q = Z_q[x]/(x^n + 1) : anneau de polynômes modulo cyclotomique.
Paramètres typiques (Kyber-512) :
- n = 256 (degré polynomial)
- q = 3329 (modulus)
- k = 2 (dimension du module)
- σ ≈ 2 (écart-type pour l'erreur gaussienne)
3.2 Syndrome Decoding Problem
Les attaques sur les réseaux euclidiens se réduisent à :
- SVP (Shortest Vector Problem) : trouver le plus court vecteur non-nul
- CVP (Closest Vector Problem) : trouver le point du réseau le plus proche d'une cible
- LWE : version moyennée, prouvée aussi difficile que SVP (Regev, 2005)
3.3 Estimation de sécurité (Lattice Estimator)
pip install lattice-estimator
from estimator import *
params = LWE.Parameters(n=256, q=3329, Xs=ND.CenteredBinomial(2), Xe=ND.CenteredBinomial(2))
security = estimate_lwe(params)
4. ML-KEM (Kyber) — Key Encapsulation Mechanism
4.1 Principe
Key Encapsulation Mechanism (KEM) : protocole en 3 étapes :
- KeyGen : génération de la paire (sk, pk)
- Encaps : produit un secret partagé + ciphertext
- Decaps : récupère le secret partagé depuis le ciphertext
4.2 Spécifications
| Paramètre | ML-KEM-512 | ML-KEM-768 | ML-KEM-1024 |
|---|
| Sécurité NIST | 128 bits | 192 bits | 256 bits |
| k (module) | 2 | 3 | 4 |
| η₁ | 3 | 2 | 2 |
| η₂ | 2 | 2 | 2 |
| du | 10 | 10 | 11 |
| dv | 4 | 4 | 5 |
| Clé publique | 800 B | 1184 B | 1568 B |
| Clé privée | 1632 B | 2400 B | 3168 B |
| Ciphertext | 768 B | 1088 B | 1568 B |
4.3 Algorithme
import hashlib
from random import randint
N = 256
Q = 3329
K = 2
ETA1 = 3
ETA2 = 2
def kyber_keygen():
"""ML-KEM.KeyGen : génération de clé"""
d = os.urandom(32)
rho, sigma = SHAKE256(d, 64)
A = generate_matrix_A(rho, k)
s = sample_poly_cbd(ETA1, k)
e = sample_poly_cbd(ETA1, k)
t = ntt_multiply_inverse(A, s) + e
ek = encode_pk(t, rho)
dk = encode_sk(s)
return ek, dk
def kyber_encaps(ek):
"""ML-KEM.Encaps : encapsulation"""
t, rho = decode_pk(ek)
m = os.urandom(32)
m_hash = H(m)
r = G(m_hash, H(ek))
A = generate_matrix_A(rho, k)
c1 = vector_ntt(A.T, r) + e1
c2 = encode_byte(t·r) + encode_bit(m)
K = KDF(m_hash, H(c1 || c2))
K, (c1, c2)
():
c1, c2 = ciphertext
s = decode_sk(dk)
m_prime = decode_bit(c2 - s·c1)
K_prime = recalculate_K(m_prime)
K_prime
4.4 Intégration TLS
Fonctionne comme un KEM hybride avec X25519 (RFC X-Wing) :
def hybrid_key_exchange(ecdhe_key, kyber_ek):
ecdhe_shared = x25519_ecdh(ecdhe_key)
kyber_shared, kyber_ct = kyber_encaps(kyber_ek)
shared_secret = SHA3_256(ecdhe_shared || kyber_shared)
return shared_secret, kyber_ct
5. ML-DSA (Dilithium) — Digital Signatures
5.1 Spécifications
| Paramètre | ML-DSA-44 | ML-DSA-65 | ML-DSA-87 |
|---|
| Sécurité NIST | 128 bits | 192 bits | 256 bits |
| k, l | 3, 2 | 4, 3 | 5, 4 |
| η | 2 | 4 | 2 |
| γ₁ | 2¹⁷ | 2¹⁹ | 2¹⁹ |
| γ₂ | (Q-1)/88 | (Q-1)/32 | (Q-1)/32 |
| Clé publique | 1312 B | 1952 B | 2592 B |
| Clé privée | 2560 B | 4032 B | 4896 B |
| Signature | 2420 B | 3309 B | 4595 B |
5.2 Algorithme Fiat-Shamir avec Aborts
Dilithium utilise le paradigme Fiat-Shamir with Aborts :
def dilithium_sign(sk, message):
"""ML-DSA.Sign — Signature Dilithium"""
s1, s2, t0 = unpack_sk(sk)
mu = H(message)
while True:
y = sample_y(gamma_1)
w = ntt_multiply(A, y)
w1 = high_bits(w, 2 * gamma_2)
c = sample_in_ball(Hash(mu, w1))
z = y + c * s1
if norm_infinity(z) >= gamma_1 - beta:
continue
r0 = low_bits(w - c * s2, 2 * gamma_2)
if norm_infinity(r0) >= gamma_2 - beta:
continue
hint = make_hint(-c * t0, w - c * s2 + c * t0)
return (z, hint, c)
def dilithium_verify(pk, message, signature):
"""ML-DSA.Verify — Vérification Dilithium"""
z, hint, c = signature
mu = H(message)
Az = ntt_multiply(A, z)
ct1 = c * t1
w_prime = Az - ct1
w1_prime = use_hint(hint, w_prime, 2 * gamma_2)
c_prime = sample_in_ball(Hash(mu, w1_prime))
return c == c_prime norm_infinity(z) < gamma_1 - beta
6. FALCON
6.1 Spécifications
FALCON (Fast Fourier Lattice-based Compact Signatures Over NTRU) :
| Paramètre | FALCON-512 | FALCON-1024 |
|---|
| Sécurité NIST | 128 bits | 256 bits |
| n | 512 | 1024 |
| q | 12289 | 12289 |
| Clé publique | 897 B | 1793 B |
| Clé privée | 1281 B | 2305 B |
| Signature | 666 B | 1280 B |
Avantage : signatures les plus compactes parmi les candidats NIST. Utilisé quand la bande passante est critique (DNS, IoT).
6.2 Principe (GPV Trapdoor)
FALCON utilise la construction Gentry-Peikert-Vaikuntanathan (GPV) avec l'échantillonnage FFT-based :
def falcon_sign(sk, message):
"""Signatures FALCON via échantillonnage Gaussian GPV"""
c = HashToPoint(message, n)
v = gaussian_fft_sampler(c, B, sigma)
s = compress_signature(v)
return s
Défi technique : l'échantillonnage Gaussian nécessite une précision arithmétique (FP) élevée — difficile à implémenter en temps constant.
7. SLH-DSA (SPHINCS+)
7.1 Spécifications
SPHINCS+ : signature basée uniquement sur des fonctions de hash (AES, SHA-2, SHA-3).
| Paramètre | SLH-DSA-128s | SLH-DSA-128f | SLH-DSA-192s | SLH-DSA-256s |
|---|
| Sécurité | 128 bits | 128 bits | 192 bits | 256 bits |
| Clé publique | 32 B | 32 B | 48 B | 64 B |
| Clé privée | 64 B | 64 B | 96 B | 128 B |
| Signature | 7856 B | 17088 B | 16224 B | 29792 B |
| Hash | SHA-256 | SHA-256 | SHA-256 | SHA-256 |
Avantage : seul standard PQC post-quantum prouvé. Basé uniquement sur la sécurité des hash (aucun réseau, aucun pairing).
7.2 Architecture (XMSS + WOTS + Hypertree)
SPHINCS+ utilise une structure arborescente :
FORS (Forest of Random Subsets) — feuilles
│
WOTS+ (Winternitz One-Time Signatures)
│
XMSS (eXtended Merkle Signature Scheme) — couches
│
Hypertree (couches d'arbres XMSS)
def wots_sign(message, secret_seed, w=16):
"""
WOTS+ : signature one-time avec paramètre w.
Plus w est grand, plus la signature est petite mais plus le calcul est lent.
"""
n = len(message) * 8 // w
msg_chunks = bytes_to_chunks(message, w)
checksum = sum(2**w - 1 - c for c in msg_chunks)
signature = []
for i in range(n + 1):
if i < n:
chain_len = msg_chunks[i]
else:
chain_len = checksum
secret = derive_secret(secret_seed, i)
sig_i = iterate_hash(secret, chain_len)
signature.append(sig_i)
return signature
8. FN-DSA et Autres
8.1 FN-DSA (FALCON-based)
Le NIST a standardisé FALCON comme FN-DSA (FIPS 206), prévu pour 2025.
8.2 Alternates et Autres Approches
| Approche | Exemple | Sécurité | Statut |
|---|
| Code-based | Classic McEliece | 256 bits | Finaliste NIST round 4 |
| Isogeny-based | SIKE (SIDH) | 128 bits | CASSÉ (2022, Castryck-Decru) |
| Hash-based | SPHINCS+ | 256 bits | Standardisé |
| Multivariate | GeMSS, Rainbow | 192 bits | CASSÉ (Rainbow 2022) |
8.3 Classic McEliece
L'un des plus vieux systèmes post-quantiques, toujours non-cassé depuis 1978.
def mceliece_keygen(m, t):
"""Génération de clé McEliece"""
n = 2**m
k = n - m * t
g = random_goppa_polynomial(m, t)
G = generate_generator_matrix(g, m, t)
S = random_invertible_matrix(k, k)
P = random_permutation_matrix(n, n)
G_pub = S * G * P
return (S, G, P), G_pub
9. Implémentations et Migration
9.1 Bibliothèques PQC
C/C++ :
git clone https://github.com/open-quantum-safe/liboqs
cd liboqs && mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..
make -j$(nproc)
sudo make install
Python :
pip install liboqs-python
pip install pqcrypto
Rust :
cargo add pqcrypto-kyber
cargo add pqcrypto-dilithium
cargo add pqcrypto-falcon
Go :
import "github.com/cloudflare/circl/kem/kyber"
import "github.com/cloudflare/circl/sign/dilithium"
9.2 Exemple complet (Python + liboqs)
import liboqs
def kem_demo():
kem = liboqs.KeyEncapsulation("Kyber768")
public_key = kem.generate_keypair()
secret_key = kem.export_secret_key()
kem_bob = liboqs.KeyEncapsulation("Kyber768")
ciphertext, shared_secret_bob = kem_bob.encap_secret(public_key)
shared_secret_alice = kem.decap_secret(ciphertext)
assert shared_secret_alice == shared_secret_bob
print(f"KEM OK: {len(shared_secret_alice)} bytes shared secret")
def sig_demo():
signer = liboqs.Signature("Dilithium3")
public_key = signer.generate_keypair()
message = b"Message to sign"
signature = signer.sign(message)
verifier = liboqs.Signature("Dilithium3")
is_valid = verifier.verify(message, signature, public_key)
print(f"Signature valide: {is_valid} ({len(signature)} bytes)")
def falcon_demo():
signer = liboqs.Signature("Falcon-1024")
pk = signer.generate_keypair()
sig = signer.sign(b"test")
print(f"FALCON-1024: signature {len(sig)} bytes")
def sphincs_demo():
signer = liboqs.Signature()
pk = signer.generate_keypair()
sig = signer.sign()
()
9.3 TLS Hybrides (OpenSSL + oqsprovider)
pip install oqsprovider
openssl s_server -cert server.crt -key server.key \
-groups x25519_kyber768:p384_kyber768
openssl s_client -connect localhost:4433 -groups x25519_kyber768
openssl list -groups | grep -E "kyber|dilithium"
9.4 Benchmarks Comparatifs (ML-DSA-65)
import time
import liboqs
def benchmark():
signer = liboqs.Signature("Dilithium3")
t0 = time.time()
pk = signer.generate_keypair()
print(f"KeyGen: {(time.time()-t0)*1000:.1f}ms")
msg = b"x" * 100
t0 = time.time()
sig = signer.sign(msg)
print(f"Sign: {(time.time()-t0)*1000:.1f}ms ({len(sig)} B)")
verifier = liboqs.Signature("Dilithium3")
t0 = time.time()
verifier.verify(msg, sig, pk)
print(f"Verify: {(time.time()-t0)*1000:.1f}ms")
9.5 Migration Strategy
ssl_certificate /etc/ssl/certs/hybrid-ecdsa-dilithium.pem
ssl_certificate_key /etc/ssl/private/hybrid-ecdsa-dilithium.key
ssl_groups "x25519:x448:kyber768"
Références