VWX.MEDIA
red and black love lock

Foto: FlyD / Unsplash (Unsplash License)

Post-quantum crypto w praktyce: ML-KEM krok po kroku

Hybrid ML-KEM-768 + X25519 w nginxie i OpenSSH na Debianie 12.

Admin· 10 maja 2026· 3 min czytania

Dlaczego post-quantum, dlaczego teraz

NIST sfinalizował standardy post-kwantowe w sierpniu 2024 (FIPS 203, 204, 205). ML-KEM (dawniej Kyber) zastępuje klasyczne ECDH w wymianie kluczy, a ML-DSA (Dilithium) - podpisy. W 2026 najwięksi gracze (Cloudflare, Google, AWS) mają już PQ w produkcji, więc czas, żeby polskie zespoły też przestały udawać, że problemu nie ma.

W tym artykule pokażę minimalny przykład wymiany kluczy ML-KEM-768 w Pythonie z użyciem biblioteki pyoqs (wrapper na liboqs 0.10), a potem przejdę do tego, jak włączyć PQ w nginxie i OpenSSH na Debianie.

Threat model: "harvest now, decrypt later"

Klasyczny argument - atakujący zbiera dziś szyfrowany ruch i czeka aż za 10-15 lat duży komputer kwantowy złamie ECDH. Dla danych o długim okresie ważności (medyczne, prawnicze, IP) to realne ryzyko już teraz. Dlatego najpierw migrujemy wymianę kluczy, a podpisy w drugiej kolejności - fałszywy podpis sprzed lat i tak jest bezużyteczny.

Instalacja środowiska

sudo apt install -y cmake ninja-build libssl-dev
git clone --depth 1 https://github.com/open-quantum-safe/liboqs
cd liboqs && mkdir build && cd build
cmake -GNinja -DOQS_BUILD_ONLY_LIB=ON ..
ninja && sudo ninja install
sudo ldconfig
pip install oqs-python==0.10.0

Minimalna wymiana kluczy

from oqs import KeyEncapsulation

ALG = "ML-KEM-768"  # NIST level 3, ~AES-192 equivalent

# Strona A (server) generuje parę kluczy
with KeyEncapsulation(ALG) as alice:
    public_key = alice.generate_keypair()

    # Strona B (client) szyfruje shared secret kluczem publicznym A
    with KeyEncapsulation(ALG) as bob:
        ciphertext, shared_b = bob.encap_secret(public_key)

    shared_a = alice.decap_secret(ciphertext)

assert shared_a == shared_b
print(f"Shared secret length: {len(shared_a)} bytes")

Co jest istotne:

  • klucz publiczny ML-KEM-768 ma 1184 bajty, ciphertext 1088 bajtów - to znacznie więcej niż 32 bajty ECDH X25519
  • shared secret ma 32 bajty, więc jest drop-in dla istniejących KDF-ów
  • operacje są bardzo szybkie (~50 µs na nowoczesnym CPU)

Hybrid mode: nie wyrzucaj ECDH

W produkcji nie zastępujemy klasycznej krypto - łączymy ją z PQ. Jeśli któryś z algorytmów się sypnie, drugi nadal trzyma. nginx 1.27+ z OpenSSL 3.5 obsługuje to natywnie:

ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1;
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;

Po reloadzie sprawdź w Wiresharku rozszerzenie key_share w ClientHello - powinieneś zobaczyć grupę X25519MLKEM768 (kod 0x11ec).

OpenSSH

OpenSSH 9.6+ ma sntrup761x25519-sha512@openssh.com od 2023, ale od wersji 9.8 dostępne jest też mlkem768x25519-sha256. W sshd_config:

KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512@openssh.com,curve25519-sha256

Uwaga: starsze klienty (PuTTY < 0.81, OpenSSH < 9.0) wynegocjują fallback > na curve25519 - to OK podczas migracji, ale po roku należy te dwa ostatnie > usunąć.

Pułapki, na które się nadziałem

  • MTU: większy ClientHello (3+ KB) bywa fragmentowany; jeśli widzisz zerwane TLS pod load balancerem, sprawdź MSS clamping
  • HSM: większość komercyjnych HSM-ów (luty 2026) nadal nie wspiera ML-KEM w hardware - softwarowy fallback jest OK, ale to nie to samo
  • Audyt: liboqs to projekt open-source, nie ma jeszcze FIPS 140-3 validation dla wszystkich kombinacji; jeżeli musisz mieć FIPS, użyj OpenSSL 3.5 z odpowiednim providerem

Co dalej

W kolejnym artykule pokażę, jak przepiąć Wireguard na PQ z użyciem Rosenpass i jak to się skaluje przy 200+ peerach.

woman standing under tree

// udostępnij

Post-quantum crypto w praktyce: ML-KEM krok po kroku - VWX.MEDIA