---
title: "AWS KMS"
description: "Use AWS KMS for MainSDK encryption keys - one wrapping key per region, IAM permissions, rotation, main.yaml, local testing and swapping."
section: "integrations"
canonical: "https://mainsdk.dev/docs/integrations/aws-kms"
updated: "2026-10-28"
---

# AWS KMS

> Use AWS KMS for MainSDK encryption keys - one wrapping key per region, IAM permissions, rotation, main.yaml, local testing and swapping.

Canonical: https://mainsdk.dev/docs/integrations/aws-kms · Updated: 2026-10-28

AWS Key Management Service (KMS) is the default driver behind [`keys`](https://mainsdk.dev/docs/interfaces/keys). MainSDK encrypts each record or file with its own data key, and AWS KMS wraps that data key with a key that never leaves KMS. You give MainSDK one KMS key per region, so a record is always wrapped by its owner's region key. Pick it when your app runs on AWS, or when you have no reason to pick another key service.

## Interfaces it serves

### Encryption keys (`keys`)

- Status: Building for 0.3 · ships as a beta API
- Driver: `aws-kms` (AWS KMS, default)
- Docs: https://mainsdk.dev/docs/interfaces/keys.md

```yaml
keys:
  driver: aws-kms
```

Swap to another provider: `gcp-kms`, `azure-keyvault`, `vault-transit`, `local`.

## Before you start

You need an AWS account with KMS available in the regions you serve.

1. **Create one symmetric encryption key per MainSDK region.** KMS keys are regional. The catalog example uses `us-east-1` for US, `eu-west-1` for EU and `ap-south-1` for India; use the regions your app and data live in. An alias per key (such as `alias/pinecart-eu`) keeps `main.yaml` readable.
2. **Give your app an IAM role.** Prefer workload identity: the role your compute already assumes. KMS credentials must come from workload identity or the `secrets` driver, never from code.
3. **Grant only what envelope encryption needs.** Allow the role to generate and decrypt data keys with these keys, in the key policy or an IAM policy (in AWS terms, actions such as `kms:GenerateDataKey` and `kms:Decrypt`). The exact action list the driver needs is not specified yet [TBD]. Keep key administration (scheduling deletion, changing policy) with a separate admin role.
4. **Decide on rotation.** MainSDK rotates on the `rotate` period you set. AWS KMS can also rotate key material on its own schedule. How the two interact is not specified yet [TBD]; avoid turning both on until it is.

## Configure

```yaml title="main.yaml"
keys:
  driver: aws-kms
  key:                       # one wrapping key per region
    us: arn:aws:kms:us-east-1:111122223333:alias/pinecart-us
    eu: arn:aws:kms:eu-west-1:111122223333:alias/pinecart-eu
    in: arn:aws:kms:ap-south-1:111122223333:alias/pinecart-in
  per-user: true             # a key per person, for crypto-erasure
  rotate: 365d               # automatic rotation period
```

Regions are `us`, `eu` and `in`. Keep the local driver for development in an overlay; `mainsdk check` refuses it in production with `MAIN-KEYS-0001`.

```yaml title="main.env.dev.yaml"
keys:
  driver: local
```

## Use it

Most of the time you don't call `keys` directly: `db` fields with `encrypt: true`, `files` and `crypto` use it for you. To seal and open a value yourself:

```ts title="app/api/notes/route.ts"
import { auth, keys } from "mainsdk";

export async function POST(req: Request) {
  const session = await auth.require(req);
  const { note } = await req.json();
  const sealed = await keys.encrypt(note, { owner: session.user, region: "eu", label: "phi" });
  // { ciphertext, wrappedKey, keyId, alg: "AES-256-GCM" }
  const plain = await keys.decrypt(sealed, { purpose: "echo note to owner" });
  return Response.json({ ok: plain.byteLength > 0 });
}
```

Erase a person everywhere, backups included, with `keys.destroyUser(user)` when `per-user` is on. Rotate on demand with `keys.rotate()` or `keys.rotate({ keyId })`.

## Local development and testing

- **No AWS account:** the `local` driver seals and opens values on your machine, for development only.
- **KMS without AWS:** LocalStack emulates KMS, and MainSDK's keys conformance suite runs the `aws-kms` driver against it.
- **Tests:** `createTestApp()` from `@mainsdk/testing` runs `keys` on its in-memory fake, and `chaos.on("keys", { outage: true })` simulates KMS being unreachable.

```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({ main: "./main.yaml" });
  const user = { id: "u1" };
  const sealed = await keys.encrypt("note", { owner: user, region: "eu", label: "phi" });
  await keys.destroyUser(user);
  await assert.rejects(keys.decrypt(sealed, { purpose: "test" }), { code: "MAIN-KEYS-0004" });
});
```

## What MainSDK adds on top

- A data key per record (AES-256-GCM or XChaCha20-Poly1305), wrapped by a KMS key that never leaves KMS.
- One wrapping key per region, chosen from the record owner's region.
- Every wrap, unwrap, destroy and rotate written to the audit log with key ID, purpose and caller, never the key material.
- Automatic rotation with old data still readable, rewrapped on its next write or by a background job.
- Per-user keys, so `keys.destroyUser()` erases a person's data everywhere.

See [Encryption keys](https://mainsdk.dev/docs/interfaces/keys) for the full contract.

## Limits and notes

- The driver comes with the keys spec (phase 1) and is the default for `keys`.
- A KMS key belongs to one AWS region. Each MainSDK region needs its own key, in an AWS region that fits your data residency.
- Sealing with the label `pan` is refused, because card numbers are never stored.
- Hardware security modules (including AWS CloudHSM) and customer-held keys through AWS XKS come with the hsm-keys spec in phase 3.
- `keys.raw()` returns the KMS client for features `keys` does not wrap. `mainsdk check` lists every use.
- On the Fleet plan, MainSDK Cloud can rotate or revoke a key everywhere during an incident; everything above is free in your app.

## Swap to or from it

```yaml title="main.yaml"
keys:
  driver: gcp-kms   # was aws-kms; point key.us, key.eu and key.in at the new service's keys
```

> **Warning:** Keys are stateful. Data keys sealed under AWS KMS still need AWS KMS to unwrap, so plan the move as a rewrap of existing data keys rather than a switch, and keep the old keys enabled until it is done. A guided migration between KMS drivers is not specified yet.

## FAQ

**Can I use one multi-region key for every region?** MainSDK asks for one wrapping key per region, set under `key.us`, `key.eu` and `key.in`, so each record stays tied to its owner's region.

**Does destroying a user's key delete a KMS key?** `keys.destroyUser()` returns a receipt with the destroyed key ID. How per-user keys map onto KMS resources is not specified yet [TBD].

**Can I read old data after rotation?** Yes. Old versions stay usable for decrypt, and records are rewrapped on their next write.

## Next

- [Encryption keys](https://mainsdk.dev/docs/interfaces/keys) and [Secrets](https://mainsdk.dev/docs/interfaces/secrets)
- [AWS Secrets Manager](https://mainsdk.dev/docs/integrations/aws-secrets-manager), [Google Cloud KMS](https://mainsdk.dev/docs/integrations/google-cloud-kms) and [HashiCorp Vault](https://mainsdk.dev/docs/integrations/hashicorp-vault)
- [Swap a driver](https://mainsdk.dev/docs/guides/swap-a-driver)
