In this blog post, I’ll show you how to setup a Yubikey with both signing and authentication keys. As a driving example, I’ll describe how to use a Yubikey to sign your git commits and authenticate via SSH with GitHub. Of course you can reuse this guide to authenticate with any other SSH server as well.
Why use Yubikeys?
These devices store private key material in a way that cannot be copied from the device. An attacker would need to steal the physical device to use your keys. This setup is in contrast to keys stored on your local hard drive, which can be easily copied by any program running on your computer.
Yubikeys store GPG keys. We will first generate keys on the device. Then your computer needs to be configured with
gpg-agent, which will manage access to the keys.
ssh can then be configured to consult the
gpg-agent for signing commits and SSH authentication by default (instead of
If you have already generated the keys on your Yubikey and just want to setup your computer to use it, skip to the gpg-agent configuration section
Table of Contents
You will need the following tools on your system:
To install these on Ubuntu 18.04:
$ sudo add-apt-repository ppa:yubico/stable $ sudo apt-get update $ sudo apt-get install pcscd scdaemon pcsc-tools gnupg2 gnupg-agent $ sudo apt-get install yubikey-manager yubikey-personalization-gui yubikey-personalization
To install these on MacOS with Homebrew
$ brew install gnupg2 $ brew install pinentry-mac $ brew install ykman $ brew install yubikey-personalization
To install these on MacOS with MacPorts
$ sudo port install gnupg2 pinentry-mac yubikey-manager ykpers
First make sure that the Yubikey is plugged in and check that
gpg can see it. If you can’t see the card, you’re probably missing some smart card driver for your system. You probably don’t need to restart your computer, but that could also be worth a shot.
$ gpg --card-status Reader ...........: Yubico Yubikey XXX Application ID ...: XXXXX Version ..........: X.X Manufacturer .....: Yubico Serial number ....: XXXXX Name of cardholder: [not set] Language prefs ...: [not set] Sex ..............: unspecified URL of public key : [not set] Login data .......: [not set] Signature PIN ....: forced Key attributes ...: rsa4096 rsa4096 rsa4096 Max. PIN lengths .: 127 127 127 PIN retry counter : 3 3 3 Signature counter : 0 Signature key ....: [none] Encryption key....: [none] Authentication key: [none] General key info..: [none]
Now we will change the key size, PIN, and Admin PIN on the device from its defaults. In this guide we will use RSA4096, but you should choose the configuration that works best for you. The default PIN and Admin PIN values for Yubikeys are 123456 and 12345678 respectively.
Note: If you incorrectly enter your Admin PIN three times, you will be locked out of your Yubikey and it will be useless. If that happens, check out the factory reset instructions.
$ gpg --card-edit gpg/card> admin Admin commands are allowed gpg/card> key-attr Changing card key attribute for: Signature key Please select what kind of key you want: (1) RSA (2) ECC Your selection? 1 What keysize do you want? (4096) 4096 Changing card key attribute for: Encryption key Please select what kind of key you want: (1) RSA (2) ECC Your selection? 1 What keysize do you want? (4096) 4096 Changing card key attribute for: Authentication key Please select what kind of key you want: (1) RSA (2) ECC Your selection? 1 What keysize do you want? (4096) 4096 gpg/card> passwd gpg: OpenPGP card no. XXXXXX detected 1 - change PIN 2 - unblock PIN 3 - change Admin PIN 4 - set the Reset Code Q - quit Your selection? 3 PIN changed. ... Your selection? 1 PIN changed. ... Your selection? q gpg/card> quit
Now let’s set the Yubikey mode to U2F/CCID composite mode. U2F mode is used for 2-factor authentication for web services like Google/GitHub. CCID mode is used for
gpg operations. We will disable OTP mode to avoid the annoying keyboard behavior when the button is accidentally pressed. Note that mode 5 is specific to Yubikey 3.0 and above (details). Make sure you’re setting the correct mode for your Yubikey.
$ ykpersonalize -m 5 Firmware version X.X.X Touch level XXX Program sequence X The USB mode will be set to: 0x5 Commit? (y/n) [n]: y WARNING: Changing mode will require you to use another tool (ykneomgr or u2f-host) to switch back if OTP mode is disabled, really commit? (y/n) [n]: y
Now disconnect and reconnect your Yubikey to apply the new settings. You’ll notice that pushing the Yubikey button no longer leads to keyboard strokes.
Optional: It is also recommended to enable a touch requirement for all authentication requests, which means you have the physically touch the device to approve any encryption/signing/authentication requests. For details, see here.
$ ykman openpgp touch aut on $ ykman openpgp touch enc on $ ykman openpgp touch sig on
Generate Your Keys
Now let’s have the Yubikey generate its own keys. This way, we can be sure the keys never existed outside the device.
Note: The email address that you enter here MUST match the one verified on your GitHub account! Otherwise, the GitHub UI will show your commits as “unverified”.
$ gpg --card-edit gpg/card> admin Admin commands are allowed gpg/card> generate Make off-card backup of encryption key? (Y/n) n Please specify how long the key should be valid. 0 = key does not expire <n> = key expires in n days <n>w = key expires in n weeks <n>m = key expires in n months <n>y = key expires in n years Key is valid for? (0) 0 Key does not expire at all Is this correct? (y/N) y GnuPG needs to construct a user ID to identify your key. Real name: <Enter your name> Email address: <Enter your email address> Comment: You selected this USER-ID: "XXX XXX <XXXXX>" Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o gpg: key XXX marked as ultimately trusted
This step may take several minutes as the device generates keys with sufficient entropy. It will generate unique subkeys for signatures, encryption, and authentication.
Now verify that you have keys on the card. Look for a
Encryption key, and
Authentication key. In particular make note of your signature key fingerprint:
$ gpg --card-status Reader ...........: Yubico Yubikey XXX Application ID ...: XXXXX Version ..........: X.X Manufacturer .....: Yubico Serial number ....: XXXXX Name of cardholder: [not set] Language prefs ...: [not set] Sex ..............: unspecified URL of public key : [not set] Login data .......: [not set] Signature PIN ....: forced Key attributes ...: rsa4096 rsa4096 rsa4096 Max. PIN lengths .: 127 127 127 PIN retry counter : 3 3 3 Signature counter : 0 Signature key ....: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX created ....: 2018-01-01 00:00:00 Encryption key....: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX created ....: 2018-01-01 00:00:00 Authentication key: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX created ....: 2018-01-01 00:00:00 General key info..: pub rsa4096/XXXXXXXXXXXXXXXX 2018-01-01 NAME <EMAIL> sec> rsa4096/XXXXXXXXXXXXXXXX created: 2018-01-01 expires: never <=== THIS ONE card-no: 0000 00000000 ssb> rsa4096/XXXXXXXXXXXXXXXX created: 2018-01-01 expires: never card-no: 0000 00000000 ssb> rsa4096/XXXXXXXXXXXXXXXX created: 2018-01-01 expires: never card-no: 0000 00000000
Make a note of your
SIGNATURE_KEY_FINGERPRINT. Then export your signature public key. For example, if your fingerprint is AED9256FF8CEC558:
$ gpg --armor --export AED9256FF8CEC558 > AED9256FF8CEC558.asc
You will need to manually copy this signature public key to any computer that you want to use for git commit signing.
First, you need to import your signature public key onto the machine. You can skip this if you generated the key on this computer. For example, if your signature public key is in
$ gpg --import < AED9256FF8CEC558.asc
gpg-agent needs to be configured with SSH support. Put the following in your
# if on MacOS, we recommend you use pinentry-mac # otherwise, look for `pinentry' on your system (e.g. pinentry-gnome3 or pinentry-tty). # If you don't set this, the default pinentry will be used pinentry-program /usr/local/bin/pinentry-mac # enables SSH support (ssh-agent) enable-ssh-support
To setup your terminal to use
gpg-agent as your SSH agent, put the following in your
export SSH_AUTH_SOCK=`gpgconf --list-dirs agent-ssh-socket` export GPG_TTY=$(tty)
Remember to restart your gpg-agent and terminal for these settings to take effect. If you don’t have gpg-agent setup to run automatically, you can start it manually:
$ gpg-agent --daemon --enable-ssh-support
Signed git Commits
You can manually ask git to sign at commit time. To do so, you need to remember to add the
-S flag every time you commit.
$ git commit -S -m 'commit message'
Note: Whenever the Yubikey is asked to sign or authenticate, you’ll need to enter your PIN into the
pinentry program. If you configured a touch requirement, you’ll also need to touch the Yubikey.
Automatically Signing Commits by Default
In order to have git automatically sign all commits for you (without the
-S flag), add this to your
~/.gitconfig. Your signing key fingerprint is from the last step of the generate your keys section.
[user] signingkey = <fingerprint> [commit] gpgsign = true
Upload GPG Keys to GitHub
Now add your GPG signature public key to GitHub.
We previously exported it to a file at the end of the generate your keys section. From any computer where the public key is already loaded, you can get it again by running:
$ gpg --armor --export SIGNATURE_KEY_FINGERPRINT
After you do this, GitHub will verify your commits and show a verified status in commit history.
After you’ve signed your first commit, you will see verified commits in the commit log both locally and on GitHub.
$ git log --show-signature Author: NAME <EMAIL> Date: Mon Jan 01 00:00:00 2018 -0000 commit message commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx gpg: Signature made Mon Jan 01 00:00:00 2018 PST gpg: using RSA key XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX gpg: Good signature from "NAME <EMAIL>" [unknown] Primary key fingerprint: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
Get Your SSH Public Key
When the Yubikey is plugged in,
gpg-agent is properly running, and your terminal is setup with the correct
SSH_AUTH_SOCK, you can get your SSH public key by running:
$ ssh-add -L
If you want to get it directly from GPG, you can run the following with the authentication key fingerprint:
$ gpg --export-ssh-key AUTHENTICATION_KEY_FINGERPRINT
Add Your Key to a Remote Server
With that SSH key, you can now add it as an authorized key to any SSH server.
To tell GitHub about this key, add the SSH key here:
You can also add it to any SSH server by adding it to
Test Out SSH
You should now be able to SSH to servers. Whenever the Yubikey is asked to sign or authenticate, you’ll need to enter your PIN into the
pinentry program. If you configured a touch requirement, you’ll also need to touch the Yubikey.
Try it out with the
-v flag on
ssh to see if the right key is being used:
$ ssh -v example.com OpenSSH_6.7p1, OpenSSL 1.0.2 22 Jan 2015 debug1: Connecting to example.com [127.0.0.1] port 22. debug1: Connection established. debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_6.7 debug1: Remote protocol version 2.0, remote software version OpenSSH_6.4 debug1: match: OpenSSH_6.4 pat OpenSSH* compat 0x04000000 debug1: SSH2_MSG_KEXINIT sent debug1: SSH2_MSG_KEXINIT received debug1: kex: server->client aes128-ctr firstname.lastname@example.org none debug1: kex: client->server aes128-ctr email@example.com none debug1: sending SSH2_MSG_KEX_ECDH_INIT debug1: expecting SSH2_MSG_KEX_ECDH_REPLY debug1: Server host key: RSA xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx debug1: Host 'example.com' is known and matches the RSA host key. debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug1: SSH2_MSG_NEWKEYS received debug1: Roaming not allowed by server debug1: SSH2_MSG_SERVICE_REQUEST sent debug1: SSH2_MSG_SERVICE_ACCEPT received debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic debug1: Next authentication method: publickey # this is where we see our YubiKey is being used debug1: Offering RSA public key: cardno:XXXXXXXXXXXX debug1: Server accepts key: pkalg ssh-rsa blen 279 debug1: Authentication succeeded (publickey). Authenticated to localhost ([127.0.0.1]:22). debug1: channel 0: new [client-session] debug1: Requesting firstname.lastname@example.org debug1: Entering interactive session. [user@localhost]~$
Using Your Yubikey on a Chromebook
If you’ve provisioned your Yubikey on another machine, it is possible to use it for SSH authentication on a Chromebook. First, make sure you’ve installed the following Chrome Apps:
Because Crostini (the project that brings a Debian VM to Chromebooks) does not currently support natively accessing the Yubikey through USB, you have to SSH into your Linux VM with agent-forwarding. That way, the SSH agent running in the Smart Card Connector will be accessible to the VM, which you use to authenticate from within the VM. Crostini is not available on all Chromebooks. Follow this guide to set up SSH and agent forwarding. If you have any issues, here’s a reference doc.
That’s pretty much it!
While not technically part of this guide, it’s usually a good idea to set up your Yubikey as a security key for two-factor authentication.
Setup your GitHub two-factor authentication here:
If you have any trouble, check out the troubleshooting section in this guide.
If you ever lose your Yubikey, remember to remove it as an authorized key on GitHub and any SSH servers you may be using.