A production-grade hybrid post-quantum encryption example using ML-KEM-768 (FIPS 203, formerly Kyber) and AES-256-GCM, implemented in PHP via liboqs FFI bindings.
RSA and elliptic-curve cryptography (ECDH, ECDSA) can be broken by a sufficiently powerful quantum computer running Shor's algorithm. ML-KEM is the NIST-standardized replacement for key exchange, designed to resist both classical and quantum attacks.
This project demonstrates how to use ML-KEM in PHP today.
The encryption follows a hybrid KEM + symmetric pattern, which is the standard approach for post-quantum encryption:
┌─────────────────────────────────────────────────────────┐
│ SENDER │
│ │
│ 1. ML-KEM-768 Encapsulate(publicKey) │
│ → sharedSecret (32 bytes) │
│ → kemCiphertext (1088 bytes) │
│ │
│ 2. AES-256-GCM Encrypt(plaintext, sha256(sharedSecret))│
│ → aesCiphertext + iv + tag │
│ │
│ 3. Output: kemCiphertext || iv || tag || aesCiphertext │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ RECEIVER │
│ │
│ 1. ML-KEM-768 Decapsulate(kemCiphertext, secretKey) │
│ → sharedSecret (32 bytes) │
│ │
│ 2. AES-256-GCM Decrypt(aesCiphertext, sha256(secret)) │
│ → plaintext │
└─────────────────────────────────────────────────────────┘
| Component | Algorithm | Purpose |
|---|---|---|
| Key Encapsulation | ML-KEM-768 (NIST Level 3) | Quantum-resistant key exchange |
| Symmetric Encryption | AES-256-GCM | Authenticated encryption of message data |
| Key Derivation | SHA-256 | Derive AES key from KEM shared secret |
| Parameter | Size |
|---|---|
| Public Key | 1,184 bytes |
| Secret Key | 2,400 bytes |
| KEM Ciphertext | 1,088 bytes |
| Shared Secret | 32 bytes |
docker build -t pqc-php .
docker run --rm pqc-phpPost-Quantum Encryption (Production)
==================================================
Algorithm: ML-KEM-768
NIST Level: 3
Public Key: 1184 bytes
Secret Key: 2400 bytes
KEM Ciphertext: 1088 bytes
Shared Secret: 32 bytes
==================================================
Keypair generated.
Encrypted "hello world" -> 1127 bytes
Payload (hex):
5afc9c0178118bb27c280bc1dae51efc4c3a16560c6042fae3b0d8deb3924fe8...
Decrypted: "hello world"
Match: YES
$pqc = new PostQuantumEncryption();
// Generate a keypair
$keys = $pqc->generateKeypair();
// $keys['publicKey'] - share this with the sender
// $keys['secretKey'] - keep this private
// Encrypt (sender side)
$encrypted = $pqc->encrypt('secret message', $keys['publicKey']);
// Decrypt (receiver side)
$decrypted = $pqc->decrypt($encrypted, $keys['secretKey']);- NIST-standardized algorithm: ML-KEM-768 (FIPS 203), not an experimental or deprecated scheme
- Battle-tested implementation: Uses liboqs (Open Quantum Safe), a widely reviewed C library
- Authenticated encryption: AES-256-GCM provides both confidentiality and integrity
- Key material zeroisation: Shared secrets and derived keys are wiped from memory via
FFI::memsetandsodium_memzeroinfinallyblocks, guaranteeing cleanup on all exit paths - No custom cryptography: All crypto primitives come from liboqs and OpenSSL
The encrypted output is a single binary string:
[ KEM Ciphertext (1088 bytes) ][ IV (12 bytes) ][ GCM Tag (16 bytes) ][ AES Ciphertext ]
- PHP 8.3+ with FFI and OpenSSL extensions
- liboqs 0.12.0+ (built automatically by the Dockerfile)
- libsodium (for
sodium_memzero, included in PHP by default)
.
├── Dockerfile # Builds PHP + liboqs environment
├── pqc_encryption.php # PostQuantumEncryption class + demo
└── README.md
MIT