Skip to main content

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

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

# Create a named key
curl -X POST https://api.knoxcall.com/admin/crypto/keys \
  -H "Authorization: Bearer $KC_ADMIN_JWT" \
  -H "X-Tenant-ID: $TENANT_ID" \
  -H "Content-Type: application/json" \
  -d '{ "name": "customer-pii", "mode": "cloud-only" }'

# Encrypt some plaintext
curl -X POST https://api.knoxcall.com/admin/crypto/keys/customer-pii/encrypt \
  -H "Authorization: Bearer $KC_ADMIN_JWT" \
  -H "X-Tenant-ID: $TENANT_ID" \
  -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/admin/crypto/keys/customer-pii/decrypt \
  -H "Authorization: Bearer $KC_ADMIN_JWT" \
  -H "X-Tenant-ID: $TENANT_ID" \
  -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_typeUse caseOperations
aes256-gcmEncrypt/decrypt application dataencrypt, decrypt, rewrap
hmac-sha256, hmac-sha512HMAC over a payload (request signing, webhook auth)hmacSign, hmacVerify, signJwt, verifyJwt
rsa-2048, rsa-3072, rsa-4096Asymmetric signing — JWTs (RS256/PS256), document signingsign, verify, signJwt, verifyJwt, getPublicKey
ecdsa-p256, ecdsa-p384Asymmetric signing — JWTs (ES256/ES384), faster than RSAsign, verify, signJwt, verifyJwt, getPublicKey
ed25519Modern asymmetric signing — JWTs (EdDSA), constant-timesign, 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:
# 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-sha256HS256, rsa-2048RS256, ecdsa-p256ES256, ed25519EdDSA). 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:
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 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: 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

OpWhat it doesWhen to use
encryptPlaintext → ciphertext, tagged with current versionEvery encryption op
decryptCiphertext → plaintext, using whatever version the prefix namesEvery decryption op
rotateMints a new active version, retires the previousPeriodic key hygiene; after a suspected compromise
rewrapDecrypts with old version + re-encrypts with active. Plaintext never crosses the wire.Migrating old ciphertext to a fresh version after a rotate
destroyVersionCryptographic erasure of a specific versionRight-to-be-forgotten; after rewrap migration is done
hmacSign / hmacVerifyKeyed HMAC over a payloadAPI request signing, message authentication
sign / verifyAsymmetric sign / verify (RSA, ECDSA, Ed25519)Document signing, custom-format signing
signJwt / verifyJwtIssue or validate a JWTService-to-service auth, customer-facing tokens
webhookSignStripe-format t=...,v1=... headerOutbound webhook signing primitive
getPublicKeyExport the public side of an asymmetric key (PEM + JWK)Hand to external verifiers

Versioning at a glance

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 for the full lifecycle.

How the keys themselves are protected

Each version is stored wrapped with your 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 — every vault gets its own AES-256-GCM Crypto Key. Rotate the vault → rotates the underlying transit key.
  • Webhook Signing (outbound) — sign payloads with hmacSign (HMAC formats) or sign (RSA/ECDSA/Ed25519). webhookSign returns a Stripe-shaped header in one call.
  • Inbound Webhooks — point a subscription at an HMAC key here instead of pre-sharing a secret; rotation flows through the Crypto Keys lifecycle.
  • Ephemeral Proxy{{ encrypted | json: ... }} template references encrypt small JSON blobs with a Crypto Key.

Next steps