Skip to main content

kc: ciphertext format & cryptographic spec

We publish the exact construction of the kc: ciphertext — algorithms, parameters, and key hierarchy — so that a security reviewer can evaluate it without a sales call. Specificity is the point: you should be able to verify what we built, not take our word for it.

Wire format

A kc: value is a single ASCII string. Each variable segment is base64url (no padding):
The leading kc: and trailing :$ make detection cheap and unambiguous (POST /v1/decrypt passes non-kc: values through untouched on this basis).

Cryptographic construction (version 1)

Per-value ECIES on the NIST P-256 curve:
  1. Generate an ephemeral P-256 keypair for this value.
  2. Compute the ECDH shared secret Z between the ephemeral private key and the recipient (tenant app) public key.
  3. Derive the content-encryption key:
  4. Encrypt with AES-256-GCM (12-byte random IV, 16-byte tag). The kc: header (version, datatype, key_ref, eph_pubkey, iv) is bound as the GCM additional authenticated data, so no header segment can be swapped without failing the tag.

Context binding (the AAD / HKDF info)

The HKDF info is a length-prefixed, canonical concatenation of a domain-separation label and the full context:
purpose is the optional data-role (pci, eu, …). Because the full context is folded into key derivation, a ciphertext is cryptographically pinned to the tenant, key, version, datatype, and data-role it was created under — decrypting under any different context simply fails the GCM tag.
How this differs from raw-ECDH designs. Some implementations feed the raw ECDH shared secret straight into AES with no KDF. KnoxCall always runs HKDF-SHA256 and binds the context into the info parameter. This gives domain separation, defends against cross-context reuse, and means a stolen ciphertext cannot be replayed under a different tenant, key, or role.

Key hierarchy & custody

  • The app private key is never stored in the clear — only as a KCT1 envelope wrapped under the tenant master key. The public half is cached for encryption.
  • Under BYOK, the tenant master key is wrapped by your cloud KMS, so KnoxCall cannot unwrap it without a call your KMS policy authorises — and you can revoke that at any time.
  • Cryptographic erasure: destroy a key version and every kc: value produced under it becomes permanently unreadable.

Versioning & rotation

Every kc: value names its key_version in the key_ref. Rotating an ecdh-p256 key mints a new version: new values encrypt under the new version while existing values still decrypt under theirs. No bulk re-encryption is required, and a long-running re-encrypt can pin a target version to stay consistent across a concurrent rotation.

Parameter summary

Migrating from Evervault ev:

KnoxCall cannot decrypt an Evervault ev: ciphertext (only your Evervault app holds that key). Migration is a one-way re-encryption you run with scripts/migrate-ev-to-kc.ts: decrypt each ev: value via your Evervault app, then re-encrypt via POST /v1/encrypt to get a kc: value.