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 config user.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.
Important
In the current version of DrDuh's config, the armor option is set, i.e. gpg
exports are not in binary format anymore by default. Keep in mind to add the
--no-armor flag when needed.
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 <your.email@example.com>' ed25519 cert neverSubkeys
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 1yAdd 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 lintCreate export folder (or directly use folder on backup drive)
mkdir $GNUPGHOME/exportsNote
Exports of the same key will always produce different files 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.ascTip
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.ascPaperkey and QR code (examples here) can optionally be generated from binary secret keys
sudo pacman -S paperkey qrencode
gpg --no-armor --export-secret-keys $KEYID | paperkey --output paperkey.asc
gpg --no-armor --export-secret-keys $KEYID | paperkey --output-type raw | qrencode --8bit --level H --output paperkey.qr.pngPrint 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 --no-armor --export $KEYID > pubkey.gpg
Revocation certificate (with optionally specified reason) in case key gets compromised (-> select 1)
Note
By default a generic one is already created in $GNUPGHOME/openpgp-revocs.d/$KEYID.rev.
gpg --gen-revoke $KEYID > revoke_compromised.ascExport 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.txtPrivate keys are stored in $GNUPGHOME/private-keys-v1.d/, check with
gpg -K --with-keygripPush 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.orgLegacy keyservers
gpg --keyserver hkps://keyserver.ubuntu.com --send-keys $KEYID
gpg --keyserver https://pgp.mit.edu --send-keys $KEYIDTip
Now is a good time to test recovery of the generated backup exports, in particular for the paperkey version. Just create another temporary GNUPGHOME and run the import commands listed below.
Process
- requires binary pubkey in addition (de-armor if required)
- needs
export GPG_TTY=$(tty)forgpg --importon live system
From QR code
zbarimg -1 --raw -q -Sbinary paperkey.qr.png | paperkey --pubring pubkey.gpg | gpg --importFrom paperkey text file
paperkey --pubring pubkey.gpg --secrets paperkey.asc | gpg --importOptionally 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.ascList expired subkeys
gpg --list-options show-unusable-subkeys -KExtend expiration dates
gpg --edit-key $KEYIDNote
(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 can leave untouched!)
# all relevant exports
gpg --armor --export-secret-keys --export-options export-backup $KEYID > full_backup.asc
gpg --armor --export $KEYID > pubkey.asc
gpg --no-armor --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 (set PIN + Admin Pin but leave reset code disabled)
# factory pin: 123456
# factory admin pin: 12345678
gpg --card-edit
gpg/card> admin
gpg/card> name
gpg/card> kdf-setup
gpg/card> passwd
Transfer keys
gpg --edit-key $KEYIDgpg> 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 -KAdd 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-statusEnforce touch for GPG operations
# on arch
sudo pacman -S yubikey-manager
sudo systemctl start pcscd.servicd
# on tails
sudo apt install yubikey-managerykman openpgp keys set-touch aut on
ykman openpgp keys set-touch sig on
ykman openpgp keys set-touch enc onBasically 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-cardgpg/card> fetch
gpg: requesting key from '<URL>'
gpg: /home/pi/.gnupg/trustdb.gpg: trustdb created
gpg: key FF3E7D88647EBCDB: public key "Dr Duh <doc@duh.to>" 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.address@web.com
gpg --import $KEYIDand then only run
gpg --card-statusto create the references.
Set trust level
export KEYID='<fingerprint>'
gpg --edit-key $KEYIDgpg> trust (5)
gpg> quit
Yubikey and GPG keys should now show these symbols in front of the secret keys:
gpg -K
gpg --card-statussec# 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_rsaThis 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>.keyThe 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 3600These 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!'