> ## Documentation Index
> Fetch the complete documentation index at: https://docs.knoxcall.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Crypto Keys overview

> Encryption-as-a-Service for your application data. Encrypt, decrypt, and rotate keys via API — without your apps ever seeing the key material.

# Crypto Keys overview

KnoxCall's **Crypto Keys** feature is encryption-as-a-service. You create a named key, then encrypt and decrypt data through KnoxCall's API. Your applications **never see the key material** — they call `/encrypt` and `/decrypt` and get back ciphertext / plaintext.

The Vault parallel: this is KnoxCall's `transit` engine.

Use it for:

* **Encrypting customer-record fields** at the application layer (PII, PHI, PCI).
* **Decoupling apps from key storage** — change the key, rotate it, destroy it without touching application code.
* **Cryptographic erasure** — destroy a key version and every piece of ciphertext under it becomes permanently unreadable. Powerful right-to-be-forgotten primitive.
* **Centralised audit** — every encrypt / decrypt call lands in the audit log.

## Quick example

```bash theme={"dark"}
# Create a named key
curl -X POST https://api.knoxcall.com/v1/crypto/keys \
  -H "Authorization: Bearer $KC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "customer-pii", "mode": "cloud-only" }'

# Encrypt some plaintext
curl -X POST https://api.knoxcall.com/v1/crypto/keys/customer-pii/encrypt \
  -H "Authorization: Bearer $KC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "plaintext": "Jane Doe, 415-555-9311" }'
# → { "ciphertext": "knoxcall:v1:G2x...", "key_version": 1 }

# Decrypt it (any version that hasn't been destroyed)
curl -X POST https://api.knoxcall.com/v1/crypto/keys/customer-pii/decrypt \
  -H "Authorization: Bearer $KC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ciphertext": "knoxcall:v1:G2x..." }'
# → { "plaintext": "Jane Doe, 415-555-9311", "plaintext_b64": "SmFuZSBEb2UsIDQxNS01NTUtOTMxMQ==", "key_version": 1 }
```

The ciphertext format is `knoxcall:v<N>:<base64-payload>` where `N` is the key version that produced it. Vault's `vault:v<N>:...` prefix is also accepted on input — you can migrate ciphertext stored under Vault's transit engine without re-encrypting.

## Algorithms

Pick a `key_type` when you create a key. The right choice depends on what you'll use it for:

| `key_type`                         | Use case                                                  | Operations                                               |
| ---------------------------------- | --------------------------------------------------------- | -------------------------------------------------------- |
| `aes256-gcm`                       | Encrypt/decrypt application data                          | `encrypt`, `decrypt`, `rewrap`                           |
| `hmac-sha256`, `hmac-sha512`       | HMAC over a payload (request signing, webhook auth)       | `hmacSign`, `hmacVerify`, `signJwt`, `verifyJwt`         |
| `rsa-2048`, `rsa-3072`, `rsa-4096` | Asymmetric signing — JWTs (RS256/PS256), document signing | `sign`, `verify`, `signJwt`, `verifyJwt`, `getPublicKey` |
| `ecdsa-p256`, `ecdsa-p384`         | Asymmetric signing — JWTs (ES256/ES384), faster than RSA  | `sign`, `verify`, `signJwt`, `verifyJwt`, `getPublicKey` |
| `ed25519`                          | Modern asymmetric signing — JWTs (EdDSA), constant-time   | `sign`, `verify`, `signJwt`, `verifyJwt`, `getPublicKey` |

**Cryptographic primitives:**

* AES-256-GCM. Ciphertext payload `iv[12] || authTag[16] || ciphertext[n]`. Format-prefix `knoxcall:v<N>:<base64>`.
* HMAC: SHA-256 (32-byte tag) or SHA-512 (64-byte tag).
* RSA: PKCS#1 v1.5 padding by default for JWT compatibility (RS256). PSS available via `rsa_padding: 'pss'` for raw signing, or `header_overrides: { alg: 'PS256' }` (/ `PS384`, `PS512`) for JWT signing.
* ECDSA: deterministic per RFC 6979.
* Ed25519: standard EdDSA.

