Post-Quantum Cryptography
Quantum-resistant algorithms (ML-DSA, ML-KEM, SLH-DSA)
Post-Quantum Cryptography (PQC) provides cryptographic algorithms that are secure against both classical and quantum computers. RNQC implements the NIST standardized lattice-based algorithms via OpenSSL 3.6+.
Why Post-Quantum?
Quantum computers threaten RSA, ECDSA, and ECDH. The PQC algorithms below are NIST-standardized replacements designed to resist quantum attacks while running efficiently on classical hardware.
Table of Contents
- Algorithms
- ML-DSA (Digital Signatures)
- ML-KEM (Key Encapsulation)
- SLH-DSA (Hash-Based Digital Signatures)
- WebCrypto API
- Real-World Examples
Algorithms
ML-DSA (FIPS 204)
Module Lattice Digital Signature Algorithm. Replacement for RSA and ECDSA signatures.
| Parameter Set | Security Level | Public Key | Signature | Use Case |
|---|---|---|---|---|
ML-DSA-44 | NIST Level 2 | 1,312 B | 2,420 B | General purpose |
ML-DSA-65 | NIST Level 3 | 1,952 B | 3,309 B | Recommended |
ML-DSA-87 | NIST Level 5 | 2,592 B | 4,627 B | Maximum security |
ML-KEM (FIPS 203)
Module Lattice Key Encapsulation Mechanism. Replacement for ECDH key exchange.
| Parameter Set | Security Level | Public Key | Ciphertext | Shared Secret |
|---|---|---|---|---|
ML-KEM-512 | NIST Level 1 | 800 B | 768 B | 32 B |
ML-KEM-768 | NIST Level 3 | 1,184 B | 1,088 B | 32 B |
ML-KEM-1024 | NIST Level 5 | 1,568 B | 1,568 B | 32 B |
SLH-DSA (FIPS 205)
Stateless Hash-Based Digital Signature Algorithm (formerly SPHINCS+). A hash-based alternative to ML-DSA — security relies only on the underlying hash function, making it a conservative choice when lattice assumptions are a concern. Each parameter set comes in s (small signature, slow) and f (fast, larger signature) variants.
| Parameter Set | Security Level | Public Key | Signature | Notes |
|---|---|---|---|---|
SLH-DSA-SHA2-128s / SLH-DSA-SHAKE-128s | NIST Level 1 | 32 B | 7,856 B | Small sig |
SLH-DSA-SHA2-128f / SLH-DSA-SHAKE-128f | NIST Level 1 | 32 B | 17,088 B | Fast sign |
SLH-DSA-SHA2-192s / SLH-DSA-SHAKE-192s | NIST Level 3 | 48 B | 16,224 B | Small sig |
SLH-DSA-SHA2-192f / SLH-DSA-SHAKE-192f | NIST Level 3 | 48 B | 35,664 B | Fast sign |
SLH-DSA-SHA2-256s / SLH-DSA-SHAKE-256s | NIST Level 5 | 64 B | 29,792 B | Small sig |
SLH-DSA-SHA2-256f / SLH-DSA-SHAKE-256f | NIST Level 5 | 64 B | 49,856 B | Fast sign |
Performance Tradeoff
The s variants produce smaller signatures but signing is markedly slower
than ML-DSA. The f variants sign faster but emit signatures 4–6× larger. For
most applications ML-DSA is preferable; reach for SLH-DSA when a hash-only
security assumption is required.
ML-DSA (Digital Signatures)
Node.js API
Generate ML-DSA key pairs and sign/verify using the standard crypto API:
import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto';
// Generate ML-DSA-65 key pair
const { publicKey, privateKey } = generateKeyPairSync('ml-dsa-65');
// Sign
const message = Buffer.from('quantum-safe message');
const signature = sign(null, message, privateKey);
// Verify
const isValid = verify(null, message, publicKey, signature);
console.log('Valid:', isValid); // trueKey Export/Import
ML-DSA keys support multiple export formats:
// Export as PEM
const pubPem = publicKey.export({ type: 'spki', format: 'pem' });
const privPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
// Export as DER
const pubDer = publicKey.export({ type: 'spki', format: 'der' });
// Re-import
import { createPublicKey, createPrivateKey } from 'react-native-quick-crypto';
const imported = createPublicKey({
key: pubDer,
format: 'der',
type: 'spki',
});ML-KEM (Key Encapsulation)
ML-KEM uses encapsulation rather than key exchange. One party encapsulates a shared secret using the other's public key, producing a ciphertext. The other party decapsulates the ciphertext with their private key to recover the same shared secret.
Node.js API
import {
generateKeyPairSync,
encapsulate,
decapsulate,
} from 'react-native-quick-crypto';
// Generate ML-KEM-768 key pair
const { publicKey, privateKey } = generateKeyPairSync('ml-kem-768');
// Encapsulate: produces shared secret + ciphertext
const { sharedSecret, ciphertext } = encapsulate(publicKey);
// Decapsulate: recovers the same shared secret
const recovered = decapsulate(privateKey, ciphertext);
console.log(sharedSecret.equals(recovered)); // trueSLH-DSA (Hash-Based Digital Signatures)
Node.js API
Generate SLH-DSA key pairs and sign/verify using the standard crypto API. Twelve parameter sets are available: slh-dsa-{sha2,shake}-{128,192,256}{s,f}.
import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto';
// Generate SLH-DSA-SHA2-128f key pair (fast variant)
const { publicKey, privateKey } = generateKeyPairSync('slh-dsa-sha2-128f');
// Sign
const message = Buffer.from('hash-based, quantum-safe message');
const signature = sign(null, message, privateKey);
// Verify
const isValid = verify(null, message, publicKey, signature);
console.log('Valid:', isValid); // trueWebCrypto API
PQC algorithms are fully supported through the SubtleCrypto interface.
ML-DSA via SubtleCrypto
import { subtle } from 'react-native-quick-crypto';
// Generate key pair
const keyPair = await subtle.generateKey({ name: 'ML-DSA-65' }, true, [
'sign',
'verify',
]);
// Sign
const data = new TextEncoder().encode('quantum-safe data');
const signature = await subtle.sign(
{ name: 'ML-DSA-65' },
keyPair.privateKey,
data,
);
// Verify
const isValid = await subtle.verify(
{ name: 'ML-DSA-65' },
keyPair.publicKey,
signature,
data,
);ML-KEM via SubtleCrypto
import { subtle } from 'react-native-quick-crypto';
// Generate encapsulation key pair
const keyPair = await subtle.generateKey({ name: 'ML-KEM-768' }, true, [
'deriveBits',
'deriveKey',
]);
// Encapsulate: get shared secret bits + ciphertext
const { sharedSecret, ciphertext } = await subtle.encapsulateBits(
{ name: 'ML-KEM-768' },
keyPair.publicKey,
);
// Decapsulate: recover shared secret
const recovered = await subtle.decapsulateBits(
{ name: 'ML-KEM-768' },
keyPair.privateKey,
ciphertext,
);
// Or derive a key directly from encapsulation
const { key: aesKey, ciphertext: ct } = await subtle.encapsulateKey(
{ name: 'ML-KEM-768' },
keyPair.publicKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt'],
);
// Decapsulate to get the same AES key
const recoveredKey = await subtle.decapsulateKey(
{ name: 'ML-KEM-768' },
keyPair.privateKey,
ct,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt'],
);SLH-DSA via SubtleCrypto
import { subtle } from 'react-native-quick-crypto';
// Generate key pair (use the canonical FIPS 205 name)
const keyPair = await subtle.generateKey({ name: 'SLH-DSA-SHA2-128f' }, true, [
'sign',
'verify',
]);
const data = new TextEncoder().encode('signed with hash-based PQC');
const signature = await subtle.sign(
{ name: 'SLH-DSA-SHA2-128f' },
keyPair.privateKey,
data,
);
const isValid = await subtle.verify(
{ name: 'SLH-DSA-SHA2-128f' },
keyPair.publicKey,
signature,
data,
);Key Export Formats
| Algorithm | spki | pkcs8 | jwk | raw-public | raw-seed |
|---|---|---|---|---|---|
| ML-DSA-44/65/87 | ✅ | ✅ | ✅ | ✅ | ✅ |
| ML-KEM-512/768/1024 | ✅ | ✅ | ✅ | ✅ | ✅ |
SLH-DSA-{SHA2,SHAKE}-{128,192,256}{s,f} | ✅ | ✅ | ✅ | ✅ | ✅ |
// Export ML-DSA public key as JWK
const jwk = await subtle.exportKey('jwk', keyPair.publicKey);
// Export raw public key bytes
const rawPub = await subtle.exportKey('raw-public', keyPair.publicKey);
// Export seed (deterministic private key material)
const seed = await subtle.exportKey('raw-seed', keyPair.privateKey);
// Import from raw-public
const imported = await subtle.importKey(
'raw-public',
rawPub,
{ name: 'ML-DSA-65' },
true,
['verify'],
);Real-World Examples
Hybrid Signature (Classical + PQC)
Combine Ed25519 with ML-DSA for defense-in-depth during the quantum transition:
import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto';
function hybridSign(message: Buffer) {
const ed = generateKeyPairSync('ed25519');
const pqc = generateKeyPairSync('ml-dsa-65');
const edSig = sign(null, message, ed.privateKey);
const pqcSig = sign(null, message, pqc.privateKey);
return {
message,
signatures: { ed25519: edSig, mlDsa65: pqcSig },
publicKeys: { ed25519: ed.publicKey, mlDsa65: pqc.publicKey },
};
}
function hybridVerify(signed: ReturnType<typeof hybridSign>): boolean {
const edValid = verify(
null,
signed.message,
signed.publicKeys.ed25519,
signed.signatures.ed25519,
);
const pqcValid = verify(
null,
signed.message,
signed.publicKeys.mlDsa65,
signed.signatures.mlDsa65,
);
// Both must pass
return edValid && pqcValid;
}Quantum-Safe Key Exchange
Use ML-KEM to establish a shared secret for symmetric encryption:
import {
generateKeyPairSync,
encapsulate,
decapsulate,
createCipheriv,
createDecipheriv,
createHash,
randomBytes,
} from 'react-native-quick-crypto';
// Server publishes its ML-KEM public key
const server = generateKeyPairSync('ml-kem-768');
// Client encapsulates a shared secret
const { sharedSecret, ciphertext } = encapsulate(server.publicKey);
// Derive AES key from shared secret
const aesKey = createHash('sha256').update(sharedSecret).digest();
const iv = randomBytes(12);
// Encrypt with AES-GCM
const cipher = createCipheriv('aes-256-gcm', aesKey, iv);
let encrypted = cipher.update('secret message', 'utf8', 'base64');
encrypted += cipher.final('base64');
const tag = cipher.getAuthTag();
// Server decapsulates to get the same shared secret
const serverSecret = decapsulate(server.privateKey, ciphertext);
const serverKey = createHash('sha256').update(serverSecret).digest();
// Server decrypts
const decipher = createDecipheriv('aes-256-gcm', serverKey, iv);
decipher.setAuthTag(tag);
let decrypted = decipher.update(encrypted, 'base64', 'utf8');
decrypted += decipher.final('utf8');
console.log(decrypted); // "secret message"