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

# Rotation and versioning

> How crypto-key versions move from active to retired to destroyed, and the rewrap flow that lets you migrate old ciphertext without ever exposing plaintext.

# Rotation and versioning

Every crypto key in KnoxCall is **versioned**. New encryptions always use the latest active version; old ciphertext keeps decrypting against whatever version originally produced it. Rotation moves the active pointer forward without breaking anything that's already encrypted.

## The version lifecycle

```text theme={"dark"}
v1 active   ──rotate──▶  v1 retired  +  v2 active   ──rotate──▶  v2 retired + v3 active  ──destroyVersion──▶  v1 destroyed
```

Each version is in one of three states:

* **`active`** — used for new encryptions. Exactly one per key at any time.
* **`retired`** — kept around so old ciphertext under that version still decrypts. As many as you've rotated.
* **`destroyed`** — cryptographic erasure. The wrapped key bytes are wiped and the row stays as an audit marker. Ciphertext under a destroyed version is **permanently unreadable**.

## Rotating a key

```bash theme={"dark"}
curl -X POST https://api.knoxcall.com/admin/crypto/keys/customer-pii/rotate \
  -H "Authorization: Bearer $KC_ADMIN_JWT" \
  -H "X-Tenant-ID: $TENANT_ID"
# → { "new_version": 2 }
```

What happens, atomically inside a single transaction:

1. A new 256-bit key is generated and wrapped with the tenant master key.
2. The new version row is inserted as `active`.
3. The previous active version is flipped to `retired`.
4. The key's `current_version` pointer is updated.

`encrypt` calls from this point forward emit `knoxcall:v2:...` ciphertext. `decrypt` calls handle either `v1` or `v2` ciphertext seamlessly.

## When to rotate

| Reason                                           | How often                                  |
| ------------------------------------------------ | ------------------------------------------ |
| Routine hygiene (compliance)                     | Every 90 days                              |
| After a personnel change with key access         | Immediately                                |
| After a suspected compromise                     | Immediately + rewrap + destroy old version |
| Pre-emptive before destroying the active version | Always (you can't destroy `active`)        |

Rotating is cheap. Decrypting from old versions is also cheap. The only meaningful "cost" of holding many retired versions is audit-trail clutter.

## Rewrap: migrate ciphertext to a new version

Rotating doesn't re-encrypt existing data. Old ciphertext keeps decrypting against retired versions. To migrate it:

```bash theme={"dark"}
curl -X POST https://api.knoxcall.com/admin/crypto/keys/customer-pii/rewrap \
  -H "Authorization: Bearer $KC_ADMIN_JWT" \
  -H "X-Tenant-ID: $TENANT_ID" \
  -H "Content-Type: application/json" \
  -d '{ "ciphertext": "knoxcall:v1:G2x..." }'
# → { "ciphertext": "knoxcall:v2:K9p...", "key_version": 2 }
```

`rewrap` decrypts the old ciphertext with the retired version, re-encrypts with the active version, and returns the new ciphertext — **the plaintext never crosses the wire**. The operation runs entirely on KnoxCall's side.

The typical migration loop:

```text theme={"dark"}
for each row in your_table where ciphertext_version < current_version:
  new_ct = POST /admin/crypto/keys/<name>/rewrap { ciphertext: row.ct }
  UPDATE your_table SET ct = new_ct WHERE id = row.id
```

After every row is rewrapped, you can safely destroy the old version.

## Destroying a version (cryptographic erasure)

```bash theme={"dark"}
curl -X DELETE https://api.knoxcall.com/admin/crypto/keys/customer-pii/versions/1 \
  -H "Authorization: Bearer $KC_ADMIN_JWT" \
  -H "X-Tenant-ID: $TENANT_ID"
```

What this does:

1. Refuses if `deletion_allowed = false` on the parent key (the prod safety latch).
2. Refuses if the version is `active` — rotate first.
3. Wipes the wrapped key bytes from the row, flips status to `destroyed`, records who and when.

Any ciphertext that named this version in its prefix is **gone forever**. There's no off-site backup. This is the feature, not a bug — it's the right-to-be-forgotten primitive.

## `deletion_allowed`

By default, new keys ship with `deletion_allowed = false` on production tenants. Flipping it to `true` is a deliberate, audit-logged action that says "I'm about to destroy a version on purpose."

Pattern we recommend:

1. Rewrap all data to the new active version.
2. Confirm in a query that no row in your DB still has ciphertext at the version you're about to destroy.
3. Flip `deletion_allowed = true`.
4. `DELETE /versions/<old>`.
5. Flip `deletion_allowed = false` again.

## What rotation doesn't do

* It doesn't break existing decryption — old ciphertext keeps working until you destroy the old version.
* It doesn't re-encrypt existing data — that's `rewrap`.
* It doesn't change the ciphertext format — same prefix, just a higher version number.
* It doesn't propagate to bundled-mode agents instantly — they pick up the new version on their next session-bundle refresh.

## Key cache behaviour

Unwrapped key versions are cached in memory for **5 minutes** after first use. Rotation invalidates the previous active entry; destroy invalidates the destroyed entry. You don't need to worry about the cache day-to-day — it's an internal performance optimisation, not something you manage.

## Audit log markers

| Audit action                          | Means                                                |
| ------------------------------------- | ---------------------------------------------------- |
| `transit.key.create`                  | A new key was minted (version 1 active)              |
| `transit.key.rotate`                  | New version became active                            |
| `transit.encrypt` / `transit.decrypt` | Single op (records key version, not plaintext)       |
| `transit.rewrap`                      | Ciphertext was rewrapped from one version to another |
| `transit.key.destroy_version`         | Version was cryptographically erased                 |

## See also

* [Crypto Keys overview →](/essentials/crypto-keys/crypto-keys-overview)
* [Tenant master key →](/essentials/key-management/tenant-master-key) — the wrap chain that protects every transit key
