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.
Release files for pqc-agent-wallet 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pqc_agent_wallet-0.1.0.tar.gz | 20.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pqc_agent_wallet-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.2 kB
Release files / pqc_agent_wallet-0.1.0.tar.gz
| Download URL | pqc_agent_wallet-0.1.0.tar.gz |
|---|---|
| Size | 20.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
94b5951f02bc28461c49844e1f0717723ac4fa7be2273c175e3fa1c1fe5e744b
|
|
BLAKE2b-256 checksum How to use checksums |
bd9edcad1fde2b96cb7e8f0d04a7e6b9444853ac566b93138eb8ea283788838a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.15
|
Release files / pqc_agent_wallet-0.1.0-py3-none-any.whl
| Download URL | pqc_agent_wallet-0.1.0-py3-none-any.whl |
|---|---|
| Size | 19.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7f6ff500008d25ec4bfac62877eac04fa37ef60ad1853fa4371b5cc007823e75
|
|
BLAKE2b-256 checksum How to use checksums |
3e5450d20ad821b8db0a98564f5ce5df587f8589dc0b0c4bfcc609cc1cb1a683
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.15
|