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:

BackendModuleLibraryWhen 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

AlgorithmKey SizeUsePerformance
**Ed25519**256-bitSigning + certificationFast, modern, recommended
**Cv25519**256-bitEncryption subkeyPaired with Ed25519
**RSA**4096-bitSigning + encryptionSlower, 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

PropertyRequirement
Minimum length8 characters
Recommended16+ characters or passphrase (4+ words)
S2K algorithmIterated+Salted SHA256
S2K iterations65536+ (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

PropertyGuarantee
**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

FileLocationPermissionsContents
`private.asc``~/.capauth/identity/`0600ASCII-armored private key (passphrase-protected)
`public.asc``~/.capauth/identity/`0644ASCII-armored public key (shareable)
`profile.json``~/.capauth/identity/`0644Profile 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:


Test Coverage

27 pytest tests covering:

CategoryTestsWhat's Tested
Key generation4Ed25519, RSA, passphrase protection, backend selection
Sign/verify5Valid signatures, tampered data rejection, wrong key rejection
Encrypt/decrypt4Round-trip, wrong key fails, large payloads
Challenge-response5Full protocol, replay rejection, expired nonce, wrong responder
Profile lifecycle4Init, load, export, serialize
CLI commands3init, verify, export
Backend interop2PGPy-generated key verified by GnuPG and vice versa

Threat Model

ThreatMitigation
**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

StandardUsage
**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