
Post-quantum hybrid encryption library combining X25519 + ML-KEM-768 with AES-256-GCM
Post-quantum hybrid encryption and key management server.
Citadel combines X25519 + ML-KEM-768 for key encapsulation and AES-256-GCM for data encryption, following NIST's hybrid approach for the post-quantum transition. Applications encrypt and decrypt data through a REST API. Citadel manages the keys — generation, rotation, revocation, access control, and audit logging.
Status: Working implementation. Unaudited. No production deployments. See Security below.
Your Application Citadel Database
| | |
|-- POST /encrypt ------->| |
| |-- hybrid KEM (X25519+ML-KEM) |
| |-- derive AES-256 key (HKDF) |
| |-- encrypt with AES-256-GCM |
|<-- encrypted blob ------| |
| |
|-- store blob ------------------------------------------>|
Your application never touches raw key material. The encrypted blob is self-contained — it includes the wrapped key, algorithm identifiers, and ciphertext. Store it in any database. Decrypt by sending it back to Citadel with the same AAD and context.
citadel-envelope Hybrid encryption core (X25519 + ML-KEM-768 + AES-256-GCM)
citadel-keystore Key lifecycle management, 4-level hierarchy, threat-adaptive policies
citadel-api HTTP server, scoped API key auth, rate limiting, real-time dashboard
# Clone
git clone https://github.com/mrcord77/rust_citadel.git
cd rust_citadel
# Set your admin API key
echo -n "your-secret-key" | sha256sum | cut -d' ' -f1
# Copy the hash
# Start
CITADEL_API_KEY_HASH=<paste-hash> docker compose up -d
# Verify
curl http://localhost:3000/health
# {"status":"ok","version":"0.2.0"}
Dashboard: http://localhost:3000
Requires Rust 1.75+.
cargo build --release -p citadel-api
CITADEL_API_KEY="your-secret-key" CITADEL_SEED_DEMO=true ./target/release/citadel-api
import requests
api = "http://localhost:3000"
headers = {"Authorization": "Bearer your-secret-key"}
# Encrypt
r = requests.post(f"{api}/api/keys/{dek_id}/encrypt", headers=headers, json={
"plaintext": "sensitive data",
"aad": "record-001", # binds ciphertext to this record
"context": "patient-records" # domain separation
})
blob = r.json()
# Decrypt
r = requests.post(f"{api}/api/decrypt", headers=headers, json={
"blob": blob,
"aad": "record-001",
"context": "patient-records"
})
plaintext = r.json()["plaintext"]
See citadel_example.py for a complete working example with AAD binding, key rotation, and threat-aware application behavior.
# Status
curl http://localhost:3000/api/status -H "Authorization: Bearer $KEY"
# List keys
curl http://localhost:3000/api/keys -H "Authorization: Bearer $KEY"
# Encrypt
curl -X POST http://localhost:3000/api/keys/$DEK_ID/encrypt \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"plaintext":"hello","aad":"test","context":"demo"}'
| Endpoint | Method | Scope | Description |
|---|---|---|---|
/health | GET | — | Health check |
/api/status | GET | read | Threat level, key counts |
/api/metrics | GET | read | Security metrics |
/api/keys | GET | read | List all keys |
/api/keys | POST | manage | Generate new key |
/api/keys/:id | GET | read | Get key details |
/api/keys/:id/activate | POST | manage | Activate a pending key |
/api/keys/:id/rotate | POST | manage | Rotate key (new version) |
/api/keys/:id/revoke | POST | manage | Permanently revoke key |
/api/keys/:id/destroy | POST | manage | Destroy key material |
/api/keys/:id/encrypt | POST | encrypt | Encrypt data |
/api/decrypt | POST | encrypt | Decrypt data |
/api/threat | GET | read | Threat intelligence details |
/api/policies | GET | read | Active key policies |
/api/auth/whoami | GET | read | Current API key info |
/api/auth/keys | GET | admin | List API keys |
/api/auth/keys | POST | admin | Create API key |
/api/auth/keys/:id | DELETE | admin | Revoke API key |
Root Key
└── Domain Key (per environment / business unit)
└── KEK — Key Encrypting Key (wraps DEKs)
└── DEK — Data Encrypting Key (encrypts application data)
Follows NIST SP 800-57. Each level contains the blast radius of a compromise — a leaked DEK doesn't expose other DEKs because the KEK is separate.
| Scope | Permissions |
|---|---|
read | View keys, status, metrics, threat level |
encrypt | Encrypt and decrypt data |
manage | Create, rotate, revoke, destroy keys |
admin | All of the above + manage API keys |
admin implies all other scopes. Principle of least privilege: give monitoring dashboards read, application services read + encrypt, admin tools admin.
Citadel monitors security events and automatically adjusts key policies:
| Level | Trigger | Response |
|---|---|---|
| LOW | Normal operations | Standard crypto-periods |
| GUARDED | Minor anomalies | Slightly tighter rotation |
| ELEVATED | Suspicious patterns | Compressed rotation schedules |
| HIGH | Active threat indicators | Forced rotation, reduced usage limits |
| CRITICAL | Under attack | Maximum restrictions |
Events that raise threat level: failed authentication, decryption failures, rapid access patterns, manual escalation. Score decays over time.
| Component | Algorithm | Standard |
|---|---|---|
| Key encapsulation (classical) | X25519 ECDH | RFC 7748 |
| Key encapsulation (post-quantum) | ML-KEM-768 | FIPS 203 |
| Data encryption | AES-256-GCM | NIST SP 800-38D |
| Key derivation | HKDF-SHA256 | NIST SP 800-56C |
Hybrid construction: both shared secrets are concatenated and fed through HKDF. Security holds if either X25519 or ML-KEM-768 remains secure.
version[1] || suite_kem[1] || suite_aead[1] || flags[1] || kem_ct_len[2] ||
x25519_ephemeral_pk[32] || mlkem768_ct[1088] || nonce[12] || aead_ct[variable]
Self-describing, versioned, no negotiation (prevents downgrade attacks). See SPEC.md for full specification.
subtle crate prevents timing attacksZeroizing<T>, zeroed on dropCitadel is unaudited software.
The implementation uses NIST-standardized primitives via established Rust crates (ml-kem, x25519-dalek, aes-gcm, hkdf). It does not implement any cryptographic algorithms. The value is in correct composition, not novel math.
What has been done:
What has NOT been done: