---
title: Encryption & cryptographic erasure
path: concepts/encryption-and-erasure
status: published
---

# Encryption & cryptographic erasure

ScaiLog encrypts each PI field under a key that belongs to one data subject, so
you can erase that subject's data everywhere at once by destroying a single key.
This page explains the key hierarchy, how a field is encrypted with AES-256-GCM,
where the keys live, and how Article 17 erasure works as cryptographic shredding.

## The key hierarchy

Keys form a three-level tree. Each level wraps (encrypts) the level below it, and
only the root ever lives outside the key store.

```text
  KRoot (deployment root)          — held in the keystore
    └─ tenant KEK  (per tenant)    — wrapped by KRoot, stored in keymap.db
         └─ subject DEK (per subject) — wrapped by the tenant KEK, stored in keymap.db
```

- **KRoot** is the deployment root key, supplied by the passphrase-protected
  keystore (optionally custodied by ScaiVault as a plugin, but never as a runtime
  dependency).
- A **tenant KEK** (key-encryption key) is minted on first use for each tenant,
  wrapped under KRoot, and stored in `keymap.db`.
- A **subject DEK** (data-encryption key) is minted the first time a given data
  subject appears, wrapped under its tenant's KEK, and stored in `keymap.db`.

Unwrapped KEKs and DEKs are cached only in memory — they are never written to
disk in the clear. Actual PI field values are encrypted directly under the
subject DEK.

## AES-256-GCM field encryption

Every wrap and every field encryption is AES-256-GCM with a fresh random 96-bit
nonce and a 16-byte authentication tag appended to the ciphertext. Each
encryption also binds a canonical **AAD** (additional authenticated data) string
to the ciphertext, so a value can only ever be decrypted in the exact context it
was encrypted in. Change any part of the context and decryption fails.

The AAD strings are fixed and versioned:

| Operation | AAD |
|-----------|-----|
| Wrap a tenant KEK under KRoot | `scailog.kekwrap.v1\|tenant=<tenant>` |
| Wrap a subject DEK under the tenant KEK | `scailog.dekwrap.v1\|tenant=<tenant>\|subject=<subject>` |
| Encrypt a PI field | `scailog.field.v1\|tenant=<tenant>\|subject=<subject>\|site=<call_site_id>\|field=<name>` |

Because the field AAD includes the tenant, subject, call site, and field name, a
ciphertext cannot be silently moved to another field or subject — the tag will
not verify. Each stored field is packed as `{"n": <b64 nonce>, "c": <b64
ciphertext‖tag>}`, and all encrypted fields for an entry are collected into the
opaque `pi_fields` blob. These AAD strings are normative test vectors; they do
not change.

## keymap.db is separate

Wrapped key material lives in its own SQLite file, `keymap.db`, kept separate
from the main `scailog.db` store. That separation is deliberate: the store holds
ciphertext and the keymap holds the keys that could decrypt it, and the keymap
gets short backup retention so destroyed keys do not linger in backups. The
crypto-shred marker (a subject's `destroyed_at`) lives in this shared keymap, so
every worker sees an erased subject authoritatively.

## Cryptographic erasure (Article 17)

Erasure in ScaiLog is cryptographic: to fulfil a subject's right to be
forgotten, ScaiLog **destroys the subject's DEK**. Once the DEK is gone, every
PI field ever encrypted under it — across every host, every backup, every
replica — is permanently unrecoverable, because the ciphertext is meaningless
without the key. There is no need to hunt down and overwrite individual rows;
the key was the single point of decryptability.

Requesting a new DEK for an erased subject is refused, so no key can be re-minted.
If new PI arrives for an already-erased subject, the pipeline stores it PI-free
(redacted) rather than mint a fresh key.

The erasure job does three things:

1. **Crypto-shred** — zero and mark the subject DEK in `keymap.db`, recording a
   `destroyed_at` timestamp.
2. **Tombstone** affected entries — in tombstone mode the entries are marked
   erased (and their now-undecryptable ciphertext scrubbed); a delete mode
   removes them outright.
3. **Sign a receipt** — write an Ed25519-signed erasure receipt recording the
   subject, timestamp, and how many entries were tombstoned.

Erasure is idempotent: re-running against an already-erased subject returns the
original receipt rather than issuing a fresh zero-count one. Crypto-shredding
ships in Phase 1 — this is design invariant **P5**.

## Signed receipts and the rotating keyring

DSAR reports and erasure receipts are signed with Ed25519 so an offline verifier
can confirm they are genuine. Signing keys rotate without breaking old
documents: the report keyring keeps one **active** key for new signatures plus
any number of **retired** keys kept only for verification. Every signature
carries the signing key's `key_id` (a fingerprint of its public key), and the
full set of public keys — active and retired — stays published (served at
`GET /v1/report-keys`) so a document signed by a since-retired key remains
verifiable forever.
