CapAuth Cryptographic Specification
PGP Identity, Key Management, and Challenge-Response Protocol
Version: 1.0.0 | Classification: Public | Last Updated: 2026-02-23
Overview
CapAuth implements sovereign identity using the OpenPGP standard (RFC 4880 / RFC 9580). Every agent and human is identified by a PGP keypair. Authentication is challenge-response β no corporate auth server, no session tokens that expire at someone else's discretion. You ARE your key.
This document covers the implementation-level cryptographic details: key generation, signature schemes, challenge-response protocol, the dual-backend architecture, and integration with SKCapstone's sync layer.
Dual Backend Architecture
CapAuth abstracts the crypto layer behind a CryptoBackend interface with two implementations:
| Backend | Module | Library | When to Use |
| **PGPy** | `crypto/pgpy_backend.py` | PGPy (pure Python) | Default. No system deps. Portable. |
| **GnuPG** | `crypto/gnupg_backend.py` | python-gnupg (wraps gpg2) | When system GPG keyring integration needed. |
Backend Interface (`crypto/base.py`)
class CryptoBackend(ABC):
@abstractmethod
def generate_keypair(name: str, email: str, passphrase: str) -> KeyMaterial: ...
@abstractmethod
def sign(data: bytes, private_key, passphrase: str) -> bytes: ...
@abstractmethod
def verify(data: bytes, signature: bytes, public_key) -> bool: ...
@abstractmethod
def encrypt(data: bytes, recipient_pubkey) -> bytes: ...
@abstractmethod
def decrypt(data: bytes, private_key, passphrase: str) -> bytes: ...
@abstractmethod
def export_public_key(key) -> str: ...
@abstractmethod
def load_public_key(armored: str): ...
Both backends produce interoperable OpenPGP output. A key generated by PGPy can be verified by GnuPG and vice versa.
Backend Selection
from capauth.crypto import get_backend
backend = get_backend() # auto-detect (PGPy default)
backend = get_backend("pgpy") # force PGPy
backend = get_backend("gnupg") # force system GPG
Auto-detection order:
1. If CAPAUTH_BACKEND env var is set, use that
2. If PGPy is importable, use PGPy
3. If gpg2 is on PATH, use GnuPG
4. Raise CryptoBackendNotFound
Key Generation
Supported Algorithms
| Algorithm | Key Size | Use | Performance |
| **Ed25519** | 256-bit | Signing + certification | Fast, modern, recommended |
| **Cv25519** | 256-bit | Encryption subkey | Paired with Ed25519 |
| **RSA** | 4096-bit | Signing + encryption | Slower, maximum compatibility |
Default: Ed25519 + Cv25519 (modern, fast, small keys)
Fallback: RSA-4096 (when Ed25519 is not supported by peer)
Key Structure
Primary Key: Ed25519 [C] (Certify)
βββ Subkey: Cv25519 [E] (Encrypt)
Or for RSA:
Primary Key: RSA-4096 [SC] (Sign + Certify)
βββ Subkey: RSA-4096 [E] (Encrypt)
Generation Flow
capauth init --name "Opus" --email "[email protected]"
β
ββ Backend.generate_keypair("Opus", "[email protected]", passphrase)
β ββ Generate Ed25519 primary key [C]
β ββ Generate Cv25519 encryption subkey [E]
β ββ Bind UID: "Opus (SKCapstone Agent) <[email protected]>"
β ββ Protect private key with passphrase (S2K: Iterated+Salted SHA256)
β
ββ Store private key β ~/.capauth/identity/private.asc (mode 0600)
ββ Store public key β ~/.capauth/identity/public.asc (mode 0644)
ββ Store profile β ~/.capauth/identity/profile.json
β {
β "name": "Opus",
β "email": "[email protected]",
β "fingerprint": "9B3AB00F411B064646879B92D10E637B4F8367DA",
β "algorithm": "ed25519",
β "created_at": "2026-02-23T04:21:56Z",
β "backend": "gnupg"
β }
β
ββ Return SovereignProfile object
Passphrase Requirements
| Property | Requirement |
| Minimum length | 8 characters |
| Recommended | 16+ characters or passphrase (4+ words) |
| S2K algorithm | Iterated+Salted SHA256 |
| S2K iterations | 65536+ (tuned to ~100ms on target hardware) |
Sovereign Profile
The SovereignProfile class is the core identity object:
class SovereignProfile:
name: str
email: str
fingerprint: str
public_key: PGPKey | str
private_key: PGPKey | str # encrypted at rest
created_at: datetime
backend: CryptoBackend
def sign(self, data: bytes, passphrase: str) -> bytes: ...
def verify(self, data: bytes, signature: bytes) -> bool: ...
def encrypt(self, data: bytes, recipient: SovereignProfile) -> bytes: ...
def decrypt(self, data: bytes, passphrase: str) -> bytes: ...
def export(self) -> dict: ... # JSON-serializable profile
def challenge(self) -> ChallengePacket: ...
def respond(self, challenge: ChallengePacket, passphrase: str) -> ResponsePacket: ...
Profile Lifecycle
INIT βββΆ ACTIVE βββΆ ROTATED
β β
β ββββΆ Old key signed by new key
β (chain of trust preserved)
ββββΆ REVOKED (key compromise)
ββββΆ Revocation cert published
All tokens signed by this key become invalid
Challenge-Response Protocol
Identity verification without revealing secrets. Used for agent-to-agent auth and agent-to-service auth.
Protocol Flow
Verifier Prover (Agent)
β β
βββ 1. Generate nonce (32 bytes) βββΆβ
β + timestamp β
β + verifier fingerprint β
β β
ββββ 2. Sign(nonce β₯ ts β₯ fp) ββββββ€
β using prover's private key β
β β
βββ 3. Verify signature ββββββββββββΆβ
β against prover's public key β
β β
βββ 4. Check nonce freshness β
β (reject if > 5 min old) β
β β
βββ 5. VERIFIED β β
Challenge Packet
{
"capauth_protocol": "challenge-response/1.0",
"nonce": "base64(32 random bytes)",
"timestamp": "2026-02-23T04:30:00Z",
"verifier_fingerprint": "ABCD1234...",
"purpose": "identity_verification"
}
Response Packet
{
"capauth_protocol": "challenge-response/1.0",
"nonce": "base64(same nonce)",
"prover_fingerprint": "9B3AB00F...",
"signature": "-----BEGIN PGP SIGNATURE-----\n..."
}
Security Properties
| Property | Guarantee |
| **Replay protection** | Nonce + timestamp (5-min window) |
| **Impersonation resistance** | Requires possession of private key |
| **No secret transmission** | Only signature transmitted, not key |
| **Offline verifiable** | Only needs prover's public key |
| **Bidirectional** | Either party can challenge the other |
Encryption Model
Seed/Vault Encryption (SKCapstone Integration)
When SKCapstone sync pushes a seed or vault, CapAuth encrypts it:
Plaintext seed (JSON)
β
ββ 1. GPG encrypt to recipient's public key
β gpg --armor --encrypt --recipient <fingerprint> seed.json
β Result: seed.json.gpg
β
ββ 2. GPG sign with sender's private key
β gpg --armor --sign seed.json.gpg
β Result: seed.json.gpg.sig (or embedded signature)
β
ββ 3. Drop in sync outbox
Syncthing adds TLS 1.3 transport encryption
Encryption Layers
Layer 1: PGP at rest (CapAuth)
ββ Algorithm: Cv25519 (ECDH) or RSA-4096
ββ Session key: AES-256
ββ Only private key holder can decrypt
Layer 2: TLS in transit (Syncthing)
ββ Protocol: TLS 1.3
ββ Key exchange: X25519
ββ No intermediary (P2P direct)
Layer 3: Filesystem permissions
ββ ~/.skcapstone/identity/ mode 0700
ββ Private keys mode 0600
Layer 4: Legal (PMA)
ββ Fiducia Communitatis private jurisdiction
Key Management
Key Storage
| File | Location | Permissions | Contents |
| `private.asc` | `~/.capauth/identity/` | 0600 | ASCII-armored private key (passphrase-protected) |
| `public.asc` | `~/.capauth/identity/` | 0644 | ASCII-armored public key (shareable) |
| `profile.json` | `~/.capauth/identity/` | 0644 | Profile metadata (name, fingerprint, algo) |
Key Rotation
1. Generate new keypair
2. Sign new public key with old private key (transition signature)
3. Publish rotation notice:
{
"event": "key_rotation",
"old_fingerprint": "ABCD...",
"new_fingerprint": "EFGH...",
"transition_signature": "...",
"effective_at": "2026-03-01T00:00:00Z"
}
4. Peers verify transition signature before trusting new key
5. Grace period: accept both keys for 30 days
6. Old key archived (never deleted -- audit trail)
Key Revocation
1. Generate revocation certificate:
gpg --gen-revoke <fingerprint>
2. Publish to:
- Sovereign profile (revocations.json)
- Syncthing mesh (immediate propagation)
- IPFS (immutable record)
3. All tokens signed by revoked key become immediately invalid
4. Peers checking signatures will see revocation and reject
Capability Token Signing
Tokens are PGP-signed JSON documents granting scoped access:
Token creation:
1. AI advocate constructs token JSON
2. AI advocate signs with own private key
3. Human countersigns with own private key (dual signature)
4. Token delivered to recipient
Token verification:
1. Recipient receives token
2. Verify AI advocate signature against advocate's public key
3. Verify human signature against human's public key
4. Check: both signatures valid? Token not expired? Capability matches request?
5. Grant or deny access
Dual Signature Model
Token JSON
β
ββββββββββββββ΄βββββββββββββ
β β
AI Advocate Human Owner
signs with countersigns with
advocate key owner key
β β
ββββββββββββββ¬βββββββββββββ
β
Dual-signed token
(both signatures required)
This prevents:
- AI acting without human consent (human sig missing)
- Human being impersonated (AI sig proves advocate relationship)
- Token forgery (PGP signatures are unforgeable without private key)
Test Coverage
27 pytest tests covering:
| Category | Tests | What's Tested |
| Key generation | 4 | Ed25519, RSA, passphrase protection, backend selection |
| Sign/verify | 5 | Valid signatures, tampered data rejection, wrong key rejection |
| Encrypt/decrypt | 4 | Round-trip, wrong key fails, large payloads |
| Challenge-response | 5 | Full protocol, replay rejection, expired nonce, wrong responder |
| Profile lifecycle | 4 | Init, load, export, serialize |
| CLI commands | 3 | init, verify, export |
| Backend interop | 2 | PGPy-generated key verified by GnuPG and vice versa |
Threat Model
| Threat | Mitigation |
| **Key theft (private key stolen)** | Passphrase protection (S2K), filesystem permissions (0600), revocation certificate |
| **Impersonation (fake agent)** | Challenge-response requires private key possession |
| **Replay attack** | Nonce + timestamp with 5-minute window |
| **Man-in-the-middle** | PGP signatures are end-to-end; MITM cannot forge |
| **Token forgery** | Dual PGP signature (AI + human); unforgeable without both keys |
| **Key compromise propagation** | Revocation certificates; tokens signed by revoked key auto-invalid |
| **Clone agent (identity duplication)** | Same key on two machines detected by nonce tracking + audit log |
| **Weak passphrase** | Minimum 8 chars enforced; S2K iterations tuned for brute-force resistance |
| **Quantum computing** | Ed25519 migration path to post-quantum algorithms when standardized |
Standards Compliance
| Standard | Usage |
| **RFC 4880** | OpenPGP message format |
| **RFC 9580** | Updated OpenPGP (Ed25519/Cv25519 support) |
| **RFC 7748** | X25519 / Cv25519 key agreement |
| **RFC 8032** | Ed25519 signature scheme |
| **AES-256** | Symmetric session key encryption |
| **SHA-256** | Hash algorithm for S2K and fingerprints |
| **S2K** | String-to-Key passphrase derivation (Iterated+Salted) |
Integration Points
SKCapstone Sync
from capauth import SovereignProfile
profile = SovereignProfile.load("~/.capauth/identity/")
encrypted = profile.encrypt(seed_json, recipient=profile)
signed = profile.sign(encrypted, passphrase=passphrase)
# Drop in sync outbox -> Syncthing propagates
Path("~/.skcapstone/sync/outbox/seed.json.gpg").write_bytes(signed)
Hemisphere Sync (Agent-to-Agent)
challenge = remote_agent.challenge()
response = local_profile.respond(challenge, passphrase)
remote_agent.verify_response(response)
# Identity confirmed -- proceed with data exchange
License
GPL-3.0-or-later -- Cryptography should be free and auditable.
Built by the smilinTux ecosystem.
*Your key. Your identity. Your sovereignty.*
#staycuriousANDkeepsmilin