**Public key export:** asymmetric keys expose `GET /v1/crypto/keys/{name}/public-key` returning both PEM (SPKI) and JWK. External services verify your signatures using the public key without ever needing the private side.

## JWT signing and verification

Use a key directly to sign / verify JWTs:

```bash theme={"dark"}
# Sign
curl -X POST https://api.knoxcall.com/v1/crypto/keys/jwt-signer/jwt \
  -H "Authorization: Bearer $KC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "claims": { "sub": "user-123", "exp": 9999999999 } }'
# → { "data": { "token": "eyJ...", "key_version": 1, "alg": "RS256" }, "meta": { "request_id": "..." } }

# Verify
curl -X POST https://api.knoxcall.com/v1/crypto/keys/jwt-signer/jwt/verify \
  -H "Authorization: Bearer $KC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "token": "eyJ..." }'
# → { "data": { "valid": true, "claims": { ... }, "key_version": 1, "alg": "RS256" }, "meta": { "request_id": "..." } }
```

The `alg` is derived from the key (`hmac-sha256` → `HS256`, `rsa-2048` → `RS256`, `ecdsa-p256` → `ES256`, `ed25519` → `EdDSA`). The verifier:

* **rejects `alg: 'none'`** unconditionally,
* **rejects algorithm confusion** — an `HS256` token won't verify against an RSA key,
* **binds `kid`** so the version that issued the token is the one used to verify,
* **validates standard claims** when you pass `expected` (issuer, audience, subject, expiry, clock skew).

**External verify** (the killer test): export the public key, hand it to `jsonwebtoken` in Node or `pyjwt` in Python, and verify a token KnoxCall signed. If those libraries accept it, the implementation is end-to-end correct.

## Webhook signing helper

For Stripe-format webhook signatures (v1 supports the Stripe `t=<unix>,v1=<hex>` format; additional formats are planned), use the helper:

```bash theme={"dark"}
curl -X POST https://api.knoxcall.com/v1/crypto/keys/stripe-webhook-secret/webhook-sign \
  -H "Authorization: Bearer $KC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "payload": "{\"event\":\"order.completed\"}" }'
# → { "data": { "signature_header": "t=1735000000,v1=<hex>", "timestamp_seconds": 1735000000, "key_version": 1, "format": "stripe" }, "meta": { "request_id": "..." } }
```

This is the same signing logic [Webhook Signing](/essentials/webhook-signing/overview) uses for outbound subscriptions, exposed as a primitive so you can sign payloads with full control over headers and timing.

## Modes

When you create a key, pick a **mode**:

### `cloud-only` (recommended)

Key material stays on KnoxCall's control plane. Every encrypt / decrypt is a network call.

* ✅ Strongest blast-radius story — a self-hosted agent or local app process can never leak the key.
* ⚠️ Higher latency — every operation is a round-trip to KnoxCall.
* 👍 Use for: high-value secrets (PCI cardholder data, PHI), audit-driven encryption (every call is logged), keys you might ever want to revoke.

### `bundled`

Key material is shipped to self-hosted agents in their session bundle (encrypted with the session key) so the agent can do encrypt / decrypt **locally**, without the call leaving the customer's network.

* ✅ Low-latency local encrypt / decrypt.
* ✅ Same trust model as KnoxCall's secret-injection feature today — in-memory, hour-bounded.
* ⚠️ A compromised agent process can read the in-memory key for the duration of its session.
* 👍 Use for: hot-path encryption inside a trusted self-hosted environment where round-trips would be too slow.

The mode is a one-shot decision per key — to switch, create a new key in the other mode and `rewrap` ciphertext to it.

## Operations

