Files
versioning/research_notes/Backup encryption security choices/key-management.md
T
retoor 3499f6cda0 Remove encryption; user-data filters; SQLite safety; recovery, retention, scheduler
- No client-side encryption: plaintext content-addressed remote (SHA-256
  names), no backup key, no cryptography dependency (server disk encryption
  is the trust model).
- Filters back user data: images, archives, databases, PDFs accepted; 10 MiB
  cap; binary sniffing removed; temp names hardened (~$, #..#, .temp).
- SQLite zero-error policy: backup-API snapshots + integrity_check, journal
  folding, locked/corrupt loud skips, verified restores.
- Recovery: reindex from manifests, remote adopt, on-demand blob fetch,
  5-day retention + thinning, GC, date-guarded remote purge, metrics.
- Scheduler with WebDAV quota signal and 70% pressure backstop (floor kept).
- 37 tests incl. live-monitor capture safety and DB safety.
2026-10-10 03:41:36 +02:00

20 KiB
Raw Blame History

Backup encryption security choices: key management

How is the master key generated and derived from a password (scrypt, Argon2, PBKDF2 parameters)?

Takeaway

All five systems use password-derived envelope encryption with a random master/data key, but KDFs and parameters differ: restic pins scrypt N=65536/r=8/p=1 (auto-calibrated on newer adds); Borg 1.x uses PBKDF2-HMAC-SHA256 while Borg 2.x defaults to Argon2id + ChaCha20-Poly1305; Kopia uses scrypt-65536-8-1 over UniqueID salt plus HKDF-SHA256 subkeys (PBKDF2/scrypt configurability added ~2025); rclone crypt uses scrypt N=16384/r=8/p=1; Tarsnap generates keys locally and only uses scrypt to passphrase-wrap the key file.

Cited Findings

  • restic: all repo data encrypted with AES-256-CTR + Poly1305-AES MAC as IV || CIPHERTEXT || MAC (32 bytes overhead, random 16-byte nonce per file); three keys needed (32-byte AES-256 key + 16-byte AES key + 16-byte Poly1305 key) — Source
  • restic: on open, password + per-key N, r, p, salt feed scrypt to derive 64 bytes; first 32 = AES-256 encryption key, last 32 split into 16-byte AES key k + 16-byte Poly1305 key r (masked); these decrypt the data field to reveal the master key JSON — Source
  • restic: canonical key-file example pins "kdf": "scrypt", "N": 65536, "r": 8, "p": 1; code rejects any KDF other than scrypt (only supported KDF is scrypt()) — Source
  • restic: AddKey calibrates KDF parameters (crypto.Calibrate(KDFTimeout, KDFMemory)) when adding keys, generates a random salt (crypto.NewSalt()) and either a fresh random master key (crypto.NewRandomKey()) or copies the existing master key template — Source
  • Borg 1.x/legacy: key file encrypted with PBKDF2-HMAC-SHA256 (32-byte salt, PBKDF2_ITERATIONS), AES-CTR with zero IV plus HMAC-SHA256 encrypt-and-MAC construction — Source
  • Borg 2.x: 256-bit key-encryption key (KEK) derived from passphrase with Argon2 + random 256-bit salt, then Encrypt-then-MAC of packed key material with ChaCha20-Poly1305 AEAD and constant IV==0 (safe because salt makes KEK unique per encryption) — Source
  • Borg: new key files support argon2 chacha20-poly1305 vs legacy sha256 (PBKDF2) algorithms; Argon2 path derives 32-byte KEK via argon2.low_level.hash_secret_raw with configurable time/memory/parallelism/type — Source
  • Borg: --key-algorithm argon2 is default (Argon2id); pbkdf2 kept for old-client compatibility; docs note Argon2 path also fixes two issues at once (separate encrypt vs MAC keys, encrypt-then-MAC instead of encrypt-and-MAC) — Source
  • Borg: random Borg key itself consists of three random secrets (crypt key, id key, chunker seed); passphrase only locks/encrypts this key, chunking and IDs also derive from it — Source
  • Kopia: envelope encryption; format blob carries uniqueID (random 32 bytes), keyAlgo (e.g. scrypt-65536-8-1), encryption: AES256_GCM, and encryptedBlockFormat holding the real content-encryption secrets (HMACSecret, MasterKey) — Source
  • Kopia: Km = PBKDF(passphrase, UniqueID, cost params) (32 bytes) → Ke = HKDF(SHA256, Km, UniqueID, "AES", 32) and AD = HKDF(SHA256, Km, UniqueID, "CHECKSUM", 32); format block encrypted with AES256-GCM using Ke, random IV prepended, AD authenticated — Source
  • Kopia: only scrypt N=65536/r=8/p=1 documented as supported ("at the moment"), field reserved for future algorithm/cost changes — Source
  • Kopia: 2025 PR adds configurable KDF: --pbkdf (pbkdf2|scrypt), --pbkdf-iter (default 600000 for PBKDF2), --pbkdf-memory (default 64 MB for scrypt), usable at repo create and change-password — Source
  • rclone crypt: file content uses NaCl SecretBox (XSalsa20 + Poly1305), 64 KiB chunks each with 16-byte authenticator, header = 8-byte magic RCLONE\x00\x00 + 24-byte random nonce incremented per chunk — Source
  • rclone crypt: filename segments PKCS#7-padded to 16 bytes, encrypted deterministically with EME (ECB-Mix-ECB) AES-256, emitted as lowercase unpadded base32 — Source
  • rclone crypt: 80 bytes of key material derived via scrypt N=16384, r=8, p=1 from password (+ optional password2 salt); without user salt a built-in internal salt is used — Source
  • Tarsnap: key files hold authentication keys (server access proofs) and encryption keys (archive encrypt/sign/verify/decrypt) separately, so server compromise does not disclose data — Source
  • Tarsnap: no password-derived master key by default; keys generated by tarsnap-keygen; passphrase protection is optional (--passphrased) and wraps the key file with keys from the scrypt KDF, with tunable --passphrase-mem / --passphrase-time — Source

Inferences

  • restic/Kopia/rclone all standardize on scrypt but with 4x cost difference (16384 vs 65536), so rclone password guessing is cheaper; Borg's Argon2id move is the most modern KDF posture.
  • Kopia's double-HKDF (separate Ke and AD from one Km) is the cleanest domain separation; restic's split-and-mask of 64 scrypt bytes is older Poly1305-AES style.

Gaps

  • restic's current auto-calibration targets (KDFTimeout, KDFMemory) were not retrieved; documented example still shows 65536/8/1 but newly added keys may use higher N.
  • Borg's exact default Argon2 time/memory/parallelism constants (ARGON2_ARGS, ARGON2_SALT_BYTES) were not captured.
  • Tarsnap's default scrypt mem/time when --passphrase-mem/--passphrase-time are omitted was not found.

Where is key material stored (repo config, local key file, OS keyring, TPM)? What is the exact UX for export/backup of keys?

Takeaway

restic and Kopia keep the (password-locked) key material inside the repository; Borg offers repokey (in repo) vs keyfile (client ~/.config/borg/keys) as a first-class choice; Tarsnap and rclone keep keys strictly client-side (key file / rclone.conf), making client-side backup mandatory. None document TPM; OS-credential integration exists only for Kopia (server/connect password caching) and via external helpers for the rest.

Cited Findings

  • restic: keys/ directory in repo holds JSON key files (hostname, username, created, kdf params, salt, encrypted data); restic cat masterkey decrypts and pretty-prints master encryption/MAC keys — Source
  • restic: passwords supplied via prompt, --password-file, or --password-command ($RESTIC_PASSWORD_FILE / $RESTIC_PASSWORD_COMMAND); empty passwords refused by default, --insecure-no-password required since 0.17.0 — Source
  • Borg 2.x: repokey modes store encrypted key in repo (repo_dir/config); keyfile modes store in home dir (~/.config/borg/keys); --key-location repokey|keyfile chosen at rcreate, movable later via key change-location — Source
  • Borg 1.x: identical split — repokey inside repo directory, keyfile in ~/.config/borg/keys (macOS: ~/Library/Application Support/borg/keys); remote repos via ssh never see passphrase, plaintext key, or plaintext files — Source
  • Borg export UX: borg key export [PATH], --paper (printable, per-line checksums for type-in), --qr-html (QR + paper copy); export stays passphrase-encrypted (no passphrase included); borg key import [--paper] restores; paper-key web tool at paperkey.html — Source; Source
  • Borg: for keyfile repos the key must be backed up independently ("NOT sufficient" to keep copy on the backed-up system); for repokey a backup is "not strictly needed" but guards against corruption/loss of the in-repo key — Source
  • Kopia: format blob (kopia.repository / kopia.blobcfg objects) lives in storage; local repository-*.config holds connection parameters (not the password); quick-reconnect token via kopia repository status -t (opaque) and -s embeds password (trivially decodable, must be guarded) — Source
  • Kopia: repository password cached in OS-specific credential storage (Keychain on macOS, Credential Manager on Windows, Keyring on Linux) — Source
  • rclone crypt: password + optional salt (password2) stored in rclone.conf in lightly obscured form (AES-CTR with static shared key, random IV prepended) — explicitly "not secure" without overall config-file encryption (rclone config password protection); env vars RCLONE_CRYPT_PASSWORD / RCLONE_CRYPT_PASSWORD2 (must be rclone obscured) — Source
  • rclone crypt: recovery = remember password or keep rclone.conf; same passwords re-entered on another machine reproduce access (obscured strings differ due to salt) — Source
  • Tarsnap: single key file from tarsnap-keygen --keyfile <file> --user <email> --machine <name>; every tarsnap invocation needs --keyfile; passphrase via --passphrase method:arg (dev:tty-stdin default, env:VAR, file:FILENAME flagged as risky) — Source; Source
  • Tarsnap: tarsnap-keymgmt --outkeyfile <new> [-r] [-w] [-d] [--nuke] mints restricted sub-keys (read-only, write-only, delete, nuke-only); -d implies -r — Source

Inferences

  • Only Borg gives a deliberate two-factor-style choice (possession of keyfile + knowledge of passphrase); restic/Kopia/rclone-default are passphrase-only if the repo/config is exfiltrated.
  • No system in scope documents TPM / hardware-backed key storage as of 2026; OS keyring appears only as a convenience cache (Kopia), not as a KDF or sealing mechanism.

Gaps

  • No reliable source found on native TPM2 / Secure Enclave / Windows Hello integration for any of the four; community wrappers (pass, keyring scripts, systemd-creds) exist but were not verified as documented behavior.
  • restic's recommended master-key backup workflow beyond cat masterkey (e.g. official paper-key guidance) was not found.

Do any support key rotation, multiple keys/passphrases, or Shamir/shared recovery? How?

Takeaway

restic and Borg (2.x/master) support multiple concurrent keys/passphrases sharing one master secret; Kopia and rclone crypt support only password change (re-wrapping the same secrets), with rclone requiring full re-upload; Tarsnap supports restricted sub-keys but not multi-passphrase unlock. No system documents Shamir secret sharing natively.

Cited Findings

  • restic: key command with list, add, remove, passwd subcommands manages multiple access keys per repo; key add prompts for current password then new password and saves a new key wrapping the same master key; list marks current key with * — Source
  • restic: key passwd creates a new key ID and removes the old one; key remove <ID> refuses to remove the currently-used key — Source; Source
  • restic: AddKey with non-nil template copies master keys from the old key instead of generating new ones, confirming rotation re-wraps rather than re-encrypts data — Source
  • Borg: key change-passphrase only re-locks the same secrets ("does not protect future nor past backups" if key+passphrase were compromised) — Source
  • Borg: key change-algorithm argon2|pbkdf2 upgrades/downgrades the KDF wrapping without changing secrets — Source
  • Borg (master/2.x): multiple borg keys per repo — each key holds the same secret material under an independent passphrase and label (first key labeled admin, protected from removal); key list/add/remove/export --label|--key selectors; passphrase tried against every key — Source
  • Borg: rcreate --other-repo SRC --copy-crypt-key can reuse crypt key across related repos (default: fresh random crypt key, shared chunker/ID keys for dedup) — Source
  • Kopia: kopia repository change-password (CLI only, not GUI) re-encrypts the format block with a new password; must already be connected (so a still-connected client can reset a forgotten password, a disconnected one cannot) — Source; Source
  • rclone crypt: no in-place password change — changing the configured password orphans existing content; must re-upload everything (in place via second crypt remote + rclone copy decrypting/re-encrypting on the fly, at 2x bandwidth/quota cost) — Source
  • Tarsnap: no multi-passphrase unlock or rotation of archive encryption keys documented; capability separation via tarsnap-keymgmt restricted keys is the only delegation mechanism — Source
  • Shamir/shared recovery: no hits in official docs for restic, Borg, Kopia, rclone crypt, or Tarsnap; only community workarounds (splitting password/paper key with external SSS tools) exist — no primary-source citation available.

Inferences

  • True cryptographic rotation (new data key, re-encryption) is offered by none of the five for existing snapshots; all "rotation" is re-wrapping or passphrase change.
  • Borg's multi-key design is closest to shared/admin recovery (per-user passphrases + protected admin key), but all keys still share one secret — compromise of the secret compromises all slots.

Gaps

  • Whether borg key add (multi-key) is released in stable 2.0 vs master-only could not be pinned down; primary evidence is the PR plus master usage/key.html.
  • No documented Shamir/threshold scheme in any official docs; confirm as absent-by-design vs merely undocumented.

What happens on key loss — is data unrecoverable by design? Any documented footguns?

Takeaway

All five declare password/key loss unrecoverable by design. The sharpest footguns: restic key remove of the sole key and deleted/corrupt keys/ files; Borg keyfile loss without export and repokey-with-empty-passphrase on exposed storage; Kopia disconnected-password loss; rclone salt (password2) loss and obscured-config confusion; Tarsnap key-file loss (including billing trap).

Cited Findings

  • restic docs repeat on every backend page: "knowledge of your password is required… Losing your password means that your data is irrecoverably lost" — Source
  • restic forum (maintainer fd0): if key-file data or salt is missing/corrupt there is "no way to decrypt the data again. Even if you know the password"; intact data+salt (+N/r/p, brute-forceable) is required — Source
  • restic footgun case: user generated a random key file, ran key remove on the old key, then lost the random file (only copy was inside the backup) — recovery required disk forensics for the deleted key file; key remove is just a file deletion in keys/ — Source
  • restic: brute force only viable if much of the password structure is known; otherwise "consider it lost" — Source
  • Borg: "always need both the Borg key and passphrase"; keyfile loss = repo loss ("if you lose the key, you lose access"); mandated offsite export ("leaving your keys inside your car" warning); empty passphrase with repokey = anyone reading the repo can unlock (as good as no encryption); keyfile+empty passphrase acceptable only if client disk is encrypted — Source
  • Borg: exported key stays encrypted — recovery needs both export file AND original passphrase, stored in separate safe places — Source
  • Kopia: "you cannot restore your files if you forget your password, there is no way to recover a forgotten password because only you know it"; FAQ: "There is no way to recover it or the files and folders within that repository. Store your repository password in a safe place, such as a password manager" — Source; Source
  • Kopia footgun nuance: password CAN be reset while still connected (change-password), which rescues forgotten-but-connected GUI users via CLI, but a disconnected client with a forgotten password is unrecoverable — Source
  • Kopia reconnect token (status -t -s) trivially decodes to the password — storing it insecurely leaks the repo — Source
  • rclone crypt: custom salt is "effectively a second password that must be memorized" and is NOT stored with the data; losing password2 loses data even with correct password — Source
  • rclone footguns: obscured passwords look encrypted but use a static shared AES-CTR key (cursory-inspection only); 1.49.0–1.53.2 random-password generator bug produced insecure passwords (fixed 1.53.3, must rotate by re-upload) — Source
  • Tarsnap FAQ: "You can't. Your key file contains the only copy of the cryptographic keys… if you lose them there is no way to get your data back" (same for forgotten key-file passphrase); separate FAQ covers being unable to stop billing after losing all keys — Source

Inferences

  • The envelope designs make server-side recovery cryptographically impossible, so every vendor pushes the same operational answer: paper/physical export + password manager + offsite separation of key-export and passphrase.
  • rclone's model is the most fragile operationally (two secrets + weak-by-default config obscuring + no re-wrap), Tarsnap second (single local file, no passphrase fallback).

Gaps

  • Corrupt-repo recovery (bitrot, backend errors) vs key loss is covered by Kopia consistency docs and restic rebuild-index/recover, but systematic per-tool data on partial recovery without keys was out of scope and not collected.