Crypto — PQC envelope
Crypto — PQC envelope
Pollen8 encrypts every at-rest credential with a hybrid post-quantum
envelope. Configure + audit at /admin/crypto.
What’s in the envelope
Every secret_box.encrypt(plain) call now produces a v2 blob with
the pqcv2: prefix containing:
| Layer | Algorithm | Standard |
|---|---|---|
| Outer KEM (classical leg) | X25519 ECDH | (PQ-transition hybrid) |
| Outer KEM (PQ leg) | ML-KEM-1024 | FIPS 203 (Aug 2024) |
| KDF | HKDF-SHA3-512 | SHA-3 family is PQ-safe |
| AEAD | AES-256-GCM | 256-bit symmetric is PQ-safe |
| Audit-chain signature | ML-DSA-65 | FIPS 204 |
Both KEM shared secrets are concatenated before KDF — failure-safe. If ML-KEM is later broken, X25519 still protects. If a CRQC breaks X25519, ML-KEM still protects.
Why now
NIST finalized FIPS 203 / 204 / 205 in August 2024. CNSA 2.0 mandate transitions federal-grade systems by 2030. The “harvest-now-decrypt-later” threat justifies starting the migration today — anything encrypted with classical-only crypto in 2025 is at risk in 2030.
Key material
The static keypairs that decrypt all v2 blobs live in deployment env vars:
POLLENIX_PQC_MLKEM_PK=<base64>POLLENIX_PQC_MLKEM_SK=<base64>POLLENIX_PQC_MLDSA_PK=<base64>POLLENIX_PQC_MLDSA_SK=<base64>Generate them once at deploy time:
python -m pollenix_core.tools.gen_pqc_keysPaste the four lines into your Helm values / K8s Secret / AWS
Secrets Manager. The X25519 keypair derives deterministically from
pqc_master_seed (or sso_secret_key fallback) — no additional
env var.
When the env vars are missing, the process generates ephemeral
keys at startup and logs a warning. The /admin/crypto status
banner surfaces the same warning. Ephemeral is fine for local dev;
production restarts will break decryption of any v2 blob written
during the prior process lifetime.
Migration from v1 (legacy Fernet)
Every existing encrypted column starts on v1 (Fernet, AES-128-CBC + HMAC-SHA256). Two migration paths run in parallel:
Lazy — call sites that use secret_box.decrypt_and_maybe_rotate()
get back (plaintext, new_v2_blob_if_rotation_needed). The call
site can write the upgraded blob in the same transaction it reads.
Idempotent — once a row is v2, new_blob returns None.
Background — /admin/crypto → Rotate v1 → v2 sweeps every
encrypted column in batches (default 100 rows × 10k cap per call).
Pages through v1 rows, decrypts + re-encrypts as v2, writes back.
Run on a schedule until the per-table report shows v1 = 0 everywhere.
Tables swept (defined in services/pqc_rotator.py):
ai_providers.creds_encryptedsso_configs.client_secret_encryptedvoice_providers.creds_encryptedvault_entries.value_encryptedmigration_connections.client_secret_encryptedlegal_citation_providers.creds_encryptedlegal_dms_connections.oauth_tokens_encrypted+.api_key_encryptedlegal_bot_installations.creds_encrypted
Add new encrypted columns? Append to the ENCRYPTED_COLUMNS tuple
and the next survey picks them up.
API
GET /api/v1/admin/crypto/status keymat fingerprint + algorithm detailsGET /api/v1/admin/crypto/survey per-column v1 vs v2 counts (read-only)POST /api/v1/admin/crypto/rotate decrypt + re-encrypt up to max_rows rowsLibrary
We ship pqcrypto — Python bindings around PQClean’s vetted
reference implementations of ML-KEM and ML-DSA. Pure-pip install;
no external C library required.
For deployments that need the FIPS-certified module (federal
contracts, CNSA 2.0 mandate), swap to liboqs-python (bindings to
liboqs.so). The secret_box.encrypt/decrypt contract stays
identical — only the import in pqc.py changes.
Headline trade-offs
| What ships | |
|---|---|
| Cipher suite | NIST-standardized, August 2024 final |
| Library | PQClean reference implementations (vetted, not yet certified) |
| Performance | ML-KEM-1024 keygen ~5x slower than X25519 alone; still microseconds |
| Blob size | ~1.6 KB larger than v1 (ML-KEM CT is 1568 B); storage cost negligible |
| Hardware | Pure-Python path; HSM / Nitro Enclave isolation on the roadmap |
| Key isolation | Env vars → process memory; enclave isolation on the roadmap |
On the roadmap
- Per-tenant keypairs. Single deployment-wide keymat today; per-tenant key isolation lands when there’s customer pull.
- Hardware enclaves (Nitro / SGX / SEV-SNP). The cryptographic primitives don’t depend on enclaves; runtime isolation is a separate hardening pass.
- TLS hybrid key exchange. That’s load-balancer config
(Cloudflare already supports
X25519MLKEM768); no application code touches it. - JWT signing. Session JWTs still use HS256. Migration to ML-DSA-65 happens in a follow-up.