A self-hosted one-time secret notes service built with Axum + redb, using hybrid post-quantum cryptography.
ML-KEM-768 (FIPS 203 / CRYSTALS-Kyber)
+
AES-256-GCM
+
HKDF-SHA256 key derivation
| Layer | Algorithm | Quantum threat |
|---|---|---|
| KEM | ML-KEM-768 (Kyber) | Quantum-hard (lattice) |
| Symmetric | AES-256-GCM | ~128-bit post-quantum security (Grover) |
| KDF | HKDF-SHA256 | Combines both secrets |
Breaking the scheme requires simultaneously breaking both ML-KEM-768 (no known quantum attack) and the classical 32-byte random secret. This is the same hybrid approach used by Google, Cloudflare, and Apple.
POST /api/secrets
1. Generate ML-KEM-768 keypair (ek, dk)
2. Encapsulate: (ct_kem, ss_pq) = ek.encapsulate()
3. Generate 32-byte classical_secret
4. Derive AES key: HKDF-SHA256(ss_pq || classical_secret)
5. Encrypt message with AES-256-GCM
6. Store in redb: { ciphertext, nonce, ek, ct_kem, expires_at }
7. Return to creator: { id, dk, classical_secret } <- NEVER stored
POST /api/secrets/:id/reveal
1. Atomically DELETE record from redb
2. Decapsulate: ss_pq = dk.decapsulate(ct_kem)
3. Re-derive AES key: HKDF-SHA256(ss_pq || classical_secret)
4. Decrypt AES-256-GCM ciphertext
5. Return plaintext
The server never stores dk or classical_secret. A complete database dump reveals only quantum-hard ciphertext.
cargo runServer starts on http://0.0.0.0:3000.
POST /api/secrets
Content-Type: application/json
{ "message": "my secret", "ttl_secs": 3600 }Response:
{
"id": "<uuid>",
"dk": "<hex-ml-kem-768-decap-key>",
"classical_secret": "<hex-32-bytes>",
"link": "/api/secrets/<uuid>/reveal",
"expires_at": 1234567890
}Share id + dk + classical_secret with the recipient — ideally in a URL fragment (#dk=...&cs=...) so they never appear in server logs.
GET /api/secrets/:idPOST /api/secrets/:id/reveal
Content-Type: application/json
{ "dk": "<hex-ml-kem-768-decap-key>", "classical_secret": "<hex>" }Response:
{ "id": "<uuid>", "message": "my secret" }After this call the secret is permanently gone from the database.
- Post-quantum KEM: ML-KEM-768 is NIST FIPS 203 standardised — lattice-based, no known quantum attack
- Hybrid construction: AES key derived from
ss_pq || classical_secretvia HKDF — both must be broken simultaneously - Zero server-side key storage:
dkandclassical_secretare never written to disk - Atomic deletion: record is removed from redb before decryption begins — no race conditions
- TTL enforcement: lazy cleanup on peek and reveal
- AES-256-GCM authentication: provides ciphertext integrity (AEAD)
Uses redb — pure-Rust embedded key-value store. Database file: paravoid.redb.