| Op                        | What it does                                                                               | When to use                                                |
| ------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| `encrypt`                 | Plaintext → ciphertext, tagged with current version                                        | Every encryption op                                        |
| `decrypt`                 | Ciphertext → plaintext, using whatever version the prefix names                            | Every decryption op                                        |
| `rotate`                  | Mints a new active version, retires the previous                                           | Periodic key hygiene; after a suspected compromise         |
| `rewrap`                  | Decrypts with old version + re-encrypts with active. **Plaintext never crosses the wire.** | Migrating old ciphertext to a fresh version after a rotate |
| `destroyVersion`          | Cryptographic erasure of a specific version                                                | Right-to-be-forgotten; after rewrap migration is done      |
| `hmacSign` / `hmacVerify` | Keyed HMAC over a payload                                                                  | API request signing, message authentication                |
| `sign` / `verify`         | Asymmetric sign / verify (RSA, ECDSA, Ed25519)                                             | Document signing, custom-format signing                    |
| `signJwt` / `verifyJwt`   | Issue or validate a JWT                                                                    | Service-to-service auth, customer-facing tokens            |
| `webhookSign`             | Stripe-format `t=...,v1=...` header                                                        | Outbound webhook signing primitive                         |
| `getPublicKey`            | Export the public side of an asymmetric key (PEM + JWK)                                    | Hand to external verifiers                                 |

## Versioning at a glance

```text theme={"dark"}
customer-pii (current_version = 3)
  v3  active     ← encrypt uses this
  v2  retired    ← can still decrypt v2 ciphertext
  v1  destroyed  ← v1 ciphertext is permanently unreadable
```

* **Encrypt** always uses the active version.
* **Decrypt** uses the version named in the ciphertext prefix.
* **Retired** versions stay around so legacy ciphertext keeps working.
* **Destroyed** versions are cryptographic erasure — that ciphertext is permanently unreadable. (You can't destroy the active version; rotate first.)

`deletion_allowed` is a per-key safety latch. Set it `false` on production keys to require explicitly flipping the latch before any version can be destroyed.

See [Rotation & versioning](/essentials/crypto-keys/rotation-and-versioning) for the full lifecycle.

## How the keys themselves are protected

Each version is stored wrapped with your [tenant master key](/essentials/key-management/tenant-master-key) (KCT1 envelope format). When KnoxCall needs to use a version, it unwraps it on the fly into an in-memory cache (5-minute TTL).

If you cryptographically erase your **tenant master key**, every transit key under it becomes permanently unreadable — by construction. There's no separate "delete all keys" step; the wrapping chain does it.

## Audit trail

Every encrypt / decrypt / rotate / destroy lands in the audit log under `resource_type='transit'` with the key name, version, and caller. You can answer "who decrypted PII record 12345 last Tuesday?" in two log queries.

## Plays well with...

* **Routes** — store an encrypted secret in a route header by encrypting it with a transit key and decrypting on egress.
* **Databases** — encrypt PII in your own DB schema; decrypt only on read.
* **[Vaults](/essentials/vaults/overview)** — every vault gets its own AES-256-GCM Crypto Key. Rotate the vault → rotates the underlying transit key.
* **[Webhook Signing (outbound)](/essentials/webhook-signing/overview)** — sign payloads with `hmacSign` (HMAC formats) or `sign` (RSA/ECDSA/Ed25519). `webhookSign` returns a Stripe-shaped header in one call.
* **[Inbound Webhooks](/essentials/inbound-webhooks/overview)** — point a subscription at an HMAC key here instead of pre-sharing a secret; rotation flows through the Crypto Keys lifecycle.
* **[Ephemeral Proxy](/essentials/ephemeral-proxy/overview)** — `{{ encrypted | json: ... }}` template references encrypt small JSON blobs with a Crypto Key.

## Next steps

* [Rotation & versioning →](/essentials/crypto-keys/rotation-and-versioning)
* [Tenant master key →](/essentials/key-management/tenant-master-key)
* [API reference →](/api-reference/overview)
