mirror of
https://github.com/borgbackup/borg.git
synced 2026-10-12 02:11:36 +00:00
New deployment chapter deriving the borg passphrase from a YubiKey OTP slot's HMAC-SHA1 challenge-response via BORG_PASSCOMMAND: only a random challenge is stored, the passphrase itself is computed by the token on every unlock (the long-proven KeePassXC-style mechanism, as also documented for restic in the Arch wiki). Covers programming a slot with and without touch (touchless = unattended backups, --touch = presence-gated interactive use), redundant twin YubiKeys via a shared slot secret, adding the derived passphrase as an additional borg key to an existing repository, the paper copy of the derived passphrase as the recovery path, and honest caveats: Yubico-proprietary (not on other vendors' keys nor the FIDO2-only Security Key series - the age guide covers those), two scarce slots, device-bound vs presence-gated threat model, macOS Input Monitoring permission, and ykman instead of the deprecated ykpers tooling. Co-Authored-By: Claude Fable 5 <[email protected]>
118 lines
5.6 KiB
ReStructuredText
118 lines
5.6 KiB
ReStructuredText
.. include:: ../global.rst.inc
|
|
.. highlight:: none
|
|
|
|
Passphrases from a YubiKey (challenge-response)
|
|
===============================================
|
|
|
|
The YubiKey's OTP application offers HMAC-SHA1 challenge-response: the YubiKey holds a
|
|
secret key and computes ``HMAC-SHA1(secret, challenge)`` for any challenge sent to it.
|
|
This can derive the borg passphrase on demand - the passphrase is never stored anywhere,
|
|
only a random challenge is - by wiring the derivation into ``BORG_PASSCOMMAND`` (see
|
|
:ref:`env_vars`). It is the same long-proven mechanism used by e.g. KeePassXC.
|
|
|
|
This is the most minimal hardware-backed setup for YubiKey owners: one tool
|
|
(``ykman``, from the yubikey-manager package), no other files than a challenge, and the
|
|
slot can be programmed with or without a touch requirement - so it covers both
|
|
unattended backups and presence-gated interactive use. The trade-offs: it is a
|
|
Yubico-proprietary mechanism (it does not exist on other vendors' security keys, nor on
|
|
Yubico's FIDO2-only "Security Key" series), and a YubiKey has only two OTP slots, which
|
|
may be shared with other uses of challenge-response. For a vendor-neutral approach that
|
|
also covers other hardware (any FIDO2 security key, TPM, Apple Secure Enclave), see the
|
|
age-based deployment guide.
|
|
|
|
Program a challenge-response slot
|
|
---------------------------------
|
|
|
|
Check which slots are free first - **programming a slot overwrites its previous
|
|
content** (slot 1 usually holds the factory Yubico OTP credential; slot 2 is typically
|
|
free)::
|
|
|
|
ykman otp info
|
|
|
|
Program slot 2 with a random secret::
|
|
|
|
ykman otp chalresp --generate 2
|
|
|
|
Without ``--touch``, responses are computed silently - that is what enables unattended
|
|
use. Add ``--touch`` instead if every unlock shall require a touch on the YubiKey
|
|
(presence-gated, for interactive use; not usable for unattended backups).
|
|
|
|
``--generate`` prints the generated secret once. Either store that hex secret offline
|
|
(it allows programming a *second* YubiKey with the same secret later, see below) or
|
|
discard it - afterwards it cannot be read back from the YubiKey.
|
|
|
|
For two YubiKeys as redundant twins, generate the secret yourself and program it into
|
|
both - they will then derive identical passphrases::
|
|
|
|
openssl rand -hex 20 # the slot secret; store it offline or destroy it afterwards
|
|
ykman otp chalresp 2 <SECRET> # run once per YubiKey
|
|
|
|
Derive the borg passphrase
|
|
--------------------------
|
|
|
|
Create a random challenge and keep it with your borg configuration (the HMAC challenge
|
|
is limited to 64 bytes, so 32 random bytes = 64 hex digits is a good choice)::
|
|
|
|
openssl rand -hex 32 > ~/.config/borg/yubikey-challenge
|
|
chmod 600 ~/.config/borg/yubikey-challenge
|
|
|
|
The passphrase is the response to that challenge::
|
|
|
|
ykman otp calculate 2 "$(cat ~/.config/borg/yubikey-challenge)"
|
|
d2f19d6c2b48e1665d2ea1042ee5d1c34e29bb2e
|
|
|
|
Keep an offline copy of this response (e.g. on paper, in a safe place): it is the
|
|
actual borg passphrase, and it is your recovery path if the YubiKey is lost - without
|
|
YubiKey and without the paper copy, this borg key cannot be unlocked.
|
|
|
|
The challenge file is not usable without the YubiKey holding the slot secret, but keep
|
|
it protected anyway: both "factors" are needed to derive the passphrase.
|
|
|
|
Wire it into borg
|
|
-----------------
|
|
|
|
``BORG_PASSCOMMAND`` runs its command *without* a shell, so ``$(...)`` substitution is
|
|
not available there. Put the derivation into a small script, e.g.
|
|
``~/.config/borg/yubikey-passphrase.sh`` (make it executable: ``chmod 700``)::
|
|
|
|
#!/bin/sh
|
|
exec ykman otp calculate 2 "$(cat ~/.config/borg/yubikey-challenge)"
|
|
|
|
and use it::
|
|
|
|
export BORG_PASSCOMMAND=~/.config/borg/yubikey-passphrase.sh
|
|
|
|
For a **new** repository, set ``BORG_PASSCOMMAND`` before ``borg repo-create``: the
|
|
command's output is then used as the new repository's passphrase.
|
|
|
|
For an **existing** repository, add the derived passphrase as an additional borg key
|
|
(keeping the existing passphrase as an independent way in, e.g. for recovery)::
|
|
|
|
BORG_NEW_PASSPHRASE="$(~/.config/borg/yubikey-passphrase.sh)" borg key add --label yubikey
|
|
|
|
With a touchless slot, scheduled backups (cron, systemd timers) now work while the
|
|
YubiKey is plugged in; with ``--touch``, every borg command asks for a touch.
|
|
|
|
Notes and caveats
|
|
-----------------
|
|
|
|
- A touchless slot makes the YubiKey act like an *uncopyable keyfile*: stealing the
|
|
challenge file (or any other file) is useless without the physical YubiKey, but code
|
|
running on the machine can derive the passphrase silently while the YubiKey is
|
|
plugged in - it is device-bound, not presence-gated. A ``--touch`` slot prevents
|
|
silent derivation, at the price of unattended use. In both cases, borg necessarily
|
|
obtains the passphrase and key material at unlock time - a compromised client still
|
|
gets them.
|
|
- Reprogramming the slot (or losing all YubiKeys programmed with its secret)
|
|
permanently breaks this derivation. Keep the paper copy of the derived passphrase,
|
|
or another borg key with its own passphrase, as the recovery path - and remember
|
|
that a repository is only as secure as its *weakest* borg key.
|
|
- The response is 160 bits (HMAC-SHA1). SHA-1's known weaknesses do not matter here
|
|
(it is used as a keyed PRF, not for collision resistance), and 160 random bits are
|
|
far beyond brute force.
|
|
- On macOS, the ``ykman otp`` commands access the YubiKey's keyboard (HID) interface,
|
|
which requires the *Input Monitoring* permission for your terminal application
|
|
(System Settings > Privacy & Security > Input Monitoring).
|
|
- The legacy ``ykchalresp -2 -i <challengefile>`` tool (from the deprecated ykpers
|
|
package) computes the same responses; prefer the maintained ``ykman``.
|