Repository: drduh/YubiKey-Guide
Branch: master
Commit: 9778977fa261
Files: 16
Total size: 185.5 KB
Directory structure:
gitextract_zrqpbut6/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── SECENV.md
├── config/
│ ├── gpg-agent.conf
│ └── gpg.conf
├── media/
│ └── schema_gpg.pptx
├── nix/
│ ├── diceware-vt.patch
│ └── flake.nix
├── pubkeys/
│ └── debian-DA87E80D6294BE9B.asc
├── scripts/
│ ├── generate.sh
│ ├── reset-yubikey
│ └── switch-to-backup-yubikey
└── templates/
├── passphrase.html
└── passphrase.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: [drduh]
================================================
FILE: .gitignore
================================================
.~*
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 drduh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
This is a guide to using [YubiKey](https://www.yubico.com/products/identifying-your-yubikey/) as a [smart card](https://security.stackexchange.com/questions/38924/how-does-storing-gpg-ssh-private-keys-on-smart-cards-compare-to-plain-usb-drives) for secure encryption, signature and authentication operations.
Cryptographic keys on YubiKey are [non-exportable](https://web.archive.org/web/20201125172759/https://support.yubico.com/hc/en-us/articles/360016614880-Can-I-Duplicate-or-Back-Up-a-YubiKey-), unlike filesystem-based credentials, while remaining convenient for regular use. YubiKey can be configured to require a physical touch for cryptographic operations, reducing the risk of unauthorized access.
- [Purchase YubiKey](#purchase-yubikey)
- [Prepare environment](#prepare-environment)
- [Install software](#install-software)
- [Prepare GnuPG](#prepare-gnupg)
* [Configuration](#configuration)
* [Identity](#identity)
* [Key](#key)
* [Expiration](#expiration)
* [Passphrase](#passphrase)
- [Create Certify key](#create-certify-key)
- [Create Subkeys](#create-subkeys)
- [Verify keys](#verify-keys)
- [Backup keys](#backup-keys)
- [Export public key](#export-public-key)
- [Configure YubiKey](#configure-yubikey)
* [Change PIN](#change-pin)
* [Set attributes](#set-attributes)
- [Transfer Subkeys](#transfer-subkeys)
* [Signature key](#signature-key)
* [Encryption key](#encryption-key)
* [Authentication key](#authentication-key)
- [Verify transfer](#verify-transfer)
- [Finish setup](#finish-setup)
- [Using YubiKey](#using-yubikey)
* [Encryption](#encryption)
* [Signature](#signature)
* [Configure touch](#configure-touch)
* [SSH](#ssh)
+ [Replace agents](#replace-agents)
+ [Copy public key](#copy-public-key)
+ [Import SSH keys](#import-ssh-keys)
+ [SSH agent forwarding](#ssh-agent-forwarding)
- [Use ssh-agent](#use-ssh-agent)
- [Use S.gpg-agent.ssh](#use-sgpg-agentssh)
- [Chained forwarding](#chained-forwarding)
* [GitHub](#github)
* [GnuPG agent forwarding](#gnupg-agent-forwarding)
+ [Legacy distributions](#legacy-distributions)
+ [Chained GnuPG agent forwarding](#chained-gnupg-agent-forwarding)
* [Using multiple YubiKeys](#using-multiple-yubikeys)
* [Email](#email)
+ [Thunderbird](#thunderbird)
+ [Mailvelope](#mailvelope)
+ [Mutt](#mutt)
* [Keyserver](#keyserver)
- [Updating keys](#updating-keys)
* [Renew Subkeys](#renew-subkeys)
* [Rotate Subkeys](#rotate-subkeys)
- [Reset YubiKey](#reset-yubikey)
- [Optional hardening](#optional-hardening)
* [Improving entropy](#improving-entropy)
* [Enable KDF](#enable-kdf)
* [Network considerations](#network-considerations)
- [Notes](#notes)
- [Troubleshooting](#troubleshooting)
- [Alternative solutions](#alternative-solutions)
- [Additional resources](#additional-resources)
# Purchase YubiKey
[All YubiKeys](https://www.yubico.com/store/compare/) *except* FIDO-only Security Key Series and Bio Series YubiKeys are compatible with this guide.
[Verify YubiKey](https://support.yubico.com/hc/en-us/articles/360013723419-How-to-Confirm-Your-Yubico-Device-is-Genuine) by visiting [yubico.com/genuine](https://www.yubico.com/genuine/). Select *Verify Device* to begin the process. Touch the YubiKey when prompted and allow the site to see the make and model of the device when prompted. This device attestation may help mitigate [supply chain attacks](https://media.defcon.org/DEF%20CON%2025/DEF%20CON%2025%20presentations/DEF%20CON%2025%20-%20r00killah-and-securelyfitz-Secure-Tokin-and-Doobiekeys.pdf).
Several portable storage devices (such as microSD cards) for storing encrypted backups are also recommended.
# Prepare environment
A dedicated, secure operating environment is recommended to generate cryptographic keys.
The following is a general ranking of environments least to most hospitable to generating materials:
1. Public, shared or other computer owned by someone else
1. Daily-use personal operating system with unrestricted network access
1. Virtualized operating system with limited capabilities (using [virt-manager](https://virt-manager.org/), VirtualBox or VMware, for example)
1. Dedicated and hardened [Debian](https://www.debian.org/) or [OpenBSD](https://www.openbsd.org/) installation
1. Ephemeral [Debian Live](https://www.debian.org/CD/live/) or [Tails](https://tails.boum.org/index.en.html) booted without primary storage attached
1. Hardened hardware and firmware (e.g., [Coreboot](https://www.coreboot.org/), [Intel ME removed](https://github.com/corna/me_cleaner))
1. Air-gapped system without network capabilities, preferably ARM-based Raspberry Pi or other architecturally diverse equivalent
Debian Live is used in this guide to balance usability and security, with some additional instructions for OpenBSD.
Download the latest Debian Live image and signature files:
```console
export IMAGE_URL="https://cdimage.debian.org/debian-cd/current-live/amd64/iso-hybrid/"
curl -fLO "$IMAGE_URL/SHA512SUMS" -O "$IMAGE_URL/SHA512SUMS.sign"
curl -fLO "$IMAGE_URL/$(awk '/xfce.iso$/ {print $2}' SHA512SUMS)"
```
Download the Debian signing public key:
```console
gpg --keyserver hkps://keyring.debian.org \
--recv DF9B9C49EAA9298432589D76DA87E80D6294BE9B
```
If the public key cannot be received, use a different keyserver or DNS server:
```console
gpg --keyserver hkps://keyserver.ubuntu.com:443 \
--recv DF9B9C49EAA9298432589D76DA87E80D6294BE9B
```
The Debian Live signing public key is also available for import in [pubkeys](https://github.com/drduh/YubiKey-Guide/tree/master/pubkeys):
```console
gpg --import pubkeys/debian-DA87E80D6294BE9B.asc
```
Verify the signature:
```console
gpg --verify SHA512SUMS.sign SHA512SUMS
```
`gpg: Good signature from "Debian CD signing key <debian-cd@lists.debian.org>"` must appear in the output.
Verify the cryptographic hash of the image file matches the one in the signed file:
```console
grep $(sha512sum debian-live-*-amd64-xfce.iso) SHA512SUMS
```
See [Verifying authenticity of Debian CDs](https://www.debian.org/CD/verify) for more information.
Connect a portable storage device and identify the disk label - this guide uses `/dev/sdc` throughout, but this value may differ on your system:
**Linux**
```console
$ sudo dmesg | tail
usb-storage 3-2:1.0: USB Mass Storage device detected
sd 2:0:0:0: [sdc] Attached SCSI removable disk
```
Copy the Debian image to the device:
```console
sudo dd if=debian-live-*-amd64-xfce.iso of=/dev/sdc bs=4M status=progress ; sync
```
**OpenBSD**
```console
$ dmesg | tail -n2
sd2 at scsibus4 targ 1 lun 0: <TS-RDF5, SD Transcend, TS3A> SCSI4 0/direct removable serial.0000000000000
sd2: 15193MB, 512 bytes/sector, 31116288 sectors
$ doas dd if=debian-live-*-amd64-xfce.iso of=/dev/rsd2c bs=4m
465+1 records in
465+1 records out
1951432704 bytes transferred in 139.125 secs (14026448 bytes/sec)
```
Power off, remove internal hard drives and all unnecessary devices, such as the wireless card.
# Install software
Load the operating system and configure networking. Optional hardening steps related to networking can be found [below](#network-considerations).
> [!TIP]
> If the screen locks on Debian Live, unlock with `user` / `live`
Open terminal and install required software packages.
**Debian/Ubuntu**
```console
sudo apt update
sudo apt -y upgrade
sudo apt -y install \
wget gnupg2 gnupg-agent dirmngr \
cryptsetup scdaemon pcscd \
yubikey-personalization yubikey-manager
```
**OpenBSD**
```console
doas pkg_add gnupg pcsc-tools
```
**macOS**
Download and install [Homebrew](https://brew.sh/) and the following packages:
```console
brew install \
gnupg yubikey-personalization ykman pinentry-mac wget
```
> [!NOTE]
> An additional Python package dependency may need to be installed to use [`ykman`](https://support.yubico.com/support/solutions/articles/15000012643-yubikey-manager-cli-ykman-user-guide) - `pip install yubikey-manager`
Or using [MacPorts](https://www.macports.org/install.php), install the following packages:
```console
sudo port install gnupg2 yubikey-manager pinentry wget
```
**NixOS**
Build an air-gapped NixOS LiveCD image:
```console
ref=$(git ls-remote https://github.com/drduh/Yubikey-Guide refs/heads/master | awk '{print $1}')
nix build --experimental-features "nix-command flakes" \
github:drduh/YubiKey-Guide/$ref?dir=nix#nixosConfigurations.yubikeyLive.x86_64-linux.config.system.build.isoImage
```
If you have this repository checked out:
Recommended, but optional: update `nixpkgs` and `drduh/config`:
```console
nix flake update --commit-lock-file
```
Build the image:
```console
nix build --experimental-features "nix-command flakes" nix#nixosConfigurations.yubikeyLive.x86_64-linux.config.system.build.isoImage
```
Copy to USB drive:
```console
sudo cp -v result/iso/yubikeyLive.iso /dev/sdc ; sync
```
Skip steps to create a temporary working directory and a hardened configuration, as they are already part of the image.
Test builds using virtualization tools like QEMU. Keep in mind a virtualized environment does not provide the same amount of security as an ephemeral system (see *Prepare environment* above).
Here is an example QEMU invocation after placing `yubikeyLive` in `result/iso` using the above `nix build` command:
```console
# Launch with 4G memory, 2 CPUs and KVM enabled
qemu-system-x86_64 \
-enable-kvm \
-m 4G \
-smp 2 \
-drive readonly=on,media=cdrom,format=raw,file=result/iso/yubikeyLive.iso
```
**Arch**
```console
sudo pacman -Syu --needed gnupg pcsclite ccid yubikey-personalization
```
**RHEL7**
```console
sudo yum install -y gnupg2 pinentry-curses pcsc-lite pcsc-lite-libs gnupg2-smime
```
**Fedora**
```console
sudo dnf install --skip-unavailable \
wget gnupg2 \
cryptsetup gnupg2-scdaemon pcsc-lite \
yubikey-personalization-gui yubikey-manager
```
# Prepare GnuPG
Create a temporary directory which will be cleared on [reboot](https://en.wikipedia.org/wiki/Tmpfs) and set it as the GnuPG directory:
```console
export GNUPGHOME=$(mktemp -d -t $(date +%Y.%m.%d)-XXXX)
```
## Configuration
Create or import a [hardened configuration](https://github.com/drduh/YubiKey-Guide/blob/master/config/gpg.conf):
```console
cd $GNUPGHOME
wget https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/config/gpg.conf
```
The options will look similar to:
```console
$ grep -v "^#" $GNUPGHOME/gpg.conf
personal-cipher-preferences AES256 AES192 AES
personal-digest-preferences SHA512 SHA384 SHA256
personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed
cert-digest-algo SHA512
s2k-digest-algo SHA512
s2k-cipher-algo AES256
charset utf-8
no-comments
no-emit-version
no-greeting
keyid-format 0xlong
list-options show-uid-validity
verify-options show-uid-validity
with-fingerprint
require-cross-certification
require-secmem
no-symkey-cache
armor
use-agent
throw-keyids
```
> [!IMPORTANT]
> Networking should be disabled for the remainder of the setup.
## Identity
When creating an identity with GnuPG, the default options ask for a "Real name", "Email address" and optional "Comment".
Depending on how you plan to use GnuPG, set these values respectively[^1]:
```console
export IDENTITY="YubiKey User <yubikey@example.domain>"
```
Or use any attribute which will uniquely identity the key (this may be incompatible with certain use cases):
```console
export IDENTITY="My Cool YubiKey - 2025"
```
## Key
Set the algorithm and key size - RSA/4096 is recommended:
```console
export KEY_TYPE=rsa4096
```
## Expiration
Determine the desired Subkey validity duration.
Setting a Subkey expiry forces identity and credential lifecycle management. However, setting an expiry on the Certify key is pointless, because it can just be used to extend itself[^2].
This guide recommends a two year expiration for Subkeys to balance security and usability, however longer durations are possible to reduce maintenance frequency.
When Subkeys expire, they may still be used to decrypt with GnuPG and authenticate with SSH, however they can **not** be used to encrypt nor sign new messages.
Subkeys must be renewed or rotated using the Certify key - see [Updating keys](#updating-keys).
Set Subkeys to expire on a planned date:
```console
export EXPIRATION=2027-07-01
```
The expiration date may also be relative, for example set to two years from today:
```console
export EXPIRATION=2y
```
## Passphrase
Generate a passphrase for the Certify key. This credential will be used to manage identity Subkeys.
To improve readability, this guide recommends a passphrase consisting only of uppercase letters and numbers.
The following commands will generate a strong[^3] passphrase while avoiding certain similar-looking characters:
```console
export CERTIFY_PASS=$(LC_ALL=C tr -dc "A-Z2-9" < /dev/urandom | \
tr -d "IOUS5" | \
fold -w ${PASS_GROUPSIZE:-4} | \
paste -sd ${PASS_DELIMITER:--} - | \
head -c ${PASS_LENGTH:-29})
printf "\n$CERTIFY_PASS\n\n"
```
To change the passphrase length, delimiting character or group sizes, export the respective variable(s) prior to running the passphrase generation command, for example:
```console
export PASS_GROUPSIZE=6
export PASS_DELIMITER=+
export PASS_LENGTH=48
```
Write the passphrase in a secure location - separate from the portable storage device used for key material, or memorize it.
This repository includes a [`passphrase.html`](https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/templates/passphrase.html) template to help with credential transcription. Save the [raw file](https://github.com/drduh/YubiKey-Guide/raw/refs/heads/master/templates/passphrase.html), open in a browser to render and print.
Mark the corresponding character on sequential rows for each character in the passphrase. [`passphrase.txt`](https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/templates/passphrase.txt) can also be printed without a browser:
```console
lp -d Printer-Name passphrase.txt
```
[Diceware](https://secure.research.vt.edu/diceware) is another popular method for creating memorable passphrases.
# Create Certify key
The primary key to generate is the Certify key, which is responsible for issuing Subkeys for encryption, signature and authentication operations.
The Certify key should be kept offline at all times and only accessed from a dedicated and secure environment to issue or revoke Subkeys.
Do not set an expiration date on the Certify key.
Generate the Certify key:
```console
echo "$CERTIFY_PASS" | \
gpg --batch --passphrase-fd 0 \
--quick-generate-key "$IDENTITY" "$KEY_TYPE" cert never
```
Set and view the Certify key identifier and fingerprint for use later:
```console
export KEYID=$(gpg -k --with-colons "$IDENTITY" | \
awk -F: '/^pub:/ { print $5; exit }')
export KEYFP=$(gpg -k --with-colons "$IDENTITY" | \
awk -F: '/^fpr:/ { print $10; exit }')
printf "\nKey ID/Fingerprint: %20s\n%s\n\n" "$KEYID" "$KEYFP"
```
<details>
<summary>Add additional IDs (optional)</summary>
This is an optional step for use cases requiring [additional identities](https://github.com/drduh/YubiKey-Guide/issues/445), for example:
- different email addresses for different languages
- different email addresses for professional versus personal but please see alternative reason below for not tying these addresses together
- anonymized email addresses for different git providers
An alternative would be to have distinct keys but you would then require multiple YubiKeys, as each can only hold a single key for each type (signing, encryption, authentication). Nevertheless, there can be good reasons to have multiple YubiKeys:
- if you have different email addresses for professional versus personal use cases, having distinct keys allow you to disassociate the identities
- if you are also using the YubiKey as a U2F or FIDO2 device, having multiple YubiKeys is generally recommended as a backup measure
Define an array containing additional user IDs. Each array element must be wrapped in quotes and each element must be space-delimited:
```console
declare -a additional_uids
additional_uids=("Super Cool YubiKey 2025" "uid 1 <uid1@example.org>")
```
Add the additional user IDs to the Certify key:
```console
for uid in "${additional_uids[@]}" ; do \
echo "$CERTIFY_PASS" | \
gpg --batch --passphrase-fd 0 \
--pinentry-mode=loopback --quick-add-uid "$KEYFP" "$uid"
done
```
Adjust the trust of the additional IDs to *ultimate*:
```console
gpg --command-fd=0 --pinentry-mode=loopback --edit-key "$KEYID" <<EOF
uid *
trust
5
y
save
EOF
```
</details>
# Create Subkeys
Generate Signature and Encryption Subkeys using the previously configured key type, passphrase and expiration:
```console
echo "$CERTIFY_PASS" | \
gpg --batch --pinentry-mode=loopback --passphrase-fd 0 \
--quick-add-key "$KEYFP" "$KEY_TYPE" sign "$EXPIRATION"
echo "$CERTIFY_PASS" | \
gpg --batch --pinentry-mode=loopback --passphrase-fd 0 \
--quick-add-key "$KEYFP" "$KEY_TYPE" encrypt "$EXPIRATION"
```
Followed by the Authentication Subkey:
> [!NOTE]
> Some systems no longer accept RSA for SSH authentication; to use [Ed25519](https://ed25519.cr.yp.to/), set the `KEY_TYPE` variable to `ed25519` before generating Authentication Subkey.
```console
echo "$CERTIFY_PASS" | \
gpg --batch --pinentry-mode=loopback --passphrase-fd 0 \
--quick-add-key "$KEYFP" "$KEY_TYPE" auth "$EXPIRATION"
```
# Verify keys
List available secret keys:
```console
gpg -K
```
The output will display **[C]ertify, [S]ignature, [E]ncryption and [A]uthentication** keys:
```console
sec rsa4096/0xF0F2CFEB04341FB5 2025-07-01 [C]
Key fingerprint = 4E2C 1FA3 372C BA96 A06A C34A F0F2 CFEB 0434 1FB5
uid [ultimate] YubiKey User <yubikey@example>
ssb rsa4096/0xB3CD10E502E19637 2025-07-01 [S] [expires: 2027-07-01]
ssb rsa4096/0x30CBE8C4B085B9F7 2025-07-01 [E] [expires: 2027-07-01]
ssb rsa4096/0xAD9E24E1B8CB9600 2025-07-01 [A] [expires: 2027-07-01]
```
# Backup keys
Save a copy of the Certify key, Subkeys and public key:
```console
echo "$CERTIFY_PASS" | \
gpg --output $GNUPGHOME/$KEYID-Certify.key \
--batch --pinentry-mode=loopback --passphrase-fd 0 \
--armor --export-secret-keys $KEYID
echo "$CERTIFY_PASS" | \
gpg --output $GNUPGHOME/$KEYID-Subkeys.key \
--batch --pinentry-mode=loopback --passphrase-fd 0 \
--armor --export-secret-subkeys $KEYID
gpg --output $GNUPGHOME/$KEYID-$(date +%F).asc \
--armor --export $KEYID
```
Create an **encrypted** backup on portable storage to be kept offline in a secure and durable location.
The following process is recommended to be repeated several times on multiple portable storage devices, as they are likely to fail over time. As an additional backup measure, [Paperkey](https://www.jabberwocky.com/software/paperkey/) can be used to make a physical copy of key materials for improved durability.
> [!TIP]
> [ext2](https://en.wikipedia.org/wiki/Ext2) volumes (without encryption) can be mounted on Linux and OpenBSD.
> Use [FAT32](https://en.wikipedia.org/wiki/Fat32) or [NTFS](https://en.wikipedia.org/wiki/Ntfs) volumes for macOS and Windows compatibility instead.
**Linux**
Attach a portable storage device and check its label, in this case `/dev/sdc`:
```console
$ sudo dmesg | tail
usb-storage 3-2:1.0: USB Mass Storage device detected
sd 2:0:0:0: [sdc] Attached SCSI removable disk
$ sudo fdisk -l /dev/sdc
Disk /dev/sdc: 14.9 GiB, 15931539456 bytes, 31116288 sectors
```
> [!CAUTION]
> Confirm the destination (`of`) before issuing the following command - it is destructive! This guide uses `/dev/sdc` throughout, but this value may be different on your system.
Zero the header to prepare for encryption:
```console
sudo dd if=/dev/zero of=/dev/sdc bs=4M count=1
```
Remove and re-connect the storage device.
Erase and create a new partition table:
```console
sudo fdisk /dev/sdc <<EOF
g
w
EOF
```
Create a small (at least 20 Mb is recommended to account for the LUKS header size) partition for storing secret materials:
```console
sudo fdisk /dev/sdc <<EOF
n
+20M
w
EOF
```
Use [LUKS](https://dys2p.com/en/2023-05-luks-security.html) to encrypt the new partition.
Generate another unique [Passphrase](#passphrase) (ideally different from the one used for the Certify key) to protect the encrypted volume:
```console
export LUKS_PASS=$(LC_ALL=C tr -dc "A-Z2-9" < /dev/urandom | \
tr -d "IOUS5" | \
fold -w ${PASS_GROUPSIZE:-4} | \
paste -sd ${PASS_DELIMITER:--} - | \
head -c ${PASS_LENGTH:-29})
printf "\n$LUKS_PASS\n\n"
```
This passphrase will also be used infrequently to access the Certify key and should be very strong.
Write the passphrase down or memorize it.
Format the partition:
```console
echo $LUKS_PASS | \
sudo cryptsetup -q luksFormat /dev/sdc1
```
Mount the partition:
```console
echo $LUKS_PASS | \
sudo cryptsetup -q luksOpen /dev/sdc1 gnupg-secrets
```
Create an ext2 filesystem:
```console
sudo mkfs.ext2 /dev/mapper/gnupg-secrets -L gnupg-$(date +%F)
```
Mount the filesystem and copy the temporary GnuPG working directory with key materials:
```console
sudo mkdir -p /mnt/encrypted-storage
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
sudo cp -av $GNUPGHOME /mnt/encrypted-storage/
```
Unmount and close the encrypted volume:
```console
sudo umount /mnt/encrypted-storage
sudo cryptsetup luksClose gnupg-secrets
```
Repeat the process for any additional storage devices (at least two are recommended).
**OpenBSD**
Attach a USB disk and determine its label:
```console
$ dmesg | grep sd.\ at
sd2 at scsibus5 targ 1 lun 0: <TS-RDF5, SD Transcend, TS37> SCSI4 0/direct removable serial.00000000000000000000
```
Print the existing partitions to make sure it's the right device:
```console
doas disklabel -h sd2
```
Initialize the disk by creating an `a` partition with FS type `RAID` and size of 25 Megabytes:
```console
$ doas fdisk -giy sd2
Writing MBR at offset 0.
Writing GPT.
$ doas disklabel -E sd2
Label editor (enter '?' for help at any prompt)
sd2> a a
offset: [64]
size: [31101776] 25M
FS type: [4.2BSD] RAID
sd2*> w
sd2> q
No label changes
```
Encrypt with bioctl using a unique [Passphrase](#passphrase):
```console
$ doas bioctl -c C -l sd2a softraid0
New passphrase:
Re-type passphrase:
softraid0: CRYPTO volume attached as sd3
```
Create an `i` partition on the new crypto volume and the filesystem:
```console
$ doas fdisk -giy sd3
Writing MBR at offset 0.
Writing GPT.
$ doas disklabel -E sd3
Label editor (enter '?' for help at any prompt)
sd3> a i
offset: [64]
size: [16001]
FS type: [4.2BSD]
sd3*> w
sd3> q
No label changes.
$ doas newfs sd3i
```
Mount the filesystem and copy the temporary directory with the keyring:
```console
doas mkdir -p /mnt/encrypted-storage
doas mount /dev/sd3i /mnt/encrypted-storage
doas cp -av $GNUPGHOME /mnt/encrypted-storage
```
Unmount and remove the encrypted volume:
```console
doas umount /mnt/encrypted-storage
doas bioctl -d sd3
```
See [OpenBSD FAQ#14](https://www.openbsd.org/faq/faq14.html#softraidCrypto) for more information.
# Export public key
> [!IMPORTANT]
> Without the public key, it will **not** be possible to use GnuPG to decrypt/sign messages. However, YubiKey can still be used for SSH authentication.
Connect another portable storage device or create a new partition on the existing one.
**Linux**
Using the same `/dev/sdc` device as in the previous step, create a small (at least 20 Mb is recommended) partition for storing materials:
```console
sudo fdisk /dev/sdc <<EOF
n
+20M
w
EOF
```
Create a filesystem and export the public key:
```console
sudo mkfs.ext2 /dev/sdc2
sudo mkdir -p /mnt/public
sudo mount /dev/sdc2 /mnt/public
gpg --armor --export $KEYID | sudo tee /mnt/public/$KEYID-$(date +%F).asc
sudo chmod 0444 /mnt/public/*.asc
```
Unmount and remove the storage device:
```console
sudo umount /mnt/public
```
**OpenBSD**
```console
$ doas disklabel -E sd2
Label editor (enter '?' for help at any prompt)
sd2> a b
offset: [32130]
size: [31069710] 25M
FS type: [swap] 4.2BSD
sd2*> w
sd2> q
No label changes.
```
Create a filesystem and export the public key to it:
```console
doas newfs sd2b
doas mkdir -p /mnt/public
doas mount /dev/sd2b /mnt/public
gpg --armor --export $KEYID | doas tee /mnt/public/$KEYID-$(date +%F).asc
```
Unmount and remove the storage device:
```console
doas umount /mnt/public
```
# Configure YubiKey
Connect YubiKey and confirm its status:
```console
gpg --card-status
```
If the YubiKey is locked, [Reset](#reset-yubikey) it.
## Change PIN
YubiKey's [PGP](https://developers.yubico.com/PGP/) interface has its own PINs separate from other modules such as [PIV](https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html):
Name | Default | Capability
:---: | :---: | ---
User PIN | `123456` | cryptographic operations (decrypt, sign, authenticate)
Admin PIN | `12345678` | reset PIN, change Reset Code, add keys and owner information
Reset Code | None | reset PIN ([more information](https://forum.yubico.com/viewtopicd01c.html?p=9055#p9055))
Determine the desired PIN values. They can be shorter than the Certify key passphrase due to limited brute-forcing opportunities; the User PIN should be convenient enough to remember for every-day use.
The *User PIN* must be at least 6 characters and the *Admin PIN* must be at least 8 characters. A maximum of 127 ASCII characters are allowed. See [Managing PINs](https://www.gnupg.org/howtos/card-howto/en/ch03s02.html) for more information.
Set PIN values, for example a 6 digit User PIN and 8 digit Admin PIN:
```console
export ADMIN_PIN=$(LC_ALL=C tr -dc '0-9' < /dev/urandom | \
fold -w8 | head -1)
export USER_PIN=$(LC_ALL=C tr -dc '0-9' < /dev/urandom | \
fold -w6 | head -1)
printf "\nAdmin PIN: %12s\nUser PIN: %13s\n\n" \
"$ADMIN_PIN" "$USER_PIN"
```
Change the Admin PIN:
```console
gpg --command-fd=0 --pinentry-mode=loopback --change-pin <<EOF
3
12345678
$ADMIN_PIN
$ADMIN_PIN
q
EOF
```
Change the User PIN:
```console
gpg --command-fd=0 --pinentry-mode=loopback --change-pin <<EOF
1
123456
$USER_PIN
$USER_PIN
q
EOF
```
Remove and re-insert YubiKey.
> [!CAUTION]
> Three incorrect *User PIN* entries will cause it to become blocked and must be unblocked with either the *Admin PIN* or *Reset Code*. Three incorrect *Admin PIN* or *Reset Code* entries will destroy data on YubiKey.
The number of [retry attempts](https://docs.yubico.com/software/yubikey/tools/ykman/OpenPGP_Commands.html#ykman-openpgp-access-set-retries-options-pin-retries-reset-code-retries-admin-pin-retries) can be changed, for example to 5 attempts:
```console
ykman openpgp access set-retries 5 5 5 -f -a $ADMIN_PIN
```
## Set attributes
Use previously set values:
```console
gpg --command-fd=0 --pinentry-mode=loopback --edit-card <<EOF
admin
login
$IDENTITY
$ADMIN_PIN
quit
EOF
```
[Smart card attributes](https://gnupg.org/howtos/card-howto/en/smartcard-howto-single.html) can also be set with `gpg --edit-card` and `admin` mode. Use `help` to see available options. The `login` attribute is [required](https://github.com/drduh/YubiKey-Guide/issues/461).
Run `gpg --card-status` to verify results (*Login data* field).
# Transfer Subkeys
> [!IMPORTANT]
> Transferring keys to YubiKey converts the on-disk key into a "stub" - making it no longer usable to transfer to subsequent YubiKeys. Ensure keys were backed up before proceeding.
The Certify key passphrase and Admin PIN are required to transfer keys.
## Signature key
Transfer the Signature key:
```console
gpg --command-fd=0 --pinentry-mode=loopback --edit-key $KEYID <<EOF
key 1
keytocard
1
$CERTIFY_PASS
$ADMIN_PIN
save
EOF
```
## Encryption key
Repeat the process for the Encryption key:
```console
gpg --command-fd=0 --pinentry-mode=loopback --edit-key $KEYID <<EOF
key 2
keytocard
2
$CERTIFY_PASS
$ADMIN_PIN
save
EOF
```
## Authentication key
Repeat the process for the Authentication key:
```console
gpg --command-fd=0 --pinentry-mode=loopback --edit-key $KEYID <<EOF
key 3
keytocard
3
$CERTIFY_PASS
$ADMIN_PIN
save
EOF
```
# Verify transfer
Verify Subkeys are on YubiKey with `gpg -K` - indicated by `ssb>`:
```console
sec rsa4096/0xF0F2CFEB04341FB5 2025-07-01 [C]
Key fingerprint = 4E2C 1FA3 372C BA96 A06A C34A F0F2 CFEB 0434 1FB5
uid [ultimate] YubiKey User <yubikey@example>
ssb> rsa4096/0xB3CD10E502E19637 2025-07-01 [S] [expires: 2027-07-01]
ssb> rsa4096/0x30CBE8C4B085B9F7 2025-07-01 [E] [expires: 2027-07-01]
ssb> rsa4096/0xAD9E24E1B8CB9600 2025-07-01 [A] [expires: 2027-07-01]
```
The `>` after a tag indicates the key is stored on a smart card.
# Finish setup
Verify the following steps were performed correctly:
- [ ] Memorized or wrote down the Certify key (identity) passphrase to a secure and durable location
* `echo $CERTIFY_PASS` to see it again; [`passphrase.html`](https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/templates/passphrase.html) or [`passphrase.txt`](https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/templates/passphrase.txt) to transcribe it
- [ ] Memorized or wrote down passphrase to encrypted volume on portable storage
* `echo $LUKS_PASS` to see it again; [`passphrase.html`](https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/templates/passphrase.html) or [`passphrase.txt`](https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/templates/passphrase.txt) to transcribe it
- [ ] Saved the Certify key and Subkeys to encrypted portable storage, to be kept offline
* At least two backups are recommended, stored at separate locations
- [ ] Exported a copy of the public key where is can be easily accessed later
* Separate device or non-encrypted partition was used
- [ ] Memorized or wrote down the User PIN and Admin PIN, which are unique and changed from default values
* `echo $USER_PIN $ADMIN_PIN` to see them again; [`passphrase.html`](https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/templates/passphrase.html) or [`passphrase.txt`](https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/templates/passphrase.txt) to transcribe them
- [ ] Moved Encryption, Signature and Authentication Subkeys to YubiKey
* `gpg -K` shows `ssb>` for each of the 3 Subkeys
Reboot, clearing the ephemeral environment, to complete setup.
# Using YubiKey
Initialize GnuPG:
```console
gpg -k
```
Create or import a [hardened configuration](https://github.com/drduh/YubiKey-Guide/blob/master/config/gpg.conf):
```console
cd ~/.gnupg
wget https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/config/gpg.conf
```
Set the following option. This avoids the problem where GnuPG will repeatedly prompt for the insertion of an already-inserted YubiKey:
```console
touch scdaemon.conf
echo "disable-ccid" >>scdaemon.conf
```
Install the required packages:
**Debian/Ubuntu**
```console
sudo apt update
sudo apt install -y gnupg gnupg-agent scdaemon pcscd
```
**Arch**
```console
sudo pacman -S --needed gnupg pcsc-tools
sudo systemctl enable --now pcscd.service
```
**macOS**
```console
brew install gnupg
```
Or using MacPorts
```console
sudo port install gnupg2 pcsc-tools
```
**OpenBSD**
```console
doas pkg_add gnupg pcsc-tools
doas rcctl enable pcscd
doas reboot
```
Mount the non-encrypted volume with the public key:
**Debian/Ubuntu**
```console
sudo mkdir -p /mnt/public
sudo mount /dev/sdc2 /mnt/public
```
**OpenBSD**
```console
doas mkdir -p /mnt/public
doas mount /dev/sd3i /mnt/public
```
Import the public key:
```console
gpg --import /mnt/public/*.asc
```
Or download the public key from a keyserver:
```console
gpg --recv $KEYID
```
Or with the URL on YubiKey, retrieve the public key using the command `gpg --edit-card`.
```console
gpg/card> fetch
gpg/card> quit
```
Determine the key ID:
```console
gpg -k
export KEYID=0xF0F2CFEB04341FB5
```
Assign ultimate trust by typing `trust` and selecting option `5` then `quit`:
```console
gpg --command-fd=0 --pinentry-mode=loopback --edit-key $KEYID <<EOF
trust
5
y
save
EOF
```
Remove and re-insert YubiKey.
Verify the status with `gpg --card-status` which will list the available Subkeys:
```console
Reader ...........: Yubico YubiKey OTP FIDO CCID 00 00
Application ID ...: D2760001240102010006055532110000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 05553211
Name of cardholder: YubiKey User
Language prefs ...: en
Salutation .......:
URL of public key : [not set]
Login data .......: yubikey@example
Signature PIN ....: not forced
Key attributes ...: rsa4096 rsa4096 rsa4096
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 0
KDF setting ......: on
Signature key ....: CF5A 305B 808B 7A0F 230D A064 B3CD 10E5 02E1 9637
created ....: 2025-07-01 12:00:00
Encryption key....: A5FA A005 5BED 4DC9 889D 38BC 30CB E8C4 B085 B9F7
created ....: 2025-07-01 12:00:00
Authentication key: 570E 1355 6D01 4C04 8B6D E2A3 AD9E 24E1 B8CB 9600
created ....: 2025-07-01 12:00:00
General key info..: sub rsa4096/0xB3CD10E502E19637 2025-07-01 YubiKey User <yubikey@example>
sec# rsa4096/0xF0F2CFEB04341FB5 created: 2025-07-01 expires: never
ssb> rsa4096/0xB3CD10E502E19637 created: 2025-07-01 expires: 2027-07-01
card-no: 0006 05553211
ssb> rsa4096/0x30CBE8C4B085B9F7 created: 2025-07-01 expires: 2027-07-01
card-no: 0006 05553211
ssb> rsa4096/0xAD9E24E1B8CB9600 created: 2025-07-01 expires: 2027-07-01
card-no: 0006 05553211
```
`sec#` indicates the corresponding key is not available (the Certify key is offline).
YubiKey is now ready for use!
## Encryption
Encrypt a message to yourself (useful for storing credentials or protecting backups):
```console
echo -e "\ntest message string" | \
gpg --encrypt --armor \
--recipient $KEYID --output encrypted.txt
```
Decrypt the message - a prompt for the User PIN will appear:
```console
gpg --decrypt --armor encrypted.txt
```
To encrypt to multiple recipients/keys, set the preferred key ID last:
```console
echo "test message string" | \
gpg --encrypt --armor \
--recipient $KEYID_2 --recipient $KEYID_1 --recipient $KEYID \
--output encrypted.txt
```
Use a [shell function](https://github.com/drduh/config/blob/main/zshrc) to make encrypting files easier:
```console
secret () {
output="${1}".$(date +%s).enc
gpg --encrypt --armor --output ${output} \
-r $KEYID "${1}" && echo "${1} -> ${output}"
}
reveal () {
output=$(echo "${1}" | rev | cut -c16- | rev)
gpg --decrypt --output ${output} "${1}" && \
echo "${1} -> ${output}"
}
```
Example output:
```console
$ secret document.pdf
document.pdf -> document.pdf.1580000000.enc
$ reveal document.pdf.1580000000.enc
gpg: anonymous recipient; trying secret key 0xF0F2CFEB04341FB5 ...
gpg: okay, we are the anonymous recipient.
gpg: encrypted with RSA key, ID 0x0000000000000000
document.pdf.1580000000.enc -> document.pdf
```
[drduh/Purse](https://github.com/drduh/Purse) is a password manager based on GnuPG and YubiKey to securely store and use credentials.
## Signature
Sign a message:
```console
echo "test message string" | gpg --armor --clearsign > signed.txt
```
Verify the signature:
```console
gpg --verify signed.txt
```
The output will be similar to:
```console
gpg: Signature made Mon 01 Jan 2025 12:00:00 PM UTC
gpg: using RSA key CF5A305B808B7A0F230DA064B3CD10E502E19637
gpg: Good signature from "YubiKey User <yubikey@example>" [ultimate]
Primary key fingerprint: 4E2C 1FA3 372C BA96 A06A C34A F0F2 CFEB 0434 1FB5
Subkey fingerprint: CF5A 305B 808B 7A0F 230D A064 B3CD 10E5 02E1 9637
```
## Configure touch
By default, YubiKey will perform cryptographic operations without requiring any action from the user after the key is unlocked once with the PIN.
To require a touch for each key operation, use [YubiKey Manager](https://developers.yubico.com/yubikey-manager/) and the Admin PIN to set key policy.
Encryption:
```console
ykman openpgp keys set-touch dec on
```
> [!NOTE]
> YubiKey Manager prior to versions 5.1.0 use `enc` instead of `dec` for encryption:
```console
ykman openpgp keys set-touch enc on
```
Even older versions of YubiKey Manager use `touch` instead of `set-touch`
Signature:
```console
ykman openpgp keys set-touch sig on
```
Authentication:
```console
ykman openpgp keys set-touch aut on
```
To view and adjust policy options:
```console
ykman openpgp keys set-touch -h
```
`Cached` or `Cached-Fixed` may be desirable for YubiKey use with email clients.
YubiKey will blink when it is waiting for a touch. On Linux, [maximbaz/yubikey-touch-detector](https://github.com/maximbaz/yubikey-touch-detector) can be used to indicate YubiKey is waiting for a touch.
## SSH
Create or import a [hardened configuration](https://github.com/drduh/YubiKey-Guide/blob/master/config/gpg-agent.conf):
```console
cd ~/.gnupg
wget https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/config/gpg-agent.conf
```
> [!NOTE]
> `cache-ttl` options do **not** apply when using YubiKey as a smart card, because the PIN is [cached by the smart card itself](https://dev.gnupg.org/T3362). To clear the PIN from cache (equivalent to `default-cache-ttl` and `max-cache-ttl`), remove YubiKey, or set `forcesig` when editing the card to be prompted for the PIN each time.
> [!TIP]
> Set `pinentry-program` to `/usr/bin/pinentry-gnome3` for a GUI-based prompt.
**macOS**
Install pinentry with `brew install pinentry-mac` or `sudo port install pinentry` then edit `gpg-agent.conf` to set the `pinentry-program` path to:
* Apple Silicon Macs: `/opt/homebrew/bin/pinentry-mac`
* Intel Macs: `/usr/local/bin/pinentry-mac` or `/opt/local/bin/pinentry` (MacPorts)
* MacGPG Suite: `/usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac`
Then run `gpgconf --kill gpg-agent` for the change to take effect.
To use graphical applications on macOS, [additional setup is required](https://jms1.net/yubikey/make-ssh-use-gpg-agent.md).
Create `$HOME/Library/LaunchAgents/gnupg.gpg-agent.plist` with the following contents:
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>gnupg.gpg-agent</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
<key>ProgramArguments</key>
<array>
<string>/usr/local/MacGPG2/bin/gpg-connect-agent</string>
<string>/bye</string>
</array>
</dict>
</plist>
```
Load it:
```console
launchctl load $HOME/Library/LaunchAgents/gnupg.gpg-agent.plist
```
Create `$HOME/Library/LaunchAgents/gnupg.gpg-agent-symlink.plist` with the following contens:
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/ProperyList-1.0/dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>gnupg.gpg-agent-symlink</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/ln -sf $HOME/.gnupg/S.gpg-agent.ssh $SSH_AUTH_SOCK</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
```
Load it:
```console
launchctl load $HOME/Library/LaunchAgents/gnupg.gpg-agent-symlink.plist
```
Reboot to activate changes.
**Windows**
Windows can already have some virtual smart card readers installed, like the one provided for Windows Hello. To verify YubiKey is the correct one used by scdaemon, add it to its configuration.
Find the YubiKey label using PowerShell:
```powershell
PS C:\WINDOWS\system32> Get-PnpDevice -Class SoftwareDevice | Where-Object {$_.FriendlyName -like "*YubiKey*"} | Select-Object -ExpandProperty FriendlyName
Yubico YubiKey OTP+FIDO+CCID 0
```
See [How to setup Signed Git Commits with a YubiKey NEO and GPG and Keybase on Windows (2018)](https://www.hanselman.com/blog/HowToSetupSignedGitCommitsWithAYubiKeyNEOAndGPGAndKeybaseOnWindows.aspx) for more information.
Edit `%APPDATA%/gnupg/scdaemon.conf` to add:
```console
reader-port <device name, e.g. Yubico YubiKey OTP+FIDO+CCID 0>
```
Edit `%APPDATA%/gnupg/gpg-agent.conf` to add:
```console
enable-ssh-support
enable-putty-support
```
Restart the agent:
```console
gpg-connect-agent killagent /bye
gpg-connect-agent /bye
```
Verify YubiKey details:
```console
gpg --card-status
```
Import the public key and set ultimate trust:
```console
gpg --import <path to public key file>
```
Retrieve the public key id:
```console
gpg --list-public-keys
```
Export the SSH public key:
```console
gpg --export-ssh-key <public key id>
```
Copy the public SSH key to a file - it corresponds to the secret key on YubiKey and can be copied to SSH destination hosts.
Create a shortcut that points to `gpg-connect-agent /bye` and place it in the startup folder `shell:startup` to make sure the agent starts after reboot. Modify the shortcut properties so it starts in a "Minimized" window.
PuTTY can now be used for public-key SSH authentication. When the server asks for public-key verification, PuTTY will forward the request to GnuPG, which will prompt for a PIN to authorize the operation.
**WSL**
The goal is to configure SSH client inside WSL work together with the Windows agent, such as gpg-agent.exe.
See the [WSL agent architecture](media/schema_gpg.png) illustration for an overview.
GnuPG forwarding for cryptographic operations is not supported. See [vuori/weasel-pageant](https://github.com/vuori/weasel-pageant) for more information.
One way to forward is just `ssh -A` (still need to eval weasel to setup local ssh-agent), and only relies on OpenSSH. In this track, `ForwardAgent` and `AllowAgentForwarding` in ssh/sshd config may be involved. However, when using ssh socket forwarding, do not enable `ForwardAgent` in ssh config. See [SSH Agent Forwarding](#ssh-agent-forwarding) for more information. This requires Ubuntu 16.04 or newer for WSL and Kleopatra.
Download [vuori/weasel-pageant](https://github.com/vuori/weasel-pageant).
Add `eval $(/mnt/c/<path of extraction>/weasel-pageant -r -a /tmp/S.weasel-pageant)` to the shell rc file. Use a named socket here so it can be used in the `RemoteForward` directive of `~/.ssh/config`. Source it with `source ~/.bashrc`.
Display the SSH key with `$ ssh-add -l`
Edit `~/.ssh/config` to add the following for each agent forwarding host:
```console
RemoteForward <remote SSH socket path> /tmp/S.weasel-pageant
```
The remote SSH socket path can be found with `gpgconf --list-dirs agent-ssh-socket`
Add the following to the shell rc file:
```console
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
```
Add the following to `/etc/ssh/sshd_config`:
```console
StreamLocalBindUnlink yes
```
Reload SSH daemon:
```console
sudo service sshd reload
```
Remove YubiKey and reboot. Log back into Windows, open a WSL console and enter `ssh-add -l` - no output should appear.
Plug in YubiKey, enter the same command to display the ssh key.
Connect to the remote host and use `ssh-add -l` to confirm forwarding works.
Agent forwarding may be chained through multiple hosts. Follow the same [protocol](#remote-host-configuration) to configure each host.
An alternate method is the [usbipd-win](https://github.com/dorssel/usbipd-win) library. If you encounter issues with accessing the YubiKey in WSL after configuring usbipd-win, you may need to add custom polkit rules to ensure proper permissions for the pcscd service. Here's an example configuration using a scard group (the group logic is optional):
Create a new rule file at /etc/polkit-1/rules.d/99-pcscd.rules:
```bash
polkit.addRule(function(action, subject) {
if (action.id == "org.debian.pcsc-lite.access_card" &&
subject.isInGroup("scard")) {
return polkit.Result.YES;
}
});
polkit.addRule(function(action, subject) {
if (action.id == "org.debian.pcsc-lite.access_pcsc" &&
subject.isInGroup("scard")) {
return polkit.Result.YES;
}
});
```
### Replace agents
To launch `gpg-agent` for use by SSH, use the `gpg-connect-agent /bye` or `gpgconf --launch gpg-agent` commands.
Add the following to the shell rc file:
```console
export GPG_TTY=$(tty)
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent
gpg-connect-agent updatestartuptty /bye > /dev/null
```
For fish, `config.fish` should look like this (consider putting them into the `is-interactive` block):
```fish
set -x GPG_TTY (tty)
set -x SSH_AUTH_SOCK (gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent
```
When using `ForwardAgent` for ssh-agent forwarding, `SSH_AUTH_SOCK` only needs to be set on the *local* host, where YubiKey is connected. On the *remote* host, `ssh` will set `SSH_AUTH_SOCK` to something like `/tmp/ssh-mXzCzYT2Np/agent.7541` upon connection. Do **not** set `SSH_AUTH_SOCK` on the remote host - doing so will break [SSH Agent Forwarding](#ssh-agent-forwarding).
For `S.gpg-agent.ssh` (see [SSH Agent Forwarding](#ssh-agent-forwarding) for more info), `SSH_AUTH_SOCK` should also be set on the *remote*. However, `GPG_TTY` should not be set on the *remote*, explanation specified in that section.
### Copy public key
> [!TIP]
> It is **not** necessary to import the GnuPG public key in order to use SSH only.
Copy and paste the output from `ssh-add` to the server's `authorized_keys` file:
```console
$ ssh-add -L
ssh-rsa AAAAB4NzaC1yc2EAAAADAQABAAACAz[...]zreOKM+HwpkHzcy9DQcVG2Nw== cardno:000605553211
```
**Optional** Save the public key for identity file configuration. By default, SSH attempts to use all the identities available via the agent. It's often a good idea to manage exactly which keys SSH will use to connect to a server, for example to separate different roles or [to avoid being fingerprinted by untrusted ssh servers](https://words.filippo.io/ssh-whoami-filippo-io/). To do this you'll need to use the command line argument `-i [identity_file]` or the `IdentityFile` and `IdentitiesOnly` options in `.ssh/config`.
The argument provided to `IdentityFile` is traditionally the path to the _private_ key file (for example `IdentityFile ~/.ssh/id_rsa`). For YubiKey, `IdentityFile` must point to the _public_ key file, and `ssh` will select the appropriate private key from those available via ssh-agent. To prevent `ssh` from trying all keys in the agent, use `IdentitiesOnly yes` along with one or more `-i` or `IdentityFile` options for the target host.
To reiterate, with `IdentitiesOnly yes`, `ssh` will not enumerate public keys loaded into `ssh-agent` or `gpg-agent`. This means public-key authentication will not proceed unless explicitly named by `ssh -i [identity_file]` or in `.ssh/config` on a per-host basis.
In the case of YubiKey usage, to extract the public key from the ssh agent:
```console
ssh-add -L | grep "cardno:000605553211" > ~/.ssh/id_rsa_yubikey.pub
```
Then explicitly associate this YubiKey-stored key for used with a host, `github.com` for example, as follows:
```console
$ cat << EOF >> ~/.ssh/config
Host github.com
IdentitiesOnly yes
IdentityFile ~/.ssh/id_rsa_yubikey.pub
EOF
```
Connect with public key authentication:
```console
$ ssh git@github.com -vvv
[...]
debug2: key: cardno:000605553211 (0x1234567890),
debug1: Authentications that can continue: publickey
debug3: start over, passed a different list publickey
debug3: preferred gssapi-keyex,gssapi-with-mic,publickey,keyboard-interactive,password
debug3: authmethod_lookup publickey
debug3: remaining preferred: keyboard-interactive,password
debug3: authmethod_is_enabled publickey
debug1: Next authentication method: publickey
debug1: Offering RSA public key: cardno:000605553211
debug3: send_pubkey_test
debug2: we sent a publickey packet, wait for reply
debug1: Server accepts key: pkalg ssh-rsa blen 535
debug2: input_userauth_pk_ok: fp e5:de:a5:74:b1:3e:96:9b:85:46:e7:28:53:b4:82:c3
debug3: sign_and_send_pubkey: RSA e5:de:a5:74:b1:3e:96:9b:85:46:e7:28:53:b4:82:c3
debug1: Authentication succeeded (publickey).
[...]
```
> [!TIP]
> To enable multiple connections, use the [ControlMaster](https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing) SSH option.
### Import SSH keys
If there are existing SSH keys to make available via `gpg-agent`, they will need to be imported. Then, remove the original private keys. When importing the key, `gpg-agent` uses the key filename as the label - this makes it easier to follow where the key originated from. In this example, we're starting with just the YubiKey in place and importing `~/.ssh/id_rsa`:
```console
$ ssh-add -l
4096 SHA256:... cardno:00060123456 (RSA)
$ ssh-add ~/.ssh/id_rsa && rm ~/.ssh/id_rsa
```
When invoking `ssh-add`, a prompt for the SSH key passphrase will appear, then the `pinentry` program will prompt and confirm a new passphrase to encrypt the converted key within the GnuPG key store.
The migrated key will be listed in `ssh-add -l`:
```console
$ ssh-add -l
4096 SHA256:... cardno:00060123456 (RSA)
2048 SHA256:... /Users/username/.ssh/id_rsa (RSA)
```
To show the keys with MD5 fingerprints, as used by `gpg-connect-agent`'s `KEYINFO` and `DELETE_KEY` commands:
```console
$ ssh-add -E md5 -l
4096 MD5:... cardno:00060123456 (RSA)
2048 MD5:... /Users/username/.ssh/id_rsa (RSA)
```
When using the key `pinentry` will be invoked to request the key passphrase. The passphrase will be cached for up to 10 idle minutes between uses, up to a maximum of 2 hours.
### SSH agent forwarding
> [!CAUTION]
> SSH Agent Forwarding can [add additional risk](https://matrix.org/blog/2019/05/08/post-mortem-and-remediations-for-apr-11-security-incident/#ssh-agent-forwarding-should-be-disabled) - proceed with caution!
There are two methods for ssh-agent forwarding, one is provided by OpenSSH and the other is provided by GnuPG.
The latter one may be more insecure as raw socket is just forwarded (not like `S.gpg-agent.extra` with only limited functionality; if `ForwardAgent` implemented by OpenSSH is just forwarding the raw socket, then they are insecure to the same degree). But for the latter one, one convenience is that one may forward once and use this agent everywhere in the remote. So again, proceed with caution!
For example, tmux does not have environment variables such as `$SSH_AUTH_SOCK` when connecting to remote hosts and attaching an existing session. For each shell, find the socket and `export SSH_AUTH_SOCK=/tmp/ssh-agent-xxx/xxxx.socket`. However, with `S.gpg-agent.ssh` in a fixed place, it can be used as the ssh-agent in shell rc files.
#### Use ssh-agent
You should now be able to use `ssh -A remote` on the _local_ host to log into _remote_ host, and should then be able to use YubiKey as if it were connected to the remote host. For example, using e.g. `ssh-add -l` on that remote host will show the public key from the YubiKey (`cardno:`). Always use `ForwardAgent yes` only for a single host, never for all servers.
#### Use S.gpg-agent.ssh
First you need to go through [GnuPG agent forwarding](#gnupg-agent-forwarding), know the conditions for gpg-agent forwarding and know the location of `S.gpg-agent.ssh` on both the local and the remote.
You may use the command:
```console
$ gpgconf --list-dirs agent-ssh-socket
```
Edit `.ssh/config` to add the remote host:
```console
Host
Hostname remote-host.tld
StreamLocalBindUnlink yes
RemoteForward /run/user/1000/gnupg/S.gpg-agent.ssh /run/user/1000/gnupg/S.gpg-agent.ssh
#RemoteForward [remote socket] [local socket]
#Note that ForwardAgent is not wanted here!
```
After successfully ssh into the remote host, confirm `/run/user/1000/gnupg/S.gpg-agent.ssh` exists.
Then in the *remote* you can type in command line or configure in the shell rc file with:
```console
export SSH_AUTH_SOCK="/run/user/$UID/gnupg/S.gpg-agent.ssh"
```
After sourcing the shell rc file, `ssh-add -l` will return the correct public key.
In this process no gpg-agent in the remote is involved, hence `gpg-agent.conf` in the remote is of no use. Also pinentry is invoked locally.
#### Chained forwarding
If you use `ssh-agent` provided by OpenSSH and want to forward it into a *third* box, you can just `ssh -A third` on the *remote*.
Meanwhile, if you use `S.gpg-agent.ssh`, assume you have gone through the steps above and have `S.gpg-agent.ssh` on the *remote*, and you would like to forward this agent into a *third* box, first you may need to configure `sshd_config` and `SSH_AUTH_SOCK` of *third* in the same way as *remote*, then in the ssh config of *remote*, add the following lines
```console
Host third
Hostname third-host.tld
StreamLocalBindUnlink yes
RemoteForward /run/user/1000/gnupg/S.gpg-agent.ssh /run/user/1000/gnupg/S.gpg-agent.ssh
#RemoteForward [remote socket] [local socket]
#Note that ForwardAgent is not wanted here!
```
The path must be set according to `gpgconf --list-dirs agent-ssh-socket` on *remote* and *third* hosts.
## GitHub
YubiKey can be used to sign commits and tags, and authenticate SSH to GitHub when configured in [Settings](https://github.com/settings/keys).
Configure the signing key:
```console
git config --global user.signingkey $KEYID
```
Alternatively, if you are using the aforementioned `IdentityFile` (SSH key) for signing:
```console
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_rsa_yubikey.pub
```
Configure the `user.name` and `user.email` option to match the email address associated with the PGP identity:
```console
git config --global user.name 'YubiKey User'
git config --global user.email yubikey@example
```
To sign commits or tags, use the `-S` option, or consider enabling commit and tag signing by default:
```console
git config --global commit.gpgsign true
git config --global tag.gpgSign true
```
**Windows**
Configure authentication:
```console
git config --global core.sshcommand "plink -agent"
git config --global gpg.program 'C:\Program Files (x86)\GnuPG\bin\gpg.exe'
```
Then update the repository URL to `git@github.com:USERNAME/repository`
## GnuPG agent forwarding
YubiKey can be used sign git commits and decrypt files on remote hosts with GnuPG Agent Forwarding. To ssh through another network, especially to push to/pull from GitHub using ssh, see [Remote Machines (SSH Agent forwarding)](#ssh-agent-forwarding).
`gpg-agent.conf` is not needed on the remote host; after forwarding, remote GnuPG directly communicates with `S.gpg-agent` without starting `gpg-agent` on the remote host.
On the remote host, edit `/etc/ssh/sshd_config` to set `StreamLocalBindUnlink yes`
**Optional** Without root access on the remote host to edit `/etc/ssh/sshd_config`, socket located at `gpgconf --list-dir agent-socket` on the remote host will need to be removed before forwarding works. See [AgentForwarding GNUPG wiki page](https://wiki.gnupg.org/AgentForwarding) for more information.
Import the public key on the remote host. On the local host, copy the public keyring to the remote host:
```console
scp ~/.gnupg/pubring.kbx remote:~/.gnupg/
```
On modern distributions, such as Fedora 30, there is no need to set `RemoteForward` in `~/.ssh/config`
### Legacy distributions
On the local host, run:
```console
gpgconf --list-dirs agent-extra-socket
```
This should return a path to agent-extra-socket - `/run/user/1000/gnupg/S.gpg-agent.extra` - though on older Linux distros (and macOS) it may be `/home/<user>/.gnupg/S.gpg-agent.extra`
Find the agent socket on the **remote** host:
```console
gpgconf --list-dirs agent-socket
```
This should return a path such as `/run/user/1000/gnupg/S.gpg-agent`
Finally, enable agent forwarding for a given host by adding the following to the local host's `~/.ssh/config` (agent sockets may differ):
```
Host
Hostname remote-host.tld
StreamLocalBindUnlink yes
RemoteForward /run/user/1000/gnupg/S.gpg-agent /run/user/1000/gnupg/S.gpg-agent.extra
#RemoteForward [remote socket] [local socket]
```
It may be necessary to edit `gpg-agent.conf` on the *local* host to add the following information:
```
pinentry-program /usr/bin/pinentry-gtk-2
extra-socket /run/user/1000/gnupg/S.gpg-agent.extra
```
> [!IMPORTANT]
> The pinentry program starts on the *local* host, not remote.
Any pinentry program except `pinentry-tty` or `pinentry-curses` may be used. This is because local `gpg-agent` may start headlessly (by systemd without `$GPG_TTY` set locally telling which tty it is on), thus failed to obtain the pin. Errors on the remote may be misleading saying that there is *IO Error*. (Yes, internally there is actually an *IO Error* since it happens when writing to/reading from tty while finding no tty to use, but for end users this is not friendly.)
See [Issue 85](https://github.com/drduh/YubiKey-Guide/issues/85) for more information and troubleshooting.
### Chained GnuPG agent forwarding
Assume you have gone through the steps above and have `S.gpg-agent` on the *remote*, and you would like to forward this agent into a *third* box, first you may need to configure `sshd_config` of *third* in the same way as *remote*, then in the ssh config of *remote*, add the following lines:
```console
Host third
Hostname third-host.tld
StreamLocalBindUnlink yes
RemoteForward /run/user/1000/gnupg/S.gpg-agent /run/user/1000/gnupg/S.gpg-agent
#RemoteForward [remote socket] [local socket]
```
You should change the path according to `gpgconf --list-dirs agent-socket` on *remote* and *third*.
On *local* you have `S.gpg-agent.extra` whereas on *remote* and *third*, you only have `S.gpg-agent`
## Using multiple YubiKeys
When a GnuPG key is added to YubiKey using `keytocard`, the key is deleted from the keyring and a **stub** is added, pointing to the YubiKey. The stub identifies the GnuPG key ID and YubiKey serial number.
When a Subkey is added to an additional YubiKey, the stub is overwritten and will now point to the latest YubiKey. GnuPG will request a specific YubiKey by serial number, as referenced by the stub, and will not recognize another YubiKey with a different serial number.
To scan an additional YubiKey and recreate the correct stub:
```console
gpg-connect-agent "scd serialno" "learn --force" /bye
```
Alternatively, use a script to delete the GnuPG shadowed key, where the serial number is stored (see [GnuPG #T2291](https://dev.gnupg.org/T2291)):
```console
mkdir -p ~/scripts && cat >> ~/scripts/remove-keygrips.sh <<EOF
#!/usr/bin/env bash
(( $# )) || { echo "Specify a key." >&2; exit 1; }
KEYGRIPS=$(gpg --with-keygrip --list-secret-keys "$@" | awk '/Keygrip/ { print $3 }')
for keygrip in $KEYGRIPS
do
rm "$HOME/.gnupg/private-keys-v1.d/$keygrip.key" 2> /dev/null
done
gpg --card-status
EOF
chmod +x ~/scripts/remove-keygrips.sh
~/scripts/remove-keygrips.sh $KEYID
```
See discussion in Issues [#19](https://github.com/drduh/YubiKey-Guide/issues/19) and [#112](https://github.com/drduh/YubiKey-Guide/issues/112) for more information and troubleshooting steps.
## Email
YubiKey can be used to decrypt and sign emails and attachments using [Thunderbird](https://www.thunderbird.net/), [Enigmail](https://www.enigmail.net) and [Mutt](http://www.mutt.org/). Thunderbird supports OAuth 2 authentication and can be used with Gmail. See [this EFF guide](https://ssd.eff.org/en/module/how-use-pgp-linux) for more information. Mutt has OAuth 2 support since version 2.0.
### Thunderbird
Follow [instructions on the mozilla wiki](https://wiki.mozilla.org/Thunderbird:OpenPGP:Smartcards#Configure_an_email_account_to_use_an_external_GnuPG_key) to setup your YubiKey with your thunderbird client using the external gpg provider.
> [!NOTE]
> Thunderbird will [fail](https://github.com/drduh/YubiKey-Guide/issues/448) to decrypt emails if the ASCII `armor` option is enabled in `gpg.conf`. If you see the error `gpg: [don't know]: invalid packet (ctb=2d)` or `message cannot be decrypted (there are unknown problems with this encrypted message)` simply remove this option.
### Mailvelope
[Mailvelope](https://www.mailvelope.com/en) allows YubiKey to be used with Gmail and others.
> [!NOTE]
> Mailvelope [does not work](https://github.com/drduh/YubiKey-Guide/issues/178) with the `throw-keyids` option set in `gpg.conf`
On macOS, install gpgme using Homebrew:
```console
brew install gpgme
```
To allow Chrome to run gpgme, edit `~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/gpgmejson.json` to add:
```json
{
"name": "gpgmejson",
"description": "Integration with GnuPG",
"path": "/usr/local/bin/gpgme-json",
"type": "stdio",
"allowed_origins": [
"chrome-extension://kajibbejlbohfaggdiogboambcijhkke/"
]
}
```
Edit the default path to allow Chrome to find GnuPG:
```console
sudo launchctl config user path /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
Finally, install the [Mailvelope extension](https://chromewebstore.google.com/detail/mailvelope/kajibbejlbohfaggdiogboambcijhkke) from the Chrome web store.
### Mutt
Mutt has both CLI and TUI interfaces - the latter provides powerful functions for processing email. In addition, PGP can be integrated such that cryptographic operations can be done without leaving TUI.
To enable GnuPG support, copy `/usr/share/doc/mutt/samples/gpg.rc`
Edit the file to enable options `pgp_default_key`, `pgp_sign_as` and `pgp_autosign`
`source` the file in `muttrc`
> [!NOTE]
> `pinentry-tty` set as the pinentry program (in `gpg-agent.conf`) is reported to cause problems with Mutt TUI, because it uses curses; use `pinentry-curses` or other graphic pinentry program instead.
## Keyserver
Public keys can be uploaded to a public server for discoverability:
```console
gpg --send-key $KEYID
gpg --keyserver keys.gnupg.net --send-key $KEYID
gpg --keyserver hkps://keyserver.ubuntu.com:443 --send-key $KEYID
```
Or if [uploading to keys.openpgp.org](https://keys.openpgp.org/about/usage):
```console
gpg --export $KEYID | curl -T - https://keys.openpgp.org
```
The public key URL can also be added to YubiKey (based on [Shaw 2003](https://datatracker.ietf.org/doc/html/draft-shaw-openpgp-hkp-00)):
```console
URL="hkps://keyserver.ubuntu.com:443/pks/lookup?op=get&search=${KEYID}"
```
Edit YubiKey with `gpg --edit-card` and the Admin PIN:
```console
gpg/card> admin
gpg/card> url
URL to retrieve public key: hkps://keyserver.ubuntu.com:443/pks/lookup?op=get&search=0xFF00000000000000
gpg/card> quit
```
# Updating keys
PGP does not provide [forward secrecy](https://en.wikipedia.org/wiki/Forward_secrecy), meaning a compromised key may be used to decrypt all past messages. Although keys stored on YubiKey are more difficult to exploit, it is not impossible: the key and PIN could be physically compromised, or a vulnerability may be discovered in firmware or in the random number generator used to create keys, for example. Therefore, it is recommended practice to rotate Subkeys periodically.
When a Subkey expires, it can either be renewed or replaced. Both actions require access to the Certify key.
- Renewing Subkeys by updating expiration indicates continued custody of the Certify key and is generally more convenient.
- Replacing Subkeys is less convenient, but potentially more secure: new Subkeys will **not** be able to decrypt previous messages, nor authenticate with SSH, etc. Recipients will need the updated public key. Any encrypted secrets must be decrypted and re-encrypted to new Subkeys. This process is functionally equivalent to losing the YubiKey and provisioning a new one.
Neither rotation method is superior and it is up to personal philosophy on identity management and individual threat modeling to decide which one to use, or whether to expire Subkeys at all. Ideally, Subkeys would be ephemeral: used only once for each unique encryption, signature and authentication event, however in practice that is not really practical nor worthwhile with YubiKey. Advanced users may dedicate an air-gapped machine for frequent credential rotation.
To renew or rotate Subkeys, follow the same process as generating keys: boot to a secure environment, install required software and disable networking.
Connect the portable storage device with the Certify key and identify the disk label.
Decrypt and mount the encrypted volume:
```console
sudo cryptsetup luksOpen /dev/sdc1 gnupg-secrets
sudo mkdir -p /mnt/encrypted-storage
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
```
Mount the non-encrypted public partition:
```console
sudo mkdir -p /mnt/public
sudo mount /dev/sdc2 /mnt/public
```
Copy the original private key materials (after updating the encrypted storage directory name) to a temporary working directory:
```console
export GNUPGHOME=$(mktemp -d -t $(date +%Y.%m.%d)-XXXX)
cp -avi /mnt/encrypted-storage/2025.12.31-AbCd/* $GNUPGHOME/
```
Confirm the identity is available, set the key id and fingerprint:
```console
gpg -K
export KEYID=$(gpg -k --with-colons "$IDENTITY" | \
awk -F: '/^pub:/ { print $5; exit }')
export KEYFP=$(gpg -k --with-colons "$IDENTITY" | \
awk -F: '/^fpr:/ { print $10; exit }')
echo $KEYID $KEYFP
```
Recall the Certify key passphrase and set it, for example:
```console
export CERTIFY_PASS=ABCD-0123-IJKL-4567-QRST-UVWX
```
## Renew Subkeys
Set the updated expiration date:
```console
export EXPIRATION=2027-09-01
```
Renew the Subkeys:
```console
echo "$CERTIFY_PASS" | \
gpg --batch --pinentry-mode=loopback \
--passphrase-fd 0 --quick-set-expire "$KEYFP" "$EXPIRATION" \
$(gpg -K --with-colons | awk -F: '/^fpr:/ { print $10 }' | tail -n "+2" | tr "\n" " ")
```
Export the updated public key:
```console
gpg --armor --export $KEYID | sudo tee /mnt/public/$KEYID-$(date +%F).asc
```
Transfer the public key to the destination host and import it:
```console
gpg --import /mnt/public/*.asc
```
Alternatively, publish to a public key server and download it:
```console
gpg --send-key $KEYID
gpg --recv $KEYID
```
The validity of the GnuPG identity will be extended, allowing it to be used again for encryption and signature operations.
The SSH public key does **not** need to be updated on remote hosts.
## Rotate Subkeys
Follow the original procedure to [Create Subkeys](#create-subkeys).
Previous Subkeys can be deleted from the identity.
Finish by transfering new Subkeys to YubiKey.
Copy the **new** temporary working directory to encrypted storage, which is still mounted:
```console
sudo cp -avi $GNUPGHOME /mnt/encrypted-storage
```
Unmount and close the encrypted volume:
```console
sudo umount /mnt/encrypted-storage
sudo cryptsetup luksClose gnupg-secrets
```
Export the updated public key:
```console
sudo mkdir -p /mnt/public
sudo mount /dev/sdc2 /mnt/public
gpg --armor --export $KEYID | sudo tee /mnt/public/$KEYID-$(date +%F).asc
sudo umount /mnt/public
```
Remove the storage device and follow the original steps to transfer new Subkeys (`4`, `5` and `6`) to YubiKey, replacing existing ones.
Reboot or securely erase the GnuPG temporary working directory.
# Reset YubiKey
If PIN attempts are exceeded, the YubiKey is locked and must be [Reset](https://developers.yubico.com/ykneo-openpgp/ResetApplet.html) and set up again using the encrypted backup.
Copy the following to a file and run `gpg-connect-agent -r $file`, then re-insert the YubiKey to complete reset.
```console
/hex
scd serialno
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 e6 00 00
scd apdu 00 44 00 00
/echo Card has been successfully reset.
/bye
```
Or use `ykman` (sometimes in `~/.local/bin/`):
```console
$ ykman openpgp reset
WARNING! This will delete all stored OpenPGP keys and data and restore factory settings? [y/N]: y
Resetting OpenPGP data, don't remove your YubiKey...
Success! All data has been cleared and default PINs are set.
PIN: 123456
Reset code: NOT SET
Admin PIN: 12345678
```
# Optional hardening
The following steps may improve the security and privacy of YubiKey.
## Improving entropy
Generating cryptographic keys requires high-quality [randomness](https://www.random.org/randomness/), measured as entropy. Most operating systems use software-based pseudorandom number generators or CPU-based hardware random number generators (HRNG).
Optionally, a device such as [OneRNG](https://onerng.info/onerng/) may be used to [increase the speed](https://lwn.net/Articles/648550/) and possibly the quality of available entropy.
Before creating keys, configure [rng-tools](https://wiki.archlinux.org/title/Rng-tools):
```console
sudo apt -y install at rng-tools python3-gnupg openssl
wget https://github.com/OneRNG/onerng.github.io/raw/master/sw/onerng_3.7-1_all.deb
```
Verify the package:
```console
sha256sum onerng_3.7-1_all.deb
```
The value must match:
```console
b7cda2fe07dce219a95dfeabeb5ee0f662f64ba1474f6b9dddacc3e8734d8f57
```
Install the package:
```console
sudo dpkg -i onerng_3.7-1_all.deb
echo "HRNGDEVICE=/dev/ttyACM0" | sudo tee /etc/default/rng-tools
```
Insert the device and restart rng-tools:
```console
sudo atd
sudo service rng-tools restart
```
## Enable KDF
> [!IMPORTANT]
> This feature may not be compatible with older GnuPG versions, especially mobile clients. These incompatible clients will not function because the PIN will always be rejected.
This step must be completed before changing PINs or moving keys or an error will occur: `gpg: error for setup KDF: Conditions of use not satisfied`
Key Derived Function (KDF) enables YubiKey to store the hash of PIN, preventing the PIN from being passed as plain text.
Enable KDF using the default Admin PIN of `12345678`:
```console
gpg --command-fd=0 --pinentry-mode=loopback --card-edit <<EOF
admin
kdf-setup
12345678
EOF
```
## Network considerations
This section is primarily focused on Debian / Ubuntu based systems, but the same concept applies to any system connected to a network.
Whether you're using a VM, installing on dedicated hardware, or running a Live OS temporarily, start *without* a network connection and disable any unnecessary services listening on all interfaces before connecting to the network.
The reasoning for this is because services like cups or avahi can be listening by default. While this isn't an immediate problem it simply broadens the attack surface. Not everyone will have a dedicated subnet or trusted network equipment they can control, and for the purposes of this guide, these steps treat *any* network as untrusted / hostile.
**Disable listening services**
- Ensures only essential network services are running
- If the service doesn't exist you'll get a "Failed to stop" which is fine
- Only disable `Bluetooth` if you don't need it
```bash
sudo systemctl stop bluetooth exim4 cups avahi avahi-daemon sshd
```
**Firewall**
Enable a basic firewall policy of *deny inbound, allow outbound*. Note that Debian does not come with a firewall, simply disabling the services in the previous step is fine. The following options have Ubuntu and similar systems in mind.
On Ubuntu, `ufw` is built in and easy to enable:
```bash
sudo ufw enable
```
On systems without `ufw`, `nftables` is replacing `iptables`. The [nftables wiki has examples](https://wiki.nftables.org/wiki-nftables/index.php/Simple_ruleset_for_a_workstation) for a baseline *deny inbound, allow outbound* policy. The `fw.inet.basic` policy covers both IPv4 and IPv6.
(Remember to download this README and any other resources to another external drive when creating the bootable media, to have this information ready to use offline)
Regardless of which policy you use, write the contents to a file (e.g. `nftables.conf`) and apply the policy with the following comand:
```bash
sudo nft -f ./nftables.conf
```
**Review system state**
`NetworkManager` should be the only listening service on port 68/udp to obtain a DHCP lease (and 58/icmp6 if you have IPv6).
If you want to look at every process's command line arguments you can use `ps axjf`. This prints a process tree which may have a large number of lines but should be easy to read on a live image or fresh install.
```bash
sudo ss -anp -A inet # Dump all network state information
ps axjf # List all processes in a process tree
ps aux # BSD syntax, list all processes but no process tree
```
If you find any additional processes listening on the network that aren't needed, take note and disable them with one of the following:
```bash
sudo systemctl stop <process-name> # Stops services managed by systemctl
sudo pkill -f '<process-name-or-command-line-string>' # Terminate the process by matching it's command line string
pgrep -f '<process-name-or-command-line-string>' # Obtain the PID
sudo kill <pid> # Terminate the process via its PID
```
Now connect networking.
# Notes
1. YubiKey has two configurations, invoked with either a short or long press. By default, the short-press mode is configured for HID OTP; a brief touch will emit an OTP string starting with `cccccccc`. OTP mode can be swapped to the second configuration via the YubiKey Personalization tool or disabled entirely using [YubiKey Manager](https://developers.yubico.com/yubikey-manager): `ykman config usb -d OTP`
1. Using YubiKey for GnuPG does not prevent use of [other features](https://developers.yubico.com/), such as [WebAuthn](https://developers.yubico.com/WebAuthn/) and [OTP](https://developers.yubico.com/OTP/).
1. Add additional identities to a Certify key with the `adduid` command during setup, then trust it ultimately with `trust` and `5` to configure for use.
1. To switch between YubiKeys, remove the first YubiKey and restart gpg-agent, ssh-agent and pinentry with `pkill "gpg-agent|ssh-agent|pinentry" ; eval $(gpg-agent --daemon --enable-ssh-support)` then insert the other YubiKey and run `gpg-connect-agent updatestartuptty /bye`
1. To use YubiKey on multiple computers, import the corresponding public keys, then confirm YubiKey is visible with `gpg --card-status`. Trust the imported public keys ultimately with `trust` and `5`, then `gpg --list-secret-keys` will show the correct and trusted key.
1. When your Certify key is offline, *caveat emptor*: If you wish to [participate in keysigning parties](https://www.gnupg.org/gph/en/manual/x334.html), you'll find [signing others' imported public keys](https://gist.github.com/F21/b0e8c62c49dfab267ff1d0c6af39ab84) requires first setting up a secure enclave such as the ephemeral environment described above and importing your Certify key into that enclave. [A signing subkey cannot be used to sign others' imported public keys](https://security.stackexchange.com/questions/153057/possible-to-sign-an-imported-key-with-a-subkey-using-gpg).
# Troubleshooting
- Use `man gpg` to understand GnuPG options and command-line flags.
- To get more information on potential errors, restart the `gpg-agent` process with debug output to the console with `pkill gpg-agent; gpg-agent --daemon --no-detach -v -v --debug-level advanced --homedir ~/.gnupg`.
- A lot of issues can be fixed by removing and re-inserting YubiKey, or restarting the `gpg-agent` process.
- If you receive the error, `Yubikey core error: no yubikey present` - make sure the YubiKey is inserted correctly. It should blink once when plugged in.
- If you still receive the error, `Yubikey core error: no yubikey present` - you likely need to install newer versions of yubikey-personalize as outlined in [Install software](#install-software).
- If you see `General key info..: [none]` in card status output - import the public key.
- If you receive the error, `gpg: decryption failed: secret key not available` - you likely need to install GnuPG version 2.x. Another possibility is that there is a problem with the PIN, e.g., it is too short or blocked.
- If you receive the error, `Yubikey core error: write error` - YubiKey is likely locked. Install and run yubikey-personalization-gui to unlock it.
- If you receive the error, `Key does not match the card's capability` - you likely need to use 2048-bit RSA key sizes.
- If you receive the error, `sign_and_send_pubkey: signing failed: agent refused operation` - make sure you replaced `ssh-agent` with `gpg-agent` as noted above.
- If you still receive the error, `sign_and_send_pubkey: signing failed: agent refused operation` - [run the command](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=835394) `gpg-connect-agent updatestartuptty /bye`
- If you still receive the error, `sign_and_send_pubkey: signing failed: agent refused operation` - edit `~/.gnupg/gpg-agent.conf` to set a valid `pinentry` program path. `gpg: decryption failed: No secret key` could also indicate an invalid `pinentry` path
- If you still receive the error, `sign_and_send_pubkey: signing failed: agent refused operation` - it is a [known issue](https://bbs.archlinux.org/viewtopic.php?id=274571) that openssh 8.9p1 and higher has issues with YubiKey. Adding `KexAlgorithms -sntrup761x25519-sha512@openssh.com` to `/etc/ssh/ssh_config` often resolves the issue.
- If you receive the error, `The agent has no identities` from `ssh-add -L`, make sure you have installed and started `scdaemon`
- If you receive the error, `Error connecting to agent: No such file or directory` from `ssh-add -L`, the UNIX file socket that the agent uses for communication with other processes may not be set up correctly. On Debian, try `export SSH_AUTH_SOCK="/run/user/$UID/gnupg/S.gpg-agent.ssh"`. Also see that `gpgconf --list-dirs agent-ssh-socket` is returning single path, to existing `S.gpg-agent.ssh` socket.
- If you receive the error, `Permission denied (publickey)`, increase ssh verbosity with the `-v` flag and verify the public key from the card is being offered: `Offering public key: RSA SHA256:abcdefg... cardno:00060123456`. If it is, verify the correct user the target system - not the user on the local system. Otherwise, be sure `IdentitiesOnly` is not [enabled](https://github.com/FiloSottile/whosthere#how-do-i-stop-it) for this host.
- If SSH authentication still fails - add up to 3 `-v` flags to the `ssh` command to increase verbosity.
- If it still fails, it may be useful to stop the background `sshd` daemon process service on the server (e.g. using `sudo systemctl stop sshd`) and instead start it in the foreground with extensive debugging output, using `/usr/sbin/sshd -eddd`. Note that the server will not fork and will only process one connection, therefore has to be re-started after every `ssh` test.
- If you receive the error, `Please insert the card with serial number` see [Using Multiple Keys](#using-multiple-yubikeys).
- If you receive the error, `There is no assurance this key belongs to the named user` or `encryption failed: Unusable public key` or `No public key` use `gpg --edit-key` to set `trust` to `5 = I trust ultimately`
- If, when you try the above command, you get the error `Need the secret key to do this` - specify trust for the key in `~/.gnupg/gpg.conf` by using the `trust-key [key ID]` directive.
- If, when using a previously provisioned YubiKey on a new computer with `pass`, you see the following error on `pass insert`, you need to adjust the trust associated with the key. See the note above.
```
gpg: 0x0000000000000000: There is no assurance this key belongs to the named user
gpg: [stdin]: encryption failed: Unusable public key
```
- If you receive the error, `gpg: 0x0000000000000000: skipped: Unusable public key`, `signing failed: Unusable secret key`, or `encryption failed: Unusable public key` the Subkey may be expired and can no longer be used to encrypt nor sign messages. It can still be used to decrypt and authenticate, however.
- If the _pinentry_ graphical dialog does not show and this error appears: `sign_and_send_pubkey: signing failed: agent refused operation`, install the `dbus-user-session` package and restart for the `dbus` user session to be fully inherited. This is because `pinentry` complains about `No $DBUS_SESSION_BUS_ADDRESS found`, falls back to `curses` but doesn't find the expected `tty`
- If, when you try the above `--card-status` command, you get receive the error, `gpg: selecting card failed: No such device` or `gpg: OpenPGP card not available: No such device`, it's possible that the latest release of pcscd now requires polkit rules to operate properly. Create the following file to allow users in the `wheel` group to use the card. Be sure to restart pcscd when you're done to allow the new rules to take effect.
```console
cat << EOF > /etc/polkit-1/rules.d/99-pcscd.rules
polkit.addRule(function(action, subject) {
if (action.id == "org.debian.pcsc-lite.access_card" &&
subject.isInGroup("wheel")) {
return polkit.Result.YES;
}
});
polkit.addRule(function(action, subject) {
if (action.id == "org.debian.pcsc-lite.access_pcsc" &&
subject.isInGroup("wheel")) {
return polkit.Result.YES;
}
});
EOF
```
- If the public key is lost, follow [this guide](https://www.nicksherlock.com/2021/08/recovering-lost-gpg-public-keys-from-your-yubikey/) to recover it from YubiKey.
- Refer to Yubico article [Troubleshooting Issues with GPG](https://support.yubico.com/hc/en-us/articles/360013714479-Troubleshooting-Issues-with-GPG) for additional guidance.
# Alternative solutions
* [`vorburger/ed25519-sk.md`](https://github.com/vorburger/vorburger.ch-Notes/blob/develop/security/ed25519-sk.md) - use YubiKey for SSH without GnuPG
* [`smlx/piv-agent`](https://github.com/smlx/piv-agent) - SSH and GnuPG agent which can be used with PIV devices
* [`keytotpm`](https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html) - use GnuPG with TPM systems
# Additional resources
* [Yubico - PGP](https://developers.yubico.com/PGP/)
* [Yubico - Yubikey Personalization](https://developers.yubico.com/yubikey-personalization/)
* [A Visual Explanation of GPG Subkeys (2022)](https://rgoulter.com/blog/posts/programming/2022-06-10-a-visual-explanation-of-gpg-subkeys.html)
* [dhess/nixos-yubikey](https://github.com/dhess/nixos-yubikey)
* [lsasolutions/makegpg](https://gitlab.com/lsasolutions/makegpg)
* [Trammell Hudson - Yubikey (2020)](https://trmm.net/Yubikey)
* [Yubikey forwarding SSH keys (2019)](https://blog.onefellow.com/post/180065697833/yubikey-forwarding-ssh-keys)
* [GPG Agent Forwarding (2018)](https://mlohr.com/gpg-agent-forwarding/)
* [Stick with security: YubiKey, SSH, GnuPG, macOS (2018)](https://evilmartians.com/chronicles/stick-with-security-yubikey-ssh-gnupg-macos)
* [PGP and SSH keys on a Yubikey NEO (2015)](https://www.esev.com/blog/post/2015-01-pgp-ssh-key-on-yubikey-neo/)
* [Offline GnuPG Master Key and Subkeys on YubiKey NEO Smartcard (2014)](https://blog.josefsson.org/2014/06/23/offline-gnupg-master-key-and-subkeys-on-yubikey-neo-smartcard/)
* [Creating the perfect GPG keypair (2013)](https://alexcabal.com/creating-the-perfect-gpg-keypair/)
[^1]: Use single quotes to wrap double quote character(s) (`"`) - `export IDENTITY='My Identity (a.k.a. "YubiKey User") <yubikey@example.domain>'`
[^2]: [Revocation certificates](https://security.stackexchange.com/questions/14718/does-openpgp-key-expiration-add-to-security/79386#79386) should be used to revoke an identity.
[^3]: See [issue 477](https://github.com/drduh/YubiKey-Guide/issues/477) for NIST guideline discussion.
================================================
FILE: SECENV.md
================================================
# Creating a Secure Environment for GPG in Alpine Linux
Copyright (c) 2025 Matt Borja
## Abstract
This document describes a process for creating a secure environment for GPG key management using Alpine Linux: a lightweight and secure Linux distribution capable of supporting newer versions of GPG with smart card support on very modest hardware such as the ARM-based Raspberry Pi 1 Model B (32-bit). This document also considers the highly portable nature of Alpine Package Keeper (APK) for ease of dependency installation in air-gapped environments and a tightly coupled process to further assert package integrity as an installation prerequisite within the air-gapped environment.
**Tags.** Tails OS, Alpine Linux, GnuPG, Raspberry Pi.
## Disclaimer
The procedures outlined in this document are provided as best effort measures for creating a safer working environment for managing GPG keys; and are not intended to eliminate every possible threat scenario including, but not limited to those arising from the presence of: advanced persistent threats, viruses, infected firmware, or other similarly compromised peripherals or protocols used by these procedures. Caution must still be exercised when considering the use, proximity, and direct connection of any “active” or unshielded electronic device.
Furthermore:
THIS DOCUMENTATION IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THIS DOCUMENTATION OR THE USE OR OTHER DEALINGS IN THIS DOCUMENTATION.
## 1. Prepare a Secure Imaging Host
Preparing a secure environment for GPG normally involves the initial use of an external host system (e.g., Windows, Mac OS, etc.) to create its bootable disk. While this might be satisfactory to many, it is worth considering the risk of host contamination through daily use. Therefore we will consider an *intermediary environment* from which we will then create the bootable disk. One might think of this seemingly superfluous step as merely grabbing a clean plate before putting food on it to eat!
### 1.1. Use Tails OS as an Intermediary (Recommended)
[Tails OS](https://tails.net/install/expert/index.en.html) provides for a convenient, isolated ephemeral environment, placing special emphasis on proper verification of its USB images before use. Consider booting into a system like this before starting in on these procedures.
As mentioned before, this environment alone may arguably be considered satisfactory for GPG purposes, but it is out of an abundance of caution we are limiting our use of it for verification and imaging purposes only.
### 1.2. Use the target OS to download packages and gpg.conf
The goal of this section is to have all the necessary assets in hand to avert the need for an Internet connection post-installation towards the end of this guide.
#### 1.2.1. Acquire the target image
If you haven't already, follow the [Tails installation guide](https://tails.net/install/expert/index.en.html) *carefully* to ensure you have [verified](https://tails.net/install/expert/index.en.html#verify-key) and booted into a valid Tails environment before continuing.
Next, import a copy of [Alpine Linux](https://alpinelinux.org/downloads/) by either:
- Connecting Tails to the Internet and using the Tor Browser to download the image
- Leaving Tails disconnected from the Internet and instead using another device (e.g., smartphone) to bring over the downloaded image using a removable storage device
**Note.** If you have designated an older Raspberry Pi as your preferred air-gapped device, you will need to provide a SD card for imaging and likely use the ARMhf (hard float) images from their [downloads page](https://www.alpinelinux.org/downloads/).
**Important.** In the same way you should have done for Tails, you must verify the Alpine Linux image using its corresponding GPG signature against its signing key before continuing. Always check [official sources](https://docs.alpinelinux.org/user-handbook/0.1a/Installing/medium.html#_optional_verifying_the_downloaded_files_pgp) and consider additional evidence sources ([example](https://sig3.dev)) for validity completness. These steps may seem repetitive and strenuous, but are critical to building a verifiable path into a highly secure environment.
Once you've verified the image download, you can use the **Restore Disk Image...** in the *Disks* utility from within Tails to write the image to a target disk (e.g., SD card for a Raspberry Pi).
#### 1.2.2. Download a copy of gpg.conf (hardened)
While still connected to the Internet, consider downloading a copy of a hardened version of gpg.conf ([example](https://github.com/drduh/YubiKey-Guide/blob/master/config/gpg.conf)) to add to your `$GNUPGHOME` on initial boot into the secure environment.
#### 1.2.3. Boot the target image to download OS-specific packages for GnuPG
Boot into the Alpine Linux system, login as `root`, and connect to the Internet to download the required packages for this specific platform:
```shell
# 1. Set date
root@host:~$ date -s 'YYYY-MM-DD hh:mm:ss'
# 2. Connect to Internet (https://wiki.alpinelinux.org/wiki/Configure_Networking)
# 3a. Download packages for offline installation
root@host:~$ apk update
root@host:~$ apk upgrade
root@host:~$ apk fetch --recursive gpg gnupg-scdaemon pcsc-lite
# 3b. Use the same apk fetch --recursive command to download any additional packages you require for additional work in the air-gapped enviroment.
# Note. Internet is no longer required beyond this point.
# 4. Review downloaded packages
root@host:~$ ls -lha *.apk
# 5. Bundle for offline use and take SHA256 checksum
root@host:~$ tar -czvf airgap-bundle.tar.gz *.apk
root@host:~$ sha256sum airgap-bundle.tar.gz > airgap-bundle.tar.gz.sha256
# 6. Visually inspect and note SHA256 checksum for verification
root@root:~$ cat airgap-bundle.tar.gz.sha256
# 6. Mount removable storage and transfer (replace $SD_PARTITION_DEV with your actual device file handle, as in /dev/sda1, etc.)
root@host:~$ mount -t exfat "${SD_PARTITION_DEV}" /mnt
root@host:~$ cp airgap-bundle.* /mnt/
root@host:~$ umount /mnt
```
**Note.** With offline packages now in hand, you might consider repeating the prior section (*1.2.1. Acquiring the target image*) to provide yourself with another "clean plate" (one that's never been connected to the Internet) before continuing. Pragmatically speaking, you can also just "wash the plate" by re-imaging Alpine Linux over the selfsame SD card used in this step to download packages.
## 2. Boot the Secure Environment
The newly provisioned Alpine Linux environment should now be booted, free of any extraneous peripheral attachments, with networking completely disabled.
Additional setup tasks may include:
- Manually setting the system date (`date -s "$CURRENT_UTC_TIMESTAMP"`)
- Setting the keyboard language and layout
### 2.1 Install Offline Packages for GnuPG
After booting into the secure environment, the user proceeds to verify the SHA256 checksums of the previously GPG-verified APK packages download to removable storage:
```shell
root@host:~$ mkdir work && cd work
root@host:~/work$ mount -t exfat "${SD_PARTITION_DEV}" /mnt
root@host:~/work$ cp /mnt/airgap-bundle.* .
root@host:~/work$ umount /mnt
```
The following command provides strict coupling (`&&`) between the cryptographic verification of the bundle created earlier, and its subsequent extraction. If the checksum is invalid, the tarball will not be extracted and the `apk` command that follows is expected to fail.
```shell
# Require sha256sum to pass before extracting and installing with `apk`
root@host:~/work$ sha256sum -c airgap-bundle.tar.gz.sha256 \
&& tar -xzvf airgap-bundle-tar-gz \
&& apk --allow-untrusted --force-non-repository add *.apk
```
**Don't forget!** If you obtained a copy of [gpg.conf](https://github.com/drduh/YubiKey-Guide/blob/master/config/gpg.conf) in advance, be sure to import it into your `$GNUPGHOME` before continuing.
**CI/CD Considerations.** For DevOps teams, this concludes the essential requirements for provisioning an Alpine Linux image with an offline copy of packages for GPG key management. In the interest of transparency, be sure to include any relevant steps and artifacts in your software provenance and image signing before releasing.
### 2.2 Verify the Environment
Assuming package installation is successfully, begin verifying the environment for GPG use:
**Note.** If you have a YubiKey, go ahead and insert it now.
```shell
# Verify GPG installation
root@host:~$ gpg --version
# Verify smartcard connection
root@host:~$ gpg --card-status
# Import corresponding public key to view all details
root@host:~$ gpg --import yubikey.pub
root@host:~$ gpg --list-sigs "$YUBIKEY_FINGERPRINT"
root@host:~$ gpg --list-secret-keys "$YUBIKEY_FINGERPRINT"
```
**Note.** If you run into issues detecting your YubiKey switching between `$GNUPGHOME` directories (common during heavy key management operations such as ring transfers, etc.), try restarting the `gpg-agent` as follows:
```shell
root@host:~$ pkill gpg-agent
root@host:~$ gpg --card-status
```
**All done!** You can now begin [working with GPG](https://github.com/drduh/YubiKey-Guide?tab=readme-ov-file#identity) and smart cards in your new air-gapped environment!
## Stage 3. Takedown
When finished performing key management tasks, the secure environment should either be a) promptly destroyed or b) properly secured away; to close the window on unknown threats to a dormant system (e.g., physical, technological, theoretical, unknown).
================================================
FILE: config/gpg-agent.conf
================================================
# https://github.com/drduh/YubiKey-Guide/blob/master/config/gpg-agent.conf
# https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html
enable-ssh-support
ttyname $GPG_TTY
default-cache-ttl 60
max-cache-ttl 120
# Select a valid program path for PIN entry prompt
# -- Linux (default /usr/bin/pinentry)
#pinentry-program /usr/bin/pinentry
#pinentry-program /usr/bin/pinentry-curses
#pinentry-program /usr/bin/pinentry-gnome3
#pinentry-program /usr/bin/pinentry-tty
#pinentry-program /usr/bin/pinentry-x11
# -- macOS - Intel silicon (default /usr/local/bin/pinentry)
#pinentry-program /usr/local/bin/pinentry
#pinentry-program /usr/local/bin/pinentry-curses
#pinentry-program /usr/local/bin/pinentry-mac
#pinentry-program /usr/local/bin/pinentry-tty
# -- macOS - Apple silicon (default /opt/homebrew/bin/pinentry)
#pinentry-program /opt/homebrew/bin/pinentry
#pinentry-program /opt/homebrew/bin/pinentry-curses
#pinentry-program /opt/homebrew/bin/pinentry-mac
#pinentry-program /opt/homebrew/bin/pinentry-tty
# -- macOS - MacPorts
#pinentry-program /opt/local/bin/pinentry
# -- macOS - MacGPG2
#pinentry-program /usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac
# -- Windows
# https://developers.yubico.com/PGP/SSH_authentication/Windows.html
#enable-win32-openssh-support
================================================
FILE: config/gpg.conf
================================================
# https://github.com/drduh/YubiKey-Guide/blob/master/config/gpg.conf
# https://www.gnupg.org/documentation/manuals/gnupg/GPG-Options.html
# 'gpg --version' to get capabilities
# Use AES256, 192, or 128 as cipher
personal-cipher-preferences AES256 AES192 AES
# Use SHA512, 384, or 256 as digest
personal-digest-preferences SHA512 SHA384 SHA256
# Use ZLIB, BZIP2, ZIP, or no compression
personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
# Default preferences for new keys
default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed
# SHA512 as digest to sign keys
cert-digest-algo SHA512
# SHA512 as digest for symmetric ops
s2k-digest-algo SHA512
# AES256 as cipher for symmetric ops
s2k-cipher-algo AES256
# UTF-8 support for compatibility
charset utf-8
# No comments in messages
no-comments
# No version in output
no-emit-version
# Disable banner
no-greeting
# Long key id format
keyid-format 0xlong
# Display UID validity
list-options show-uid-validity
verify-options show-uid-validity
# Display all keys and their fingerprints
with-fingerprint
# Display key origins and updates
#with-key-origin
# Cross-certify subkeys are present and valid
require-cross-certification
# Enforce memory locking to avoid accidentally swapping GPG memory to disk
require-secmem
# Disable caching of passphrase for symmetrical ops
no-symkey-cache
# Output ASCII instead of binary
armor
# Enable smartcard
use-agent
# Disable recipient key ID in messages (WARNING: breaks Mailvelope)
throw-keyids
# Default key ID to use (helpful with throw-keyids)
#default-key 0xFF00000000000001
#trusted-key 0xFF00000000000001
# Group recipient keys (preferred ID last)
#group keygroup = 0xFF00000000000003 0xFF00000000000002 0xFF00000000000001
# Keyserver URL
#keyserver hkps://keys.openpgp.org
#keyserver hkps://keys.mailvelope.com
#keyserver hkps://keyserver.ubuntu.com:443
#keyserver hkps://pgpkeys.eu
#keyserver hkps://pgp.circl.lu
#keyserver hkp://zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad.onion
# Keyserver proxy
#keyserver-options http-proxy=http://127.0.0.1:8118
#keyserver-options http-proxy=socks5-hostname://127.0.0.1:9050
# Enable key retrieval using WKD and DANE
#auto-key-locate wkd,dane,local
#auto-key-retrieve
# Trust delegation mechanism
#trust-model tofu+pgp
# Show expired subkeys
#list-options show-unusable-subkeys
# Verbose output
#verbose
================================================
FILE: nix/diceware-vt.patch
================================================
diff --git a/index.html b/index.html
index 2f26ed9..3b4a2d3 100644
--- a/index.html
+++ b/index.html
@@ -920,8 +920,19 @@
<!-- core application JS -->
<script
src="index.js"
- integrity="sha384-++jBnvz86d0OUZ3chFxES5Sj6jjOZ/jKegsrHhXhOEzWxrvn7LhRGB0HP+bvLeNI"
+ integrity="sha384-v759g0TMj/jSFxhXsmlahbhJnj5NYNBopqVDq9WQaMOWsLZ0sJzLKxIoP+WzY9Yq"
crossorigin="anonymous"
></script>
+ <script>
+ $(document).ready(function () {
+ 'use strict'
+ // Use the 6 word list as the default
+ var numWords, numRolls
+ numWords = parseInt(6, 10)
+ numRolls = parseInt(5, 10)
+ displayWords(getWords(numWords, numRolls))
+ displayCrackTime(wordList)
+ })
+ </script>
</body>
</html>
diff --git a/index.js b/index.js
index e95e2a1..9d45377 100644
--- a/index.js
+++ b/index.js
@@ -238,11 +238,28 @@ function getWordFromWordNum (wordNum) {
function displayWords (words) {
'use strict'
+ // get symbol and number for the first and third words (CMD)
+ if (words.length > 1) {
+ var symbols = getWords(1,2)
+ var number = Math.floor(Math.random() * 100)
+ var symbol_pos = Math.floor(Math.random() * words.length)
+ var number_pos = Math.floor(Math.random() * words.length)
+ var capitalize_pos = Math.floor(Math.random() * words.length)
+ }
+
// add the word to the global array of words
$.each(words, function (index, obj) {
var objEntropy = new Big(obj.entropy)
totalEntropy = totalEntropy.plus(objEntropy)
$('#totalEntropy').text(totalEntropy.toFixed(2))
+ if (words.length > 1) {
+ // add symbol to random word (CMD)
+ if (index == symbol_pos) obj.word = obj.word + symbols[0].word
+ // add number to random word (CMD)
+ if (index == number_pos) obj.word = obj.word + number
+ // capitalize random word (CMD)
+ if (index == capitalize_pos) obj.word = obj.word.charAt(0).toUpperCase() + obj.word.substring(1)
+ }
wordList.push(obj.word)
})
@@ -370,4 +387,4 @@ $(document).ready(function () {
$('#addFiveDieRollWord').val('')
displayCrackTime(wordList)
})
-})
+})
\ No newline at end of file
================================================
FILE: nix/flake.nix
================================================
{
description = "A Nix Flake for an xfce-based system with YubiKey setup";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
};
outputs = {
self,
nixpkgs,
}: let
mkSystem = system:
nixpkgs.lib.nixosSystem {
inherit system;
modules = [
"${nixpkgs}/nixos/modules/profiles/all-hardware.nix"
"${nixpkgs}/nixos/modules/installer/cd-dvd/iso-image.nix"
(
{
lib,
pkgs,
config,
...
}: let
gpgAgentConf = pkgs.runCommand "gpg-agent.conf" {} ''
sed '/pinentry-program/d' ${self}/../config/gpg-agent.conf > $out
echo "pinentry-program ${pkgs.pinentry.curses}/bin/pinentry" >> $out
'';
dicewareAddress = "localhost";
dicewarePort = 8080;
viewYubikeyGuide = pkgs.writeShellScriptBin "view-yubikey-guide" ''
viewer="$(type -P xdg-open || true)"
if [ -z "$viewer" ]; then
viewer="${pkgs.glow}/bin/glow -p"
fi
exec $viewer "${self}/../README.md"
'';
shortcut = pkgs.makeDesktopItem {
name = "yubikey-guide";
icon = "${pkgs.yubioath-flutter}/share/icons/com.yubico.yubioath.png";
desktopName = "YubiKey Guide";
genericName = "Guide to using YubiKey for GnuPG and SSH";
comment = "Open YubiKey Guide in a reader program";
categories = ["Documentation"];
exec = "${viewYubikeyGuide}/bin/view-yubikey-guide";
};
yubikeyGuide = pkgs.symlinkJoin {
name = "yubikey-guide";
paths = [viewYubikeyGuide shortcut];
};
dicewareScript = pkgs.writeShellScriptBin "diceware-webapp" ''
viewer="$(type -P xdg-open || true)"
if [ -z "$viewer" ]; then
viewer="firefox"
fi
exec $viewer "http://"${lib.escapeShellArg dicewareAddress}":${toString dicewarePort}/index.html"
'';
dicewarePage = pkgs.stdenv.mkDerivation {
name = "diceware-page";
src = pkgs.fetchFromGitHub {
owner = "grempe";
repo = "diceware";
rev = "9ef886a2a9699f73ae414e35755fd2edd69983c8";
sha256 = "44rpK8svPoKx/e/5aj0DpEfDbKuNjroKT4XUBpiOw2g=";
};
patches = [
# Include changes published on https://secure.research.vt.edu/diceware/
./diceware-vt.patch
];
buildPhase = ''
cp -a . $out
'';
};
dicewareWebApp = pkgs.makeDesktopItem {
name = "diceware";
icon = "${dicewarePage}/favicon.ico";
desktopName = "Diceware Passphrase Generator";
genericName = "Passphrase Generator";
comment = "Open the passphrase generator in a web browser";
categories = ["Utility"];
exec = "${dicewareScript}/bin/${dicewareScript.name}";
};
in {
isoImage = {
isoName = "yubikeyLive.iso";
# As of writing, zstd-based iso is 1542M, takes ~2mins to
# compress. If you prefer a smaller image and are happy to
# wait, delete the line below, it will default to a
# slower-but-smaller xz (1375M in 8mins as of writing).
squashfsCompression = "zstd";
appendToMenuLabel = " YubiKey Live ${self.lastModifiedDate}";
makeEfiBootable = true; # EFI booting
makeUsbBootable = true; # USB booting
};
swapDevices = [];
boot = {
tmp.cleanOnBoot = true;
kernel.sysctl = {"kernel.unprivileged_bpf_disabled" = 1;};
};
services = {
pcscd.enable = true;
udev.packages = [pkgs.yubikey-personalization];
# Automatically log in at the virtual consoles.
getty.autologinUser = "nixos";
# Comment out to run in a console for a smaller iso and less RAM.
xserver = {
enable = true;
desktopManager.xfce = {
enable = true;
enableScreensaver = false;
};
displayManager = {
lightdm.enable = true;
};
};
displayManager = {
autoLogin = {
enable = true;
user = "nixos";
};
};
# Host the `https://secure.research.vt.edu/diceware/` website offline
nginx = {
enable = true;
virtualHosts."diceware.local" = {
listen = [
{
addr = dicewareAddress;
port = dicewarePort;
}
];
root = "${dicewarePage}";
};
};
};
programs = {
# Add firefox for running the diceware web app
firefox = {
enable = true;
preferences = {
# Disable data reporting confirmation dialogue
"datareporting.policy.dataSubmissionEnabled" = false;
# Disable welcome tab
"browser.aboutwelcome.enabled" = false;
};
# Make preferences appear as user-defined values
preferencesStatus = "user";
};
ssh.startAgent = false;
gnupg = {
dirmngr.enable = true;
agent = {
enable = true;
enableSSHSupport = true;
};
};
};
# Use less privileged nixos user
users.users = {
nixos = {
isNormalUser = true;
extraGroups = ["wheel" "video"];
initialHashedPassword = "";
};
root.initialHashedPassword = "";
};
security = {
pam.services.lightdm.text = ''
auth sufficient pam_succeed_if.so user ingroup wheel
'';
sudo = {
enable = true;
wheelNeedsPassword = false;
};
};
environment.systemPackages = with pkgs; [
# Tools for backing up keys
paperkey
pgpdump
parted
cryptsetup
# Yubico's official tools
yubikey-manager
yubikey-personalization
yubikey-personalization-gui
yubico-piv-tool
yubioath-flutter
# Testing
ent
# Password generation tools
diceware
dicewareWebApp
pwgen
rng-tools
# Might be useful beyond the scope of the guide
cfssl
pcsctools
tmux
htop
# This guide itself (run `view-yubikey-guide` on the terminal
# to open it in a non-graphical environment).
yubikeyGuide
# PDF and Markdown viewer
kdePackages.okular
];
# Disable networking so the system is air-gapped
# Comment all of these lines out if you'll need internet access
boot.initrd.network.enable = false;
networking = {
resolvconf.enable = false;
dhcpcd.enable = false;
dhcpcd.allowInterfaces = [];
interfaces = {};
firewall.enable = true;
useDHCP = false;
useNetworkd = false;
wireless.enable = false;
networkmanager.enable = lib.mkForce false;
};
# Unset history so it's never stored Set GNUPGHOME to an
# ephemeral location and configure GPG with the guide
environment.interactiveShellInit = ''
unset HISTFILE
export GNUPGHOME="/run/user/$(id -u)/gnupg"
if [ ! -d "$GNUPGHOME" ]; then
echo "Creating \$GNUPGHOME…"
install --verbose -m=0700 --directory="$GNUPGHOME"
fi
[ ! -f "$GNUPGHOME/gpg.conf" ] && cp --verbose "${self}/../config/gpg.conf" "$GNUPGHOME/gpg.conf"
[ ! -f "$GNUPGHOME/gpg-agent.conf" ] && cp --verbose ${gpgAgentConf} "$GNUPGHOME/gpg-agent.conf"
echo "\$GNUPGHOME is \"$GNUPGHOME\""
'';
# Copy the contents of contrib to the home directory, add a
# shortcut to the guide on the desktop, and link to the whole
# repo in the documents folder.
system.activationScripts.yubikeyGuide = let
homeDir = "/home/nixos/";
desktopDir = homeDir + "Desktop/";
documentsDir = homeDir + "Documents/";
in ''
mkdir -p ${desktopDir} ${documentsDir}
chown nixos ${homeDir} ${desktopDir} ${documentsDir}
cp -R ${self}/contrib/* ${homeDir}
ln -sf ${yubikeyGuide}/share/applications/yubikey-guide.desktop ${desktopDir}
ln -sf ${dicewareWebApp}/share/applications/${dicewareWebApp.name} ${desktopDir}
ln -sfT ${self} ${documentsDir}/YubiKey-Guide
'';
system.stateVersion = "25.05";
}
)
];
};
in {
nixosConfigurations.yubikeyLive.x86_64-linux = mkSystem "x86_64-linux";
nixosConfigurations.yubikeyLive.aarch64-linux = mkSystem "aarch64-linux";
formatter.x86_64-linux = (import nixpkgs {system = "x86_64-linux";}).alejandra;
formatter.aarch64-linux = (import nixpkgs {system = "aarch64-linux";}).alejandra;
};
}
================================================
FILE: pubkeys/debian-DA87E80D6294BE9B.asc
================================================
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
G5o0MlY5JRLakc6tQI1C0mXPeP/3tyY3Fs38lnh7igFx7lbV1hx5MI3foWAylnMS
S0P/JipLYmc/egH0x744V9acxrCv0DkE9QWR4wr2CcxgXTFzM3qYS3qREoS5aGJ4
qQiXxv5Ncg72/9jp4zNbC45U8N81c7u+IrCTXHOl9V0u7Uqin5nJy0ez5I/ZayEW
BxiXcRXWIXYabo/NRLIq0ScN8bISsvylPbTpc5sM0qfiSPJpEOfeZlO+Db+vdZjN
Ov0MG54vFnMeJdT1VllLxvvwGyQHP5a/R3tgvUX/ZO4Y/fPPY82B30lfp9L5GogI
UaJRQ9OuadB7ogrNagC186orTfbBFBIzApf1zMlIUjpVP2FALlKjNwdjmaqKXqQZ
1W1IchWeKh5RQp3C2Ve7fWIZm9Wwn8MaqHkjHbu9fpKicN7ngvLDqjAvTD6v0P/i
JgBGzItjf5p+sdTW3NkTlcxj0nEg90hRHy6JZjI5IHdDp78vZJpOZWRE/cyqg7ni
02rUDRgJMYrWsz8FA7t6OC2RnDPJvXSZ0RGcTC1k7ltlB6/RH0cMbQ9LemQXunT8
MhaASb7Y4OrnrRTGIqmAEzAzT23jXBFQi7NBOMrIJ3m6xbNFv4FTzQlLSOb56rMX
+0qTwt1mpTKiz+rRDPKM8w4MVS48xEum3CoCtxEiyu9l0dbGoh+j5x6N8M8Phj6I
RP9ADo1vtQmxtbegqJTykbKzVym5aRyzSBVQm3bbY7FLso8zbWG7t+bqMrqTQ47a
z8kjLo436RDfZiBkRSEVMiKaLsQnD35DqSZuJ8WbhSJk06Iko33sUoBJfFXEy5V6
ZS2L9eCRkWSdvyVo7A1BqK1od8BWPraroPBV0EVtNeD7CirxFljaSLosV5pYUpNg
9jH+a6uR7AWI5aCE2agtepK3ozeVWZg7Gs/hdVApsq9E3taiTI40SS7SR5yONFQS
h+cKkpmCAU6skY2EZQGcI4bADW167TyrY2txLK+qf32uqKZikwkmzH36f+Fs0BQD
4WJhtqozehutz9Q2NzUivJ9q4mqpUJRuuSdx0hX5fxYcJT55MqGav9lZFPHRGJIa
W3+ulOlWJby9DPFi7aQNe94XZNRBeAhDIFBSzlF/Q5bJiCpV05OomlpmT4bsPwBw
OmZKaN/iJEI6YQhuDyf3PtRofqW5wunMQJEZPFtjVrImJ9zmLEPCpdd1Fnb3+fvQ
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
VrTiV8ee22eEF7OO0A==
=h48J
-----END PGP PUBLIC KEY BLOCK-----
================================================
FILE: scripts/generate.sh
================================================
#!/usr/bin/env bash
# https://github.com/drduh/YubiKey-Guide/blob/master/scripts/generate.sh
# Generates GnuPG keys and corresponding passphrases to secure them.
#set -x # uncomment to debug
set -o errtrace
set -o nounset
set -o pipefail
umask 077
export LC_ALL="C"
fail() {
# Print an error string in red and exit.
tput setaf 1 ; printf "%s\n" "${1}" ; tput sgr0
exit 1
}
print_cred () {
# Print a credential string in red.
tput setaf 1 ; printf "%s\n" "${1}" ; tput sgr0
}
print_id () {
# Print an identity string in yellow.
tput setaf 3 ; printf "%s\n" "${1}" ; tput sgr0
}
get_id_label () {
# Returns Identity name/label.
printf "YubiKey User <yubikey@example.domain>"
}
get_key_type_sign () {
# Returns key type for signature subkey.
#printf "default"
printf "rsa4096"
}
get_key_type_enc () {
# Returns key type for encryption subkey.
#printf "default"
printf "rsa4096"
}
get_key_type_auth () {
# Returns key type for authentication subkey.
#printf "default"
#printf "rsa4096"
printf "ed25519"
}
get_key_expiration () {
# Returns key expiration date.
printf "2027-07-01"
}
get_temp_dir () {
# Returns temporary working directory path.
mktemp -d -t "$(date +%Y.%m.%d)-XXXX"
}
set_temp_dir () {
# Exports and switches to temporary dir.
export GNUPGHOME="$(get_temp_dir)"
cd "$GNUPGHOME" || exit 1
printf "set temp dir (path='%s')\n" "$(pwd)"
}
set_attrs () {
# Sets identity and key attributes.
export IDENTITY="$(get_id_label)"
export KEY_TYPE_SIGN="$(get_key_type_sign)"
export KEY_TYPE_ENC="$(get_key_type_enc)"
export KEY_TYPE_AUTH="$(get_key_type_auth)"
export KEY_EXPIRATION="$(get_key_expiration)"
printf "set attributes (label='%s', sign='%s', enc='%s', auth='%s', expire='%s')\n" \
"$IDENTITY" "$KEY_TYPE_SIGN" "$KEY_TYPE_ENC" "$KEY_TYPE_AUTH" "$KEY_EXPIRATION"
}
get_pass () {
# Returns random passphrase.
tr -dc "A-Z2-9" < /dev/urandom | \
tr -d "IOUS5" | \
fold -w "${PASS_GROUPSIZE:-4}" | \
paste -sd "${PASS_DELIMITER:--}" - | \
head -c "${PASS_LENGTH:-29}"
}
set_pass () {
# Exports Certify and LUKS passphrases.
export CERTIFY_PASS="$(get_pass)"
export ENCRYPT_PASS="$(get_pass)"
printf "set passphrases (certify='%s', encrypt='%s')\n" \
"$CERTIFY_PASS" "$ENCRYPT_PASS"
}
gen_key_certify () {
# Generates Certify key with no expiration.
echo "$CERTIFY_PASS" | \
gpg --batch --passphrase-fd 0 \
--quick-generate-key "$IDENTITY" "$KEY_TYPE_SIGN" "cert" "never"
}
set_fingerprint () {
# Sets Key ID and Fingerprint environment vars.
key_list=$(gpg --list-secret-keys --with-colons)
export KEY_ID=$(printf "$key_list" | awk -F: '/^sec/ { print $5; exit }')
export KEY_FP=$(printf "$key_list" | awk -F: '/^fpr/ { print $10; exit }')
if [[ -z "$KEY_FP" || -z "$KEY_ID" ]]; then
fail "could not set key fingerprint"
fi
printf "got identity (fp='%s', id='%s')\n" "$KEY_FP" "$KEY_ID"
}
gen_key_subs () {
# Generates Subkeys with specified expiration.
echo "$CERTIFY_PASS" | \
gpg --batch --passphrase-fd 0 --pinentry-mode=loopback \
--quick-add-key "$KEY_FP" "$KEY_TYPE_SIGN" sign "$KEY_EXPIRATION"
echo "$CERTIFY_PASS" | \
gpg --batch --passphrase-fd 0 --pinentry-mode=loopback \
--quick-add-key "$KEY_FP" "$KEY_TYPE_ENC" encrypt "$KEY_EXPIRATION"
echo "$CERTIFY_PASS" | \
gpg --batch --passphrase-fd 0 --pinentry-mode=loopback \
--quick-add-key "$KEY_FP" "$KEY_TYPE_AUTH" auth "$KEY_EXPIRATION"
}
save_secrets () {
# Exports secret keys to local files.
export OUTPUT_CERTIFY="$GNUPGHOME/$KEY_ID-Certify.key"
export OUTPUT_SUBKEYS="$GNUPGHOME/$KEY_ID-Subkeys.key"
echo "$CERTIFY_PASS" | \
gpg --output "$OUTPUT_CERTIFY" \
--batch --pinentry-mode=loopback --passphrase-fd 0 \
--armor --export-secret-keys "$KEY_ID"
echo "$CERTIFY_PASS" | \
gpg --output "$OUTPUT_SUBKEYS" \
--batch --pinentry-mode=loopback --passphrase-fd 0 \
--armor --export-secret-subkeys "$KEY_ID"
}
save_pubkey () {
# Exports public key to local file.
export OUTPUT_PUBKEY="$GNUPGHOME/$KEY_ID-Public.asc"
gpg --output "$OUTPUT_PUBKEY" \
--armor --export "$KEY_ID"
}
finish () {
# Prints final message with id and credentials.
printf "\nidentity/key label: "
print_id "$IDENTITY"
printf "key id/fingerprint: "
print_id "$KEY_ID"
print_id "$KEY_FP"
printf "subkeys expiration: "
print_id "$KEY_EXPIRATION"
printf "\nsecrets and pubkey: "
print_id "$GNUPGHOME"
print_id "$OUTPUT_PUBKEY"
printf "\ncertify passphrase: "
print_cred "$CERTIFY_PASS"
printf "encrypt passphrase: "
print_cred "$ENCRYPT_PASS"
exit 0
}
# 1. Set temporary working directory for GnuPG ops.
set_temp_dir
# 2. Set identity and key attributes, such as label and type.
set_attrs
# 3. Set passphrases for identity and storage encryption.
set_pass
# 4. Generate the Certify key.
gen_key_certify
# 5. Set resulting identity fingerprint.
set_fingerprint
# 6. Generate the Subkeys.
gen_key_subs
# 7. Export Certify and Subkeys to local storage.
save_secrets
# 8. Export public key to local storage.
save_pubkey
# 9. Print results and exit.
finish
================================================
FILE: scripts/reset-yubikey
================================================
/hex
scd serialno
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 e6 00 00
scd apdu 00 44 00 00
/echo Card has been successfully reset.
/bye
================================================
FILE: scripts/switch-to-backup-yubikey
================================================
#!/bin/sh
#
# To make a duplicate Yubikey for GPG keys
# 1. Insert Yubikey1
# 2. Create keys/subkeys
# 3. Run keytocard to transfer keys to Yubikey1
# 4. QUIT WITHOUT SAVING!!!!!
#
# This will leave the keys on the Yubikey but NOT change the
# GPG keyring to point to the Yubikey1 with a stub
#
# 5. Insert Yubikey2
# 6. Run keytocard to transfer keys to Yubikey2
# 7. QUIT and SAVE to make GPG point it's stubs to Yubikey2
#
# Running any decrypt, auth or sign will now ask you to insert Yubikey2
# To switch to Yubikey1 at any time run this script to force GPG
# to repoint the key stubs to the inserted Yubikey
read -p "Insert the Yubikey you want to use .... " ignore
echo "Switching GPG to backup Yubikey ..."
gpg-connect-agent "scd serialno" "learn --force" /bye
================================================
FILE: templates/passphrase.html
================================================
<!-- https://github.com/drduh/YubiKey-Guide/blob/master/templates/passphrase.html
https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/templates/passphrase.html
Save the raw file ^ then open in a browser to render and print -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="github.com/drduh/YubiKey-Guide">
<title>credentials</title>
<style type="text/css">
:root {
--color-dark: #000000;
--color-gray: #dedede;
}
body {
color: var(--color-dark);
font-family: monospace;
font-size: 0.8rem;
font-variant: small-caps;
text-align: center;
}
td {
border: 0.05rem solid var(--color-dark);
height: 1rem;
}
td.alt, tr.alt {
background: var(--color-gray);
}
</style>
</head>
<body>
<table>
<colgroup span="38" width="20"></colgroup>
<tr>
<td></td>
<td class="alt" colspan="10">date (yyyy-mm-dd)</td>
<td></td>
<td></td>
<td></td>
<td class="alt" colspan="10">admin pin</td>
<td></td>
<td></td>
<td></td>
<td class="alt" colspan="10">user pin</td>
<td></td>
</tr>
<tr>
<td></td>
<td>2</td>
<td>0</td>
<td>2</td>
<td>5</td>
<td>-</td>
<td>_</td>
<td>_</td>
<td>-</td>
<td>_</td>
<td>_</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td></td>
<td class="alt" colspan="10">key id</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td></td>
<td class="alt" colspan="2" rowspan="2">0x</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td></td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td></td>
<td class="alt" colspan="10">serial number</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<!-- serial number row -->
<tr>
<td></td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td>_</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
<td></td>
<td></td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<!-- blank space row -->
<tr class="alt">
<td colspan="40">for each char in passphrase, mark corresponding column on sequential row, then fold inward and tamper seal</td>
</tr>
<!-- passphrase rows -->
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
<tr class="alt">
<td>-</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
<td>I</td>
<td>J</td>
<td>K</td>
<td>L</td>
<td>M</td>
<td>N</td>
<td>O</td>
<td>P</td>
<td>Q</td>
<td>R</td>
<td>S</td>
<td>T</td>
<td>U</td>
<td>V</td>
<td>W</td>
<td>X</td>
<td>Y</td>
<td>Z</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td></td>
</tr>
</table>
</body>
</html>
================================================
FILE: templates/passphrase.txt
================================================
# https://github.com/drduh/YubiKey-Guide/blob/master/templates/passphrase.txt
DATE (YYYY-MM-DD) ADMIN PIN USER PIN
2025-__-__ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
KEY ID 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
0x________________ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
SERIAL NUMBER 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
________ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
FOR EACH CHAR IN PASSPHRASE,
MARK CORRESPONDING COLUMN ON SEQUENTIAL ROW,
THEN FOLD INWARD AND TAMPER SEAL
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
gitextract_zrqpbut6/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── SECENV.md
├── config/
│ ├── gpg-agent.conf
│ └── gpg.conf
├── media/
│ └── schema_gpg.pptx
├── nix/
│ ├── diceware-vt.patch
│ └── flake.nix
├── pubkeys/
│ └── debian-DA87E80D6294BE9B.asc
├── scripts/
│ ├── generate.sh
│ ├── reset-yubikey
│ └── switch-to-backup-yubikey
└── templates/
├── passphrase.html
└── passphrase.txt
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (197K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 16,
"preview": "github: [drduh]\n"
},
{
"path": ".gitignore",
"chars": 4,
"preview": ".~*\n"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 drduh\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 84561,
"preview": "This is a guide to using [YubiKey](https://www.yubico.com/products/identifying-your-yubikey/) as a [smart card](https://"
},
{
"path": "SECENV.md",
"chars": 9895,
"preview": "# Creating a Secure Environment for GPG in Alpine Linux\n\nCopyright (c) 2025 Matt Borja\n\n## Abstract\nThis document descri"
},
{
"path": "config/gpg-agent.conf",
"chars": 1302,
"preview": "# https://github.com/drduh/YubiKey-Guide/blob/master/config/gpg-agent.conf\n# https://www.gnupg.org/documentation/manuals"
},
{
"path": "config/gpg.conf",
"chars": 2386,
"preview": "# https://github.com/drduh/YubiKey-Guide/blob/master/config/gpg.conf\n# https://www.gnupg.org/documentation/manuals/gnupg"
},
{
"path": "nix/diceware-vt.patch",
"chars": 2185,
"preview": "diff --git a/index.html b/index.html\nindex 2f26ed9..3b4a2d3 100644\n--- a/index.html\n+++ b/index.html\n@@ -920,8 +920,19 @"
},
{
"path": "nix/flake.nix",
"chars": 10667,
"preview": "{\n description = \"A Nix Flake for an xfce-based system with YubiKey setup\";\n\n inputs = {\n nixpkgs.url = \"github:Nix"
},
{
"path": "pubkeys/debian-DA87E80D6294BE9B.asc",
"chars": 44430,
"preview": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBE0kXpEBEADotFhQLHYo3KgoGE+Rr9wVEms+iQMIrUiYy0H66iGz8L8nw7D5\nX4wuvpx9OFG2rhEK1"
},
{
"path": "scripts/generate.sh",
"chars": 5471,
"preview": "#!/usr/bin/env bash\n# https://github.com/drduh/YubiKey-Guide/blob/master/scripts/generate.sh\n# Generates GnuPG keys and "
},
{
"path": "scripts/reset-yubikey",
"chars": 489,
"preview": "/hex\nscd serialno\nscd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40\nscd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40\nscd ap"
},
{
"path": "scripts/switch-to-backup-yubikey",
"chars": 778,
"preview": "#!/bin/sh\n#\n# To make a duplicate Yubikey for GPG keys\n# 1. Insert Yubikey1\n# 2. Create keys/subkeys\n# 3. Run keytocard "
},
{
"path": "templates/passphrase.html",
"chars": 23588,
"preview": "<!-- https://github.com/drduh/YubiKey-Guide/blob/master/templates/passphrase.html\n https://raw.githubusercontent.com"
},
{
"path": "templates/passphrase.txt",
"chars": 3124,
"preview": "# https://github.com/drduh/YubiKey-Guide/blob/master/templates/passphrase.txt\n\n DATE (YYYY-MM-DD) ADMIN PIN"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the drduh/YubiKey-Guide GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (185.5 KB), approximately 76.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.