Tip
For a more detailed treatise check Dr. Duh's YubiKey-Guide.
For a local and secure backup a small encrypted file will suffice.
Additional perk: you can put multiple such files on a drive.
sudo dd if=/dev/urandom of=store.dat bs=1M count=25
sudo cryptsetup -c aes-xts-plain64 -s 512 -h sha512 -y luksFormat store.dat
sudo cryptsetup luksOpen store.dat backup
sudo mkfs.ext4 /dev/mapper/backup
sudo cryptsetup luksClose backup
Now just put this file on a safely-stored thumb drive.
Tip
Do this in the secure live system (e.g. tails, see below).
Mount encrypted file as non-root user
udisksctl mount -b /dev/mmcblk0p1 backup
sudo cryptsetup luksOpen store.dat backup
udisksctl mount -b /dev/mapper/backup
# -> do your stuff
udisksctl unmount -b /dev/mapper/backup
sudo cryptsetup luksClose backup
udisksctl unmount -b /dev/mmcblk0p1
It might make sense to version all backups.
git init
# don't use the `--global` option, we want to store it locally in `.git/config`
git configuser.email <email>
git config user.name <name>
git config commit.gpgsign true
git log --show-signature
Make your life easy and use Tails. Live systems like Arch Linux which directly start in a root shell won't work well with GPG (see here).
Important
For Tails, don't forget to enable the administrator password in additional settings during startup!
Create temporary working directory (run as user)
export GNUPGHOME=$(mktemp -d -t gnupg_$(date +%Y%m%d%H%M)_XXX)
Harden GPG config
wget -O $GNUPGHOME/gpg.conf https://raw.githubusercontent.com/drduh/config/master/gpg.conf
or copy existing one from backup drive to avoid early network connections.
Note
(Arguably) best practice:
- primary keys should never expire
- subkeys are valid for not longer than 1 year
Parent key (pure ed25519 / cv25519)
gpg --quick-generate-key 'Your Name <[email protected]>' ed25519 cert never
Subkeys
export KEYID='<fingerprint>'
gpg --quick-add-key $KEYID ed25519 sign 1y
gpg --quick-add-key $KEYID cv25519 encr 1y
gpg --quick-add-key $KEYID ed25519 auth 1y
Add additional UIDS
gpg --expert --edit-key $KEYID
gpg> adduid
Select UIDS and fully trust them (will not show correct "ultimate trust" until all changes have been saved)
gpg> uid 2
gpg> uid 3
gpg> trust
Your decision? 5
Make other UID primary
gpg> uid 3
gpg> primary
Save and quit
gpg> save
gpg> quit
Quality check (requires openpgp-tools
package)
gpg --export $KEYID | hokey lint
Create export folder
mkdir $GNUPGHOME/exports
Note
Exported files will always differ because of salting, see here.
All secret keys incl. subkeys
gpg --armor --export-secret-keys $KEYID > primary.asc
"Laptop keypair" - i.e. only subkeys but no primary
gpg --armor --export-secret-subkeys $KEYID > subs.asc
Tip
Use the backup
option to export all necessary information to restore the
secrets keys (including local signatures) but NOT the trust database entry,
check docs.
gpg --armor --export-options export-backup --export-secret-keys $KEYID > full_backup.asc
# import would read
gpg --import-options restore[,keep-ownertrust] --import full_backup.asc
Paperkey and QR code (examples here)
sudo pacman -S paperkey qrencode
gpg --export-secret-key $KEYID | paperkey --output paperkey.asc
gpg --export-secret-key $KEYID | paperkey --output-type raw | qrencode --8bit --level H --output paperkey.qr.png
Print paperkey (make sure to set a default printer in CUPS)
lp -o media=a4 -o portrait -o fit-to-page -o sides=two-sided-long-edge paperkey.asc
Paperkey needs public keys for restoration, so export them in their armored and binary versions
gpg --armor --export $KEYID > pubkey.asc
gpg --export $KEYID > pubkey.gpg
Revocation certificate (with optionally specified reason) in case key gets compromised
Note
By default a generic one is already created in $GNUPGHOME/openpgp-revocs.d/$KEYID.rev
.
gpg --gen-revoke $KEYID > revoke_compromised.asc
Export trust database
Important
Exported trust values are equal to trust level + 1:
Trust Level 1 (export: 2) = I don't know or won't say
Trust Level 2 (export: 3) = I do NOT trust
Trust Level 3 (export: 4) = I trust marginally
Trust Level 4 (export: 5) = I trust fully
Trust Level 5 (export: 6) = I trust ultimately
gpg --export-ownertrust > export/ownertrust.txt
Private keys are stored in $GNUPGHOME/private-keys-v1.d/
, check with
gpg -K --with-keygrip
Push to proper keyserver
gpg --export $KEYID | curl -T - https://keys.openpgp.org
# on tails via Tor
gpg --export $KEYID | torify curl -T - https://keys.openpgp.org
Legacy keyservers
gpg --keyserver hkps://keyserver.ubuntu.com --send-keys $KEYID
gpg --keyserver https://pgp.mit.edu --send-keys $KEYID
Process
- requires binary pubkey in addition (dearmor if required)
- needs
export GPG_TTY=$(tty)
forgpg --import
on live system
From QR code
zbarimg -1 --raw -q -Sbinary paperkey.qr.png | paperkey --pubring pubkey.gpg | gpg --import
From paperkey text file
paperkey --pubring pubkey.gpg --secrets paperkey.asc | gpg --import
Optionally use some OCR software like
OCRmyPDF / tesseract to convert printed sheets into
paperkey.asc
file.
Important
The expiration date is a public key attribute only (the private keys never expire).
Boot into secure environment and mount backup drive as explained in the respective sections above.
Create temporary GPG working directory
export GNUPGHOME=$(mktemp -d -t gnupg_$(date +%Y%m%d%H%M)_XXX)
cp <decrypted_backup_drive>/gpg.conf $GNUPGHOME
Import expired key
# pinentry setting might be necessary when in TTY
gpg [--pinentry-mode loopback] --import full_backup.asc
List expired subkeys
gpg --list-options show-unusable-subkeys -K
Extend expiration dates
gpg --edit-key $KEYID
Note
(Arguably) best practice:
- primary keys should never expire
- subkeys are valid for not longer than 1 year
Primary key
gpg> expire
gpg> never
gpg> save
Subkeys
# use key 0 to deselect all
gpg> key 1
gpg> key 2
gpg> key 3
gpg> expire
gpg> 1y
gpg> save
Public keys have changed, so export and upload them (secret keys are kept unaltered!)
# all relevant exports
gpg --armor --export-secret-keys --export-options export-backup $KEYID > full_backup.asc
gpg --armor --export $KEYID > pubkey.asc
gpg --export $KEYID > pubkey.gpg
Note
When not under version control, it might be a good idea to give the exports a different name like
gpg --armor --export $KEYID > $KEYID-$(date +%F).asc
On each host import updated public key
# from keyserver
gpg [--keyserver hkps://keys.openpgp.org] --recv $KEYID
# or alternatively
gpg [--keyserver hkps://keys.openpgp.org] --refresh-keys
# or from file
gpg --import pubkey.asc
Important
You can safely enable KDF if you're planning to use the key on Android with
OpenKeyChain.
See open-keychain/open-keychain#2368.
Confirm yubikey is genuine: https://www.yubico.com/genuine/
Prepare yubikey
gpg --card-edit
gpg/card> admin
gpg/card> kdf-setup
gpg/card> passwd
Transfer keys
gpg --edit-key $KEYID
gpg> key 1
gpg> keytocard
Your selection? 1
gpg> key 1
gpg> key 2
gpg> keytocard
Your selection? 2
gpg> key 2
gpg> key 3
gpg> keytocard
Your selection? 3
gpg> save
gpg> quit
Verify all subkeys start with ssb>
gpg -K
Add pubkey URL to yubikey
gpg --edit-card
gpg/card> admin
gpg/card> url # https://keys.openpgp.org/vks/v1/by-fingerprint/<fingerprint>
gpg/card> quit
Remove and reinsert yubikey, then check its status
gpg --card-status
Enforce touch for GPG operations
sudo pacman -S yubikey-manager
ykman openpgp keys set-touch aut on
ykman openpgp keys set-touch sig on
ykman openpgp keys set-touch enc on
Basically follow the "Using keys" section in Dr. Duh's guide.
Assuming the pubkey URL field has been set in the yubikey.
Fetch public key and deploy subkey references pointing to yubikey
gpg --edit-card
gpg/card> fetch
gpg: requesting key from '<URL>'
gpg: /home/pi/.gnupg/trustdb.gpg: trustdb created
gpg: key FF3E7D88647EBCDB: public key "Dr Duh <[email protected]>" imported
gpg: Total number processed: 1
gpg: imported: 1
gpg/card> quit
Otherwise download the public keys from a keyserver or local computer, import with
gpg --search [email protected]
gpg --import $KEYID
and then only run
gpg --card-status
to create the references.
Set trust level
export KEYID='<fingerprint>'
gpg --edit-key $KEYID
gpg> trust (5)
gpg> quit
Yubikey and GPG keys should now show these symbols in front of the secret keys:
gpg -K
gpg --card-status
sec# ed25519/0xAABBCCDDEEFF0011
ssb> ed25519/0xAABBCCDDEEFF0022
ssb> ed25519/0xAABBCCDDEEFF0033
ssb> cv25519/0xAABBCCDDEEFF0044
After 3 failed attempts yubikey gets blocked. Unblock with
# https://github.com/drduh/YubiKey-Guide/issues/168#issuecomment-1379532749
gpg --edit-card
admin
passwd # select "2 - unblock PIN" and enter admin PIN
gpg --card-status # verify "PIN retry counter" says again "3 0 3"
For use with the Android Password Store:
- Install OpenKeyChain
- Remove Google Authenticator, it will block APS's NFC connection
- Generate SSH key in APS and add it as "deploy key" to the shared repo
- Add recipient to gopass
- Clone repo on phone and unlock secrets like a boss
GPG can also act as SSH agent. To enable, add in
~/.gnupg/gpg-agent.conf
enable-ssh-support
and reload the agent
gpg-connect-agent reloadagent /bye
In zsh you might want to use this oh-my-zsh
plugin for convenience. It
takes care of properly starting the agent and exporting
SSH_AUTH_SOCK
.
Tip
To understand the difference between GPG and SSH fingerprints, consult this great blogpost.
Native SSH keys created with e.g.
ssh-keygen -a 100 -t ed25519 -f ~/.ssh/customer/sshkey -C "<email>::<customer>::$(date +'%Y-%m-%d')"
are added to the agent via
# sets the cache lifetime to 3600s, use -c for always confirm
ssh-add -c -t 3600 ~/.ssh/id_rsa
This converts the SSH key irreversibly into GPG format and stores it separately from the original private key as
cat ~/.gnupg/private-keys-v1.d/<KEYGRIP>.key
The keygrip is basically the same protocol-agnostic ID as calculated for GPG keys with
gpg -K --with-keygrip
All enabled SSH keys are listed with their keygrip and selected hashes here:
cat ~/.gnupg/sshcontrol
-----------------------
# Ed25519 key added on: 2023-02-18 20:43:21
# Fingerprints: MD5:31:7f:60:ca:e2:25:69:d7:cb:7b:7d:a1:fd:33:28:cc
# SHA256:USoxG0ZJuMMwicxRhjgPpqGPzk5cNKA/0R7o0SOddrc
4F8C61F5B655A3F682A2FBB2ECC5B888F933E421 0 confirm
# Ed25519 key added on: 2023-11-09 20:36:07
# Fingerprints: MD5:f8:21:2e:ad:14:35:63:74:49:34:a8:fd:bc:55:8b:8b
# SHA256:uKzAVdF4S3yb5RNe10JbUCmmPdJmp1vf3F1YvTnqbZQ
F97B2173319631EC38520EE3E82780E9A24960B4 3600
These hashes (as in SSH fingerprints) can be manually obtained from the original SSH key with
ssh-keygen -E sha256 -lf <ssh-pubkey>
ssh-keygen -E md5 -lf <ssh-pubkey>
Removing an SSH key involves
- delete GPG-converted private key
~/.gnupg/private-keys-v1.d/<KEYGRIP>.key
- remove corresponding entry in
~/.gnupg/sshcontrol
In order to use an authentication-enabled GPG (sub)key for SSH, run either of these commands
ssh-add -L
gpg --export-ssh-key <ID>
This will output a public key in SSH format that can be added to
~/.ssh/authorized_keys
on remote hosts.
Make sure to add encryption subkey instead of primary and set
export $GOPASS_GPG_OPTS="--no-throw-keyids"
in your shell.
When working with subkeys for git commit signatures, add them with trailing
exclamation mark to ~/.gitconfig
, otherwise GPG will use the latest one with
signing capability or resolve to primary.
Caution
Depending on the shell (e.g. for zsh) you need to escape the exclamation mark or use single quotes so it is not interpreted. See https://unix.stackexchange.com/a/234300.
# for zsh
git config user.signingkey "1F8375B06B86E11DB5707569FB9D7A2564F37DB4\!"
git config user.signingkey '1F8375B06B86E11DB5707569FB9D7A2564F37DB4!'