Skip to main content

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

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

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

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:
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:
After every row is rewrapped, you can safely destroy the old version.

Destroying a version (cryptographic erasure)

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

See also