I came across a great guide to using a YubiKey with SSH and GPG a couple years ago which helped push me over the fence and acquire my own YubiKey. Following that setup guide, I set up my keys offline using a Tails Linux boot USB with a OneRNG hardware random number generator. While a fun exercise, I must note that it’s not for the faint of heart, especially if done on a recent MacBook Pro (with a touchbar) or incompatible hardware. However, it did help explain some of the features available in GnuPG, and this came in handy recently while exploring the new support for elliptic curve cryptography in YubiKey firmware 5.2.3, the version installed in my later YubiKey 5Ci purchase. While I originally created PGP keys using the same guide last year with RSA keys, since those keys were expiring soon, it seemed like a good idea to look in to switching to Curve25519 keys. GnuPG has added some improved support for this algorithm along with supporting this updated YubiKey firmware to transfer these keys to a YubiKey. In this brief guide, I’ll go over how to generate an appropriate PGP key that can be used both in a YubiKey and for use with SSH. For more general info about using smartcards with GnuPG, see this guide from GnuPG.

About PGP Keys

PGP keys are slightly more complicated than a single keypair, and they’re fairly flexible. A key has one or more user ids (name, email address, and an optional comment), one of which is flagged as the primary uid (by default, this is the original uid used on creation). Keys have an optional expiration date along with a list of usages allowed for that specific key. Keys have one or more subkeys which allow for different keys for different use cases all on the same key. These use cases include certification (to create and certify subkeys typically), signatures, encryption, and authentication. Each key or subkey can allow one or more of those capabilities, though certification is typically reserved for the primary key. When using RSA, this is fairly simple as RSA supports all four of those capabilities. However, elliptic curves typically use dual algorithms for signing and encryption, so this will be slightly more nuanced.

Generating PGP Keys

Our master key will be created for certification with no expiration date. This is justified by the fact that the certification key will be physically tied to a YubiKey for which we can use a revocation certificate if the key is lost or stolen. The subkeys will have expiration dates to allow for key rotation as explained in the linked guide, though that is out of scope for this post. We’ll split up the remaining usage capabilities into three subkeys: a signing subkey, an encryption subkey, and an authentication subkey. Note that certification and authentication keys use signature algorithms internally, thus for our key, we’ll use ed25519 for all but our encryption subkey which will instead use cv25519.

The following commands will help us avoid using the UI for most of the work. Use this gpg.conf file in ~/.gnupg/gpg.conf for a more secure default. Then, create a certification master key (note the optional comment can be omitted entirely, no parenthesis necessary) and specify a password for the key when prompted:

gpg --quick-generate-key \
    'Your Name <your.email@example.com> (optional comment)' \
    ed25519 cert never

Next, save the key fingerprint without spaces to an environment variable:

export KEYFP=123456789ABCDEF0...

Now add subkeys for signing, encryption, and authentication. These will have expiration times to allow for key rotation. For example, using a one year expiration time:

gpg --quick-add-key $KEYFP ed25519 sign 1y
gpg --quick-add-key $KEYFP cv25519 encr 1y
gpg --quick-add-key $KEYFP ed25519 auth 1y

Next, verify the keys have been added to your local keyring:

gpg -K

This should give output like:

sec   ed25519/0x0123456789ABCDEF 2021-01-01 [C]
      Key fingerprint = 0000 1111 2222 3333 4444  5555 0123 4567 89AB CDEF
uid                   [ultimate] Matt Sicker <mattsicker@apache.org>
ssb   ed25519/0x9876543212345678 2021-01-01 [S] [expires: 2022-01-01]
ssb   cv25519/0x3141592653589793 2021-01-01 [E] [expires: 2022-01-01]
ssb   ed25519/0x8888ABCDFDEC8765 2021-01-01 [A] [expires: 2022-01-01]

Using SSH

The authentication subkey can be used for authentication in SSH. There are a few different ways to export the PGP key into an OpenSSH format, and we’ll cover a simple one here. This method will use GnuPG as an SSH agent which allows us to both use PGP keys for SSH as well as to import and encrypt existing SSH key files into a GPG-managed SSH keyring. First, obtain a copy of this gpg-agent.conf file and save it to ~/.gnupg/gpg-agent.conf. Uncomment the appropriate pinentry program for your platform (and don’t forget to install it if you haven’t already). Next, add the following lines to your ~/.bashrc, ~/.zshrc, or whatever shell rc file you use:

export GPG_TTY="$(tty)"
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent

Note that this configuration is only relevant for your local machine. If you’re using SSH agent forwarding, don’t copy this configuration to remote machines. After restarting your shell, run ssh-add -L to list the currently known SSH keys in GnuPG. Your locally stored keys should be listed here as well as your YubiKey if you’ve transferred the key there and have it plugged in. These lines correspond to SSH public keys which can be used in your ~/.ssh/authorized_keys file on relevant remote machines to SSH into. Alternatively, you can export a GPG’s authentication key into an SSH format directly using the following command:

gpg --export-ssh-key 0x1234ABCD1234ABCD

For use with GitHub and other git+ssh providers, add this public key to your account’s SSH keys.