Quantum-resistant credential wallet for AI agents. ML-KEM-768 key encapsulation, AES-256-GCM at rest, ML-DSA signed access audit log. LangChain/AutoGen/CrewAI integrations.
Project description
PQC Agent Wallet
A quantum-resistant credential vault for AI agents. Stop scattering API keys across .env files, os.environ, and LangChain memory. This library gives each AI agent a single encrypted *.wallet file, unlocked with a passphrase or an ML-KEM-768 encapsulated key, with credentials encrypted at rest using AES-256-GCM and every access signed into a tamper-evident ML-DSA audit log. Drop-in integrations for LangChain, AutoGen, and CrewAI.
The Problem
AI agents need credentials: OPENAI_API_KEY, ANTHROPIC_API_KEY, database passwords, OAuth tokens, client certs. Today, those live in:
.envfiles on developer laptops and production containers (plaintext on disk).- Classical secret managers (Vault, AWS Secrets Manager) that protect data in transit with RSA/ECDSA - breakable by a sufficiently large quantum computer ("harvest now, decrypt later").
- Agent memory (LangChain
ChatOpenAI(api_key=...)) - held in plaintext in process RAM, accessible to every tool the agent invokes.
If any of those stores is exfiltrated today and an adversary holds it until a CRQC (cryptographically relevant quantum computer) exists, every classical-crypto-protected secret is retroactively broken.
The Solution
Each agent gets a local *.wallet file:
- Credentials encrypted with AES-256-GCM (FIPS 197 - symmetric, quantum-resistant at 128-bit Grover-adjusted security).
- Unlock key derived either from a passphrase via PBKDF2-HMAC-SHA256 (600k iterations) or encapsulated to a recipient's ML-KEM-768 public key (FIPS 203, NIST PQC).
- Wallet file signed with the owner's ML-DSA-65 key (FIPS 204) - tamper-evident at rest.
- Every
get,put,delete,unlockoperation recorded as a signed entry in an append-only audit log.
Installation
pip install pqc-agent-wallet
With LangChain helpers:
pip install "pqc-agent-wallet[langchain]"
Development:
pip install -e ".[dev]"
Quick Start
from quantumshield import AgentIdentity
from pqc_agent_wallet import Wallet
owner = AgentIdentity.create("my-agent")
# Create + populate
w = Wallet.create_with_passphrase("agent.wallet", "hunter2", owner)
w.put("openai_api_key", "sk-...", service="openai", tags=["prod"])
w.put("postgres_password", "db-pass", service="postgres", scheme="password")
w.save()
w.lock()
# Later (same process or another)...
w = Wallet.load("agent.wallet", owner)
w.unlock_with_passphrase("hunter2")
api_key = w.get("openai_api_key")
Architecture
Passphrase Wallet file (*.wallet)
---------- ----------------------
| |
| PBKDF2-HMAC-SHA256 |
| (600k iterations) |
| |
v v
32-byte key ----+ +--> [ML-DSA-65 signature]
| | over canonical
| | payload bytes
v |
[AES-256-GCM] -->|
per-credential |
(random nonce) |
|
+------------------+-----------------+
| |
v v
encrypted_credentials kdf / kem_encapsulation
{ name -> (nonce, ct, meta) } (how to re-derive the key)
|
+--> sealed, authenticated (GCM tag) and individually decryptable
Every get / put / delete / unlock --> [ML-DSA-signed audit entry]
(actor DID, ts, op, target)
For KEM-unlocked wallets, swap the passphrase branch for:
Recipient ML-KEM-768 pubkey --> encapsulate() --> (ct, symmetric key)
|
v
same AES-256-GCM path
The recipient later runs decapsulate(ct, their_priv_key) to recover the symmetric key and unlock.
Cryptography
| Layer | Primitive | Standard | Notes |
|---|---|---|---|
| Symmetric encryption | AES-256-GCM | FIPS 197 | 12-byte nonce, 16-byte GCM tag |
| Key derivation (passphrase mode) | PBKDF2-HMAC-SHA256 | RFC 8018 | 600,000 iterations (OWASP 2023) |
| Key encapsulation | ML-KEM-768 | FIPS 203 | Via QuantumShield; stub path for dev |
| Signatures (wallet + audit) | ML-DSA-65 | FIPS 204 | Via QuantumShield; Ed25519 fallback |
| Hashing | SHA3-256 | FIPS 202 | For canonical digests before signing |
AES-256-GCM is already considered quantum-resistant: Grover's algorithm halves the effective security of symmetric ciphers, so AES-256 provides ~128-bit post-quantum security - still well above the practical attack floor.
Threat Model
| Threat | Mitigation |
|---|---|
| Quantum decryption of stored secrets ("harvest now, decrypt later") | Symmetric key never travels over a classical asymmetric channel; only ever PBKDF2-derived or ML-KEM-encapsulated. |
| Wallet file tampering (attacker edits encrypted payload) | ML-DSA signature over the entire canonical payload is re-verified on every load; any mutation fails verification. |
| Wrong passphrase accepted | Unlock tries to decrypt a stored credential; GCM tag failure surfaces as InvalidPassphraseError. |
| Credential leak via logs | Credential.to_safe_dict() redacts the value. Audit log stores only the credential name, not its value. |
| Unauthorized access by another agent | Agents have separate wallets. Per-agent DID embedded in audit entries. |
| Stale credential abuse | rotate() updates the stored value and rotated_at timestamp; downstream policy can reject credentials with stale rotated_at. |
| Offline attacker with a GPU farm | PBKDF2 at 600k iterations makes brute force expensive; KEM mode removes passphrase brute force entirely. |
Integrations
LangChain (or any "callable that returns a secret" pattern)
from pqc_agent_wallet.integrations import make_langchain_secret_provider
provider = make_langchain_secret_provider(wallet)
# provider("openai_api_key") -> "sk-..."
# Or bulk-resolve env-style mapping:
from pqc_agent_wallet.integrations import walletize_env
env = walletize_env(wallet, {"OPENAI_API_KEY": "openai_api_key"})
AutoGen / CrewAI / anything that reads os.getenv
from pqc_agent_wallet.integrations import install_env_shim
install_env_shim(wallet)
# Legacy code that does os.getenv("OPENAI_API_KEY") now transparently
# falls back to wallet.get("openai_api_key") when the env var is unset.
Per-agent isolation (CrewAI pattern)
# Give every agent its own wallet; one agent's compromise doesn't leak others.
researcher_wallet = Wallet.load("researcher.wallet", researcher_identity)
writer_wallet = Wallet.load("writer.wallet", writer_identity)
API Reference
Wallet
| Method | Description |
|---|---|
Wallet.create_with_passphrase(path, passphrase, owner) |
New wallet unlocked via PBKDF2(passphrase). |
Wallet.create_with_kem(path, recipient_pubkey, alg, owner) |
New wallet unlocked via ML-KEM decapsulation. |
Wallet.load(path, owner) |
Load + verify ML-DSA signature. |
unlock_with_passphrase(p) |
Derive and validate the unlock key. |
unlock_with_kem_private_key(sk, alg) |
Decapsulate the stored ciphertext. |
lock() |
Zero the in-memory key. |
put(name, value, service=, description=, scheme=, tags=, expires_at=) |
Add or overwrite a credential. |
get(name) -> str |
Retrieve plaintext (unlocked only). |
get_credential(name) -> Credential |
Full Credential with metadata. |
delete(name) |
Remove a credential. |
rotate(name, new_value) |
Overwrite value, preserve created_at, update rotated_at. |
list_names() -> list[str] |
Sorted names. |
list_metadata() -> list[CredentialMetadata] |
All metadata. |
save() |
Sign payload with owner key, write file. |
audit |
WalletAuditLog instance. |
is_unlocked |
Bool property. |
Credential / CredentialMetadata
Dataclasses. CredentialMetadata fields: name, scheme, service, description, created_at, rotated_at, expires_at, tags. Credential.to_safe_dict() redacts the value for logging.
WalletAuditLog + WalletAuditEntry
Append-only log with ML-DSA-signed entries. Fields on each entry: timestamp, operation, actor_did, credential_name, success, details, signer_did, algorithm, signature. log.entries(limit=, operation=, credential_name=) returns the most recent matching entries. entry.verify_signature(public_key_hex) validates the ML-DSA signature.
Exceptions
| Exception | When |
|---|---|
WalletError |
Base class. |
WalletLockedError |
Operation requires unlocked wallet. |
CredentialNotFoundError |
Name not present. |
InvalidPassphraseError |
Passphrase failed GCM auth check. |
TamperedWalletError |
Wallet file signature failed verification. |
WalletFormatError |
Malformed or wrong-version wallet file. |
Examples
See the examples/ directory:
basic_usage.py- create, save, reload, read, audit.langchain_integration.py-make_langchain_secret_provider+walletize_env.env_shim_demo.py- transparentos.getenvfallback to the wallet.
Run them:
python examples/basic_usage.py
python examples/langchain_integration.py
python examples/env_shim_demo.py
Why PQC Matters For Credentials
Credential-protection systems typically rotate on multi-year cadences (annual key rotation is considered aggressive). A credential you encrypt with RSA-2048 today will be sitting on someone's disk - or in a backup bucket, or on a compromised laptop - for the entire decade-long runway to practical cryptanalytic quantum computers. ML-KEM and ML-DSA close that window now, so you never have to re-encrypt the whole corpus in a panic later. Symmetric AES-256-GCM remains safe with classical assumptions; the post-quantum concern is exclusively about how the symmetric key gets to the machine, which is exactly what ML-KEM protects.
Development
pip install -e ".[dev]"
pytest
ruff check src/ tests/ examples/
Related
Part of the QuantaMrkt post-quantum tooling registry. See also:
- QuantumShield - the underlying PQC toolkit (
AgentIdentity, ML-DSA / ML-KEM primitives). - PQC RAG Signing - sister tool for sealing RAG pipeline chunks with ML-DSA.
- PQC MCP Transport - sister tool for signing Model Context Protocol JSON-RPC messages.
License
Apache License 2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pqc_agent_wallet-0.1.0.tar.gz.
File metadata
- Download URL: pqc_agent_wallet-0.1.0.tar.gz
- Upload date:
- Size: 20.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94b5951f02bc28461c49844e1f0717723ac4fa7be2273c175e3fa1c1fe5e744b
|
|
| MD5 |
84a8f00d4f21397f826d694699b5b206
|
|
| BLAKE2b-256 |
bd9edcad1fde2b96cb7e8f0d04a7e6b9444853ac566b93138eb8ea283788838a
|
File details
Details for the file pqc_agent_wallet-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pqc_agent_wallet-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f6ff500008d25ec4bfac62877eac04fa37ef60ad1853fa4371b5cc007823e75
|
|
| MD5 |
8d26b5c1fdc0636f9345093b0e1d2245
|
|
| BLAKE2b-256 |
3e5450d20ad821b8db0a98564f5ce5df587f8589dc0b0c4bfcc609cc1cb1a683
|