Post-quantum secure secrets manager with hybrid ML-KEM-768 + X25519 encryption and a terminal UI.
Defense-in-depth cryptography: Protects secrets against both classical attacks (X25519) and future quantum computers (ML-KEM-768). If one algorithm breaks, the other maintains security.
# Install
cargo install --path .
# Initialize vault (stores in ~/.local/share/dota/vault.dota by default)
dota init
# Launch TUI (default command)
dota
# Or use CLI commands
dota set API_KEY "secret-value"
dota get API_KEY
dota listflowchart LR
A[Passphrase] -->|Argon2| B[Master Key]
B --> C["ML-KEM-768 KEM"]
B --> D["X25519 ECDH"]
C --> E["HKDF-SHA256"]
D --> E
E --> F["AES-256-GCM"]
F --> G[Encrypted Secrets]
Hybrid KEM: Each secret is encrypted with AES-256-GCM. The AES key is derived by combining:
- ML-KEM-768 (post-quantum KEM) shared secret
- X25519 (classical ECDH) shared secret
- HKDF-SHA256 for domain separation
The vault stores ML-KEM ciphertexts, X25519 ephemeral public keys, and AES-GCM ciphertexts. Only someone with your passphrase can decrypt the master keypair and recover secrets.
- Post-quantum security: ML-KEM-768 (NIST FIPS 203 final standard)
- Classical security: X25519 elliptic curve Diffie-Hellman
- Memory safety: Rust with zeroization of sensitive data
- Key rotation:
dota rotate-keysre-encrypts all secrets with new keypairs - Export to environment:
dota export-env VAR1 VAR2outputs shell-compatible format - TUI and CLI: Interactive ratatui interface or scriptable commands
- Trust boundary: Vault file must be protected at rest (use disk encryption)
- Passphrase strength: Argon2id with 64 MiB memory, 3 iterations, 4 threads (OWASP 2024 recommended parameters)
- No network: All operations are local; no cloud sync or remote key escrow
- Threat model: Protects against passive adversaries with quantum computers (harvest-now-decrypt-later). Does not protect against active quantum adversaries or compromised endpoints.
- Algorithm choices: ML-KEM-768 for post-quantum resistance, X25519 for proven classical security, AES-256-GCM for authenticated encryption.
- Side channels: No explicit protection against timing or cache attacks (relies on constant-time implementations in dependencies).
Cryptographic details
- Passphrase → Argon2id (64 MiB, 3 iterations, 4 threads, 32-byte master key)
- Master key → Used to encrypt and authenticate the vault that stores ML-KEM-768 and X25519 key material
- ML-KEM-768 and X25519 keypairs are generated randomly and stored encrypted in the vault (no deterministic derivation from the master key)
- Generate ML-KEM-768 and X25519 keypairs
- For each secret:
- ML-KEM encapsulation → 32-byte shared secret + ciphertext
- X25519 ephemeral DH → 32-byte shared secret + ephemeral public key
- HKDF-SHA256(kem_ss || x25519_ss, salt, context) → 32-byte AES key
- AES-256-GCM(plaintext, aes_key, random_nonce) → ciphertext + tag
JSON structure with versioning (current: v4):
version: Protocol version for forward compatibilitycrypto: Argon2 parameters and keypair metadatasecrets: Map of name → (kem_ct, x25519_ephem_pk, aes_ct, nonce, tag, timestamp)
dota init Initialize new vault
dota unlock Launch TUI (default command)
dota set <NAME> <VALUE> Store a secret
dota get <NAME> Retrieve a secret
dota list List all secret names
dota rm <NAME> Remove a secret
dota export-env [NAMES...] Export secrets as shell variables
dota change-passphrase Update vault passphrase
dota rotate-keys Re-encrypt all secrets with new keypairs
dota info Show vault metadata
TUI shortcuts
j/kor↑/↓: Navigate secrets listEnter: Copy secret to clipboardn: Create new secret (prompts for name and value)e: Edit selected secretd: Delete selected secret (requires confirmation)r: Rotate all encryption keysq: Quit
- "Failed to decrypt vault": Incorrect passphrase or corrupted vault file. Check
~/.local/share/dota/vault.dota. - Slow unlock: Argon2id intentionally uses 64 MiB RAM and 3 iterations with 4 threads. Adjust parameters in vault metadata only if you understand the security tradeoffs.
# Run tests
cargo test
# Run with debug logging
RUST_LOG=debug cargo run
# Build optimized release binary
cargo build --releaseMIT
If you use this in research or security audits:
@software{dota2026,
author = {Fitch, Zack},
title = {Defense of the Artifacts: Post-quantum secure secrets manager},
year = {2026},
url = {https://github.com/johnzfitch/dota}
}