> ## 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.

# kc: ciphertext format & cryptographic spec

> The exact wire format and cryptographic construction behind KnoxCall's portable kc: ciphertext — ECDH P-256, HKDF-SHA256 with context binding, and AES-256-GCM. Published in full so your security reviewers can audit it.

# 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):

```
kc:<version>:<datatype>:<b64u(key_ref)>:<b64u(eph_pubkey)>:<b64u(iv)>:<b64u(ciphertext‖tag)>:$
```

| Segment          | Meaning                                                                                                                                        |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `version`        | Scheme version (integer). Drives crypto-agility — the construction can change under a new version without breaking stored data. Currently `1`. |
| `datatype`       | One of `s` (string), `n` (number), `b` (boolean), `j` (JSON), `x` (raw bytes). Lets decrypt round-trip the original type.                      |
| `key_ref`        | `"<tenant_id>:<app_key_id>:<key_version>"` — names the recipient keypair so decrypt needs **zero** out-of-band metadata.                       |
| `eph_pubkey`     | The ephemeral ECDH public key as a 65-byte uncompressed P-256 point (`0x04 ‖ X ‖ Y`).                                                          |
| `iv`             | 12-byte AES-GCM nonce.                                                                                                                         |
| `ciphertext‖tag` | AES-256-GCM output with the 16-byte authentication tag appended.                                                                               |
| `$`              | Terminator — guards against trailing-data and makes the string recognisable by eye and by tooling.                                             |

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:

   ```
   CEK = HKDF-SHA256(ikm = Z, salt = eph_pubkey, info = AAD, length = 32 bytes)
   ```

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:

```
info = len‖"KnoxCall-kc1-ECIES-P256-AES256GCM"
     ‖ len‖tenant_id ‖ len‖app_key_id ‖ key_version(uint32 BE)
     ‖ len‖datatype  ‖ len‖purpose
```

`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.

<Note>
  **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.
</Note>

## Key hierarchy & custody

```
platform master key  (env MASTER_KEY_B64, or unsealed from AWS/GCP/Azure KMS)
        │  wraps
        ▼
tenant master key    (per tenant; or wrapped by YOUR KMS under BYOK)
        │  wraps
        ▼
app ecdh-p256 private key   (PKCS#8 DER, stored only as a wrapped KCT1 envelope)
        │  decrypts
        ▼
kc: ciphertext
```

* 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](/essentials/tenant-kms/overview), 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

| Parameter          | Value                                                                          |
| ------------------ | ------------------------------------------------------------------------------ |
| Curve              | NIST P-256 (`prime256v1`), ephemeral per value                                 |
| KDF                | HKDF-SHA256, 32-byte output, salt = ephemeral public key, context-bound `info` |
| AEAD               | AES-256-GCM, 12-byte IV, 16-byte tag                                           |
| Header integrity   | `kc:` header bound as GCM AAD                                                  |
| Key wrapping       | KCT1 envelope (AES-256-GCM) under the per-tenant master key                    |
| Master key custody | env, or AWS KMS / GCP KMS / Azure Key Vault unseal; per-tenant BYOK            |

## 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.
