> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basaltic.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Encrypting under your own key

> Binding a secret to a KMS key of yours, what that changes, and why versions cannot move between keys.

## Encrypting under your own key

By default a secret's versions are encrypted under a platform-managed key. Bind
the secret to one of your own [KMS](/kms) keys at creation to use that instead:

<Tabs>
  <Tab title="Console">
    On **Create Secret**, open the **Encryption** card and choose a key under
    **Encryption key** rather than **Platform-managed key (default)**.

    The picker offers every key in the region that is `enabled` and pinned to
    **Encrypt / Decrypt**, which includes RSA keys that this service will not
    accept — see the warning below.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/secrets
    {
      "name": "prod/api/stripe-key",
      "value": "c3VwZXItc2VjcmV0LXZhbHVl",
      "kms_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
    }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    basaltic secrets create \
      --name prod/api/stripe-key \
      --value "$(base64 -w0 <<< 'super-secret-value')" \
      --kms-key-id 7c9e6679-7425-40de-944b-e07fc1f90ae7
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    sec, err := secrets.New(cfg).CreateSecret(ctx, &secrets.CreateSecretRequest{
        Name:     "prod/api/stripe-key",
        Value:    []byte("super-secret-value"),
        KMSKeyID: basaltic.String("7c9e6679-7425-40de-944b-e07fc1f90ae7"),
    })
    ```

    The key is fixed at creation — there is no field for it on update.
  </Tab>
</Tabs>

The key must be one of yours, in the same region, `enabled`, and pinned to
`encrypt_decrypt`.

<Warning>
  It must also be **symmetric — `aes-256`**. An RSA key with
  `encrypt_decrypt` usage is refused with `400 KMS_INVALID_KEY_SPEC`, because
  every version is sealed with the secret's identity bound in as encryption
  context and RSA-OAEP has nowhere to carry one. Accepting the key would drop
  that binding silently, so it is refused at creation instead.
</Warning>

### What it changes

Binding a secret to your key does not change the API surface — `value` reads
and writes look identical. What changes is that **the key becomes a control you
hold**:

* Disable the key and reads start failing with `409 KMS_KEY_DISABLED`. That
  error travels to you as itself, not as a `500` — the key state is yours, and
  re-enabling the key restores reads.
* Schedule the key for deletion and reads fail with
  `409 KMS_KEY_PENDING_DELETION` for the whole window.
* Let that window elapse and **every version of every secret under that key is
  permanently unreadable.** The ciphertext is still in the database; nothing
  can open it.

<Note>
  The binding is fixed for the life of the secret. `kms_key_id` is accepted
  only on create, and `PATCH` edits just `description` and `tags` — moving a
  secret to a different key means re-wrapping every version, so create a new
  secret and retire the old one instead.
</Note>

### Versions are bound to their secret

Whichever key is used, the ciphertext of every version is sealed with the
secret's account and secret id as encryption context. A stored blob therefore
only decrypts as the version of the secret it was written for — it cannot be
lifted out of the database and replayed as the value of a different secret,
even one encrypted under the same key.
