---
title: "Seal data and erase a person with keys"
description: "Seal a value with a per-user key held in your cloud KMS, open it with an audited purpose, rotate keys, and destroy one person's key to erase them everywhere."
section: "guides"
status: "Building for 0.3"
spec: "keys"
canonical: "https://mainsdk.dev/docs/guides/erase-a-user-with-keys"
updated: "2026-10-28"
---

# Seal data and erase a person with keys

> Seal a value with a per-user key held in your cloud KMS, open it with an audited purpose, rotate keys, and destroy one person's key to erase them everywhere.

Status: Building for 0.3 · Canonical: https://mainsdk.dev/docs/guides/erase-a-user-with-keys · Updated: 2026-10-28

You will seal a note for one user, open it, then destroy that user's key so the note can never be read again. It takes about 20 minutes. Most apps never call `keys` directly: a `db` field with `encrypt: true` uses it for you. Use it when you seal a value yourself or need crypto-erasure.

## You need

- A MainSDK app with `auth`.
- For production, a KMS key per region. In development the `local` driver needs no account.

## 1. Point `keys` at the KMS

```yaml title="main.yaml"
keys:
  driver: aws-kms
  key:
    us: arn:aws:kms:us-east-1:111122223333:alias/pinecart-us
    eu: arn:aws:kms:eu-west-1:111122223333:alias/pinecart-eu
  per-user: true
  rotate: 365d
```

The `local` driver is refused in production with `MAIN-KEYS-0001`.

## 2. Seal a value

```ts title="app/lib/notes.ts"
import { keys } from "mainsdk";

export async function sealNote(note: string, userId: string) {
  return keys.encrypt(note, { owner: userId, region: "eu", label: "phi" });
}
```

The label is one of `personal`, `sensitive`, `child` or `phi`.

## 3. Open it with a purpose

```ts title="app/lib/open-note.ts"
import { keys } from "mainsdk";
import type { Sealed } from "mainsdk/keys";

export async function openNote(sealed: Sealed) {
  return keys.decrypt(sealed, { purpose: "show note to owner" });   // Uint8Array
}
```

The purpose and key id go to the audit log.

## 4. Rotate

```ts title="server/rotate.ts"
import { keys } from "mainsdk";
import type { Sealed } from "mainsdk/keys";

export async function rotate(sealed: Sealed) {
  const report = await keys.rotate();                 // or keys.rotate({ keyId: "user:u1" })
  const moved = await keys.rewrap(sealed, { purpose: "move to the new key version" });
  return { report, moved };
}
```

Old data still decrypts after a rotation. `rewrap` wraps the data key again and leaves the ciphertext alone.

## 5. Erase a person

```ts title="app/lib/erase.ts"
import { keys } from "mainsdk";

export async function erase(id: string) {
  return keys.destroyUser(id);   // { keyId, scope, destroyedAt, versions }
}
```

## 6. Test it

```ts title="app/lib/notes.test.ts"
import { it } from "node:test";
import assert from "node:assert/strict";
import { createTestApp } from "@mainsdk/testing";
import { keys } from "mainsdk";

it("keys AC3 makes a destroyed user's data unreadable", async () => {
  createTestApp({ yaml: "schema: 1\nproject: prj_test\nkeys:\n  driver: local\n  per-user: true\n" });
  const sealed = await keys.encrypt("note", { owner: "u1", region: "eu", label: "phi" });
  await keys.destroyUser("u1");
  await assert.rejects(keys.decrypt(sealed, { purpose: "test" }), { code: "MAIN-KEYS-0004" });
});
```

## Check it worked

The test passes, and reading the note after `destroyUser` fails with `MAIN-KEYS-0004`.

## Troubleshooting

- `MAIN-KEYS-0003`: a bad region, no KMS key for the region, or `decrypt` without a `purpose`.
- `MAIN-KEYS-0005`: the key service did not answer. Check the app may use the KMS key, then retry.
- `MAIN-KEYS-0006`: the sealed value failed its integrity check. It was changed or belongs to another key.

See the [keys interface](https://mainsdk.dev/docs/interfaces/keys).
