---
title: "Key management"
description: "You keep the private half. Loss is unrecoverable, deliberately."
url: "https://saved.sh/docs/security/key-management"
---

Everything in this section rests on one key pair. You give us the public half; the private
half is yours alone, and we have no copy, no escrow and no override.

<Callout type="error">
  **If you lose the private key, your backups are unreadable, by you and by us.** There is no
  support request that recovers them. This is not a policy we could waive: there is no key on
  our side to waive it with.
</Callout>

That is the cost of the guarantee on the [encryption](/docs/security/encryption) page. It is
worth paying, and it is worth taking seriously on the day you set it up rather than later.

## Installing gpg [#installing-gpg]

You need an OpenPGP implementation on the machine that generates the key and on any machine
that will restore.

| Platform          | Install                                                              |
| ----------------- | -------------------------------------------------------------------- |
| **Debian/Ubuntu** | `sudo apt install gnupg`                                             |
| **Fedora/RHEL**   | `sudo dnf install gnupg2`                                            |
| **Alpine**        | `apk add gnupg`                                                      |
| **macOS**         | `brew install gnupg`, or [GPG Suite](https://gpgtools.org) for a GUI |
| **Windows**       | `winget install GnuPG.Gpg4win`, or [Gpg4win](https://gpg4win.org)    |

Verify it works before you rely on it:

```bash
gpg --version
```

On Windows, run the commands below in **PowerShell** or in the Git Bash shell that ships with
Git for Windows. The `gpg` commands themselves are identical on all three platforms; only the
surrounding shell syntax and paths differ, and both are noted where they matter.

## Generating a key [#generating-a-key]

The same command everywhere:

```bash
gpg --quick-generate-key "backups@example.com" default default never
```

| Argument                | Meaning                                           |
| ----------------------- | ------------------------------------------------- |
| `"backups@example.com"` | The user ID. An address you control, or any label |
| `default` (algorithm)   | The implementation's current recommendation       |
| `default` (usage)       | Sign and encrypt                                  |
| `never`                 | **No expiry**                                     |

<Callout type="warn">
  **Use `never` for a backup key.** An expired key still decrypts existing artifacts, but gpg
  refuses to *encrypt* to it, so every run starts failing on a date nobody wrote down. If your
  policy requires expiry, put a calendar reminder well before it and
  [rotate](#rotating-a-key) deliberately.
</Callout>

You will be prompted for a passphrase. Use one, and store it in your password manager
alongside a note saying which key it belongs to.

Export the public half to give to us:

```bash
gpg --armor --export backups@example.com > prod-public.asc
```

On Windows PowerShell, redirect with encoding control so the file is plain ASCII:

```powershell
gpg --armor --export backups@example.com | Out-File -Encoding ascii prod-public.asc
```

That file is what goes into a backup definition or a worker config. It is not secret.

## Backing up the private half [#backing-up-the-private-half]

**Do this immediately, before the first backup runs.** A key that exists only in one
`~/.gnupg` is a single point of failure that will not announce itself.

```bash
gpg --armor --export-secret-keys backups@example.com > prod-private.asc
```

```powershell
gpg --armor --export-secret-keys backups@example.com | Out-File -Encoding ascii prod-private.asc
```

That file plus its passphrase is the whole of your recovery capability. Treat it accordingly:

| Where the key lives by default | Path              |
| ------------------------------ | ----------------- |
| Linux, macOS                   | `~/.gnupg`        |
| Windows                        | `%APPDATA%\gnupg` |

Lock down the directory on Unix-like systems, which gpg mostly does for you:

```bash
chmod 700 ~/.gnupg
chmod 600 ~/.gnupg/*
```

On Windows, the folder inherits your user profile's ACL. Confirm no other principal has read
access if the machine is shared.

## Where to keep it [#where-to-keep-it]

The rule is one sentence: &#x2A;*the private key must not share fate with the systems it protects.**

| Good                                                       | Why                                                     |
| ---------------------------------------------------------- | ------------------------------------------------------- |
| A password manager with attachments (1Password, Bitwarden) | Encrypted, replicated, already in your recovery process |
| A hardware token (YubiKey)                                 | The key cannot be copied off it                         |
| Printed and in a safe                                      | Survives every digital failure                          |
| An offline drive in a different building                   | Survives the site                                       |

| Bad                                              | Why                                       |
| ------------------------------------------------ | ----------------------------------------- |
| On the database server                           | The thing you are backing up              |
| In the repo, or in the worker's config directory | Alongside the ciphertext                  |
| Only on one laptop                               | The laptop is the single point of failure |
| Only in the cloud account being backed up        | Shares fate by definition                 |

<Callout type="warn">
  **At least two people, or two places.** A key only one person can reach is an availability
  risk dressed as a security control. If that person is unreachable during the incident, the
  backups are gone in every way that matters.
</Callout>

## Restoring on another machine [#restoring-on-another-machine]

The point of the backup copy is that a restore works somewhere else. Import it:

```bash
gpg --import prod-private.asc
gpg --list-secret-keys
```

Or hand the key to `sctl` directly, without importing it into your keyring at all:

```bash
sctl restore file <artifact-id> --path ./restored.tar --key ./prod-private.asc
```

`--key` imports into a temporary keyring that is deleted when the command exits, which is the
right choice on a machine you do not own.

## Which key opens which artifact [#which-key-opens-which-artifact]

Every artifact records the fingerprint of the key it was encrypted to.

```bash
sctl artifact get <artifact-id>          # shows Key
gpg --list-packets artifact | head -3    # shows keyid, with no account
gpg --list-secret-keys --keyid-format LONG
```

Match the ID from the middle command against the last. That is the whole of "do I have the
right key", and it works offline.

## Rotating a key [#rotating-a-key]

Rotation is additive. Nothing re-encrypts.

1. Generate the new key pair, and back up the new private half.
2. Set the new public key on the backup, or on the worker for a local backup.
3. Future artifacts use the new key. &#x2A;*Existing artifacts still need the old one.**
4. Keep the old private key until the last artifact carrying its ID has expired.

```bash
sctl artifact list --backup $BACKUP_ID
```

<Callout type="error">
  **Never destroy an old private key while artifacts encrypted to it still exist.** Retention
  tells you exactly when that is safe: once no artifact lists the old key ID, it is dead
  weight. Until then, destroying it destroys those backups.
</Callout>

Rotate when someone with access to the key leaves, when you suspect exposure, or on whatever
schedule your policy sets. There is no cost to rotating other than keeping one more key
around for a retention window.

## If the key is compromised [#if-the-key-is-compromised]

Assume every artifact encrypted to it is readable by whoever has it. You cannot revoke that,
because the artifacts are already encrypted and the attacker may already hold copies.

What you can do, in order:

1. **Rotate the key** so future artifacts are protected.
2. **Rotate the source credentials** the backups used, since anyone reading an artifact reads
   the data those credentials protect.
3. **Check the audit log** for `artifact.download_url_issued` entries you cannot account for.
   That is the only record of a copy leaving.
4. **Consider expiring the old artifacts early**, if your retention policy and locks allow it.

## Passphrases and automation [#passphrases-and-automation]

A passphrase protects the key at rest. It also means an unattended restore prompts for input.

* For **restores**, that is fine: a human is present.
* For **encryption**, no passphrase is needed at all. Only the public key is used, and it is
  not secret.

So the worker never needs your passphrase, and you should never put it in a config file.
There is no configuration option that takes one, deliberately.

## Next [#next]

<Cards>
  <Card href="/docs/recover/rehearsal" title="Rehearsal" description="Proving the key works, before you need it." />

  <Card href="/docs/security/encryption" title="Encryption" description="What the key is protecting." />

  <Card href="/docs/backups/encryption" title="Configuring encryption" description="Where the public key goes." />
</Cards>
