[Deprecated] GPG and SSH with Yubikey for Mac

Important! This blog post is now out of date and shouldn’t be relied upon. I’m leaving it in place for history’s sake.

Yubikey NeoYubikey Neo

The Yubikey Neo [affiliate] is a great, inexpensive security device that supports Universal 2nd Factor authentication to web services and OpenPGP smart card support.

The goal of this post is to describe the setup steps for:

  • GPG Mail encryption and signing
  • SSH public-key authentication, e.g. for connecting to servers, Git source control, and Heroku.

The latter establishes a second factor for controlling access that cannot be compromised simply by theft of an SSH private key and/or use of a keylogger. With the private key for GPG and SSH held on the Yubikey, it is much more secure than if it were held on the local hard disk.

This post is written to help set this up for Macs running Yosemite or El Capitan, using Fish shell. It is assumed that Homebrew and brew cask are installed.

This post is a combination/distillation of a handful of HOWTO guides I found useful for getting this set up. This was compiled a little after I actually performed the process, so if there are any errors/omissions please let me know.

Preparing the Yubikey

Install Yubikey management tools:

$ brew update
$ brew cask update
$ brew cask install yubikey-neo-manager yubikey-personalization-gui
$ brew install yubikey-personalization

Insert the Yubikey into your USB port.

Set the Yubikey’s mode to allow concurrent OpenPGP SmartCard and OTP usage:

$ ykpersonalize -m82

Install GPG Tools for Mac:

$ brew cask install gpgtools

Set up PINs for GPG on the Yubikey. If at this stage you receive a card error’, try removing and reinserting the Yubikey.

Note: if you enter the factory default PIN incorrectly too many times the Yubikey will become blocked. It seems that it may be possible to reset this, but I have not tested this.

First, set an admin PIN (factory default is 12345678). Next, set your user PIN (factory default is 123456).

$ gpg --card-edit
... snip ...
gpg/card> admin
Admin commands are allowed

gpg/card> passwd

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 3
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 1
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? q

Creating a public/private key pair

Next, create a key on the device. We’ll choose not to make an off-card backup of the key, and to have the key expire after 1 year:

$ gpg --card-edit
gpg/card> admin
gpg/card> generate
Make off-card backup of encryption key? (Y/n) n
gpg: 3 Admin PIN attempts remaining before card is permanently locked
Admin PIN
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) 1y
Is this correct? (y/N) y

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
"Heinrich Heine (Der Dichter) <[email protected]>"

Real name: ...
Email address: ...
Comment: tester
You selected this USER-ID:
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
gpg: generating new key
gpg: please wait while key is being generated ...
gpg: key generation completed (45 seconds)
gpg: signatures created so far: 0
gpg: signatures created so far: 0
You need a Passphrase to protect your secret key.

gpg: signatures created so far: 2
gpg: signatures created so far: 2
gpg: generating new key
gpg: please wait while key is being generated ...
gpg: key generation completed (25 seconds)
gpg: signatures created so far: 4
gpg: signatures created so far: 4
gpg: key 79C56617 marked as ultimately trusted
public and secret key created and signed.
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, classic trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   2048R/79C56617 2015-08-25
Key fingerprint = ...
uid                  ...
sub   2048R/... ...
sub   2048R/... ...

I’ve removed specific elements from the above, but note the public key ID you receive (in my case, this was 79C56617).

Next, export the GPG public key somewhere safe for later use:

$ gpg --armor --export <your public key ID> > ~/my_gpg_public_key.pub

Open up the GPG Keychain’ application and right click on your keypair (it should show with sec/pub, in bold).

Firstly, generate a Revoke Certificate and store it in a safe place. You will need to use this to revoke your key if you lose your Yubikey.

Next, do Send public key to Keyserver’.

Configuring GPG Agent

This step allows GPG Agent to authenticate your SSH sessions. Edit ~/.gnupg/gpg-agent.conf:

pinentry-program /usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac
default-cache-ttl 600
max-cache-ttl 7200
debug-level advanced
log-file /var/log/gpg-agent.log

Note that:

  • the pinentry-program setting above stipulates that a Mac native GUI should be used to prompt for your GPG User PIN
  • enable-ssh-support… does what it says
  • default-cache-ttl means that your PIN will be cached for 10 minutes
  • By default the log file will not be writable and no logs will be produced. Touch the file /var/log/gpg-agent.log and chown it to your username if you want GPG Agent to log information out.

Next, as I’m using fish shell, I had to add this to my shell configuration to start GPG Agent with my shell. Put the following in ~/.config/fish/gnupg.fish:

# Start or re-use a gpg-agent.
gpgconf --launch gpg-agent

# Ensure that GPG Agent is used as the SSH agent
set -U -x SSH_AUTH_SOCK ~/.gnupg/S.gpg-agent.ssh

(Added 2016-04-17): With the release of GPG Agent Autostart in v2.1 of GPG, this section has become vastly easier! Changes are reflected above.

Now, restart your shell to make GPG Agent reload.

Using GPG for SSH login

With GPG Agent running, not much more needs to be done to use the Yubikey for SSH authentication. We still need to obtain your public key in a format that SSH can understand:

$ gpgkey2ssh <your public key ID> > ~/my_ssh_public_key.pub

This yields a regular SSH format public key. Next, edit the comment section (final part of the file).

You can now transfer this SSH public key in the usual manner to:

  • authorized_keys files on servers you need access to
  • Github
  • Heroku
  • … any other services you use that require your SSH public key

If you’ve successfully set up your Yubikey, GPG Agent and provided your new public key to Github, the following should tell you your Github username (mine is rnorth, as seen here):

$ ssh [email protected]
PTY allocation request failed on channel 0
Hi rnorth! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.

Testing GPG for encryption and signing

To check that your base GPG setup works, try the following:

$ gpg --sign -a

Type Hello World! followed by newline and Ctrl-D. The output should look vaguely similar to:

Comment: GPGTools - https://gpgtools.org


Additional things to try

  • Upload your GPG public key to keybase.io. I have a handful of invites to share, if anybody wants one.
  • Send GPG encrypted mail using Mail.app (GPG Tools installs Mail/GPG integration automatically). If you found this guide useful, please try sending me a GPG encrypted email to tell me so!


The following excellent guides were used in deriving these steps:


Previous post
Automated UI testing in Xcode 7 I’ve tried many UI testing tools for iOS apps, but never found anything that completely satisfied me. Recent versions of KIF have come closest, but
Next post
iOS App Transport Security in dev environments App Transport Security, introduced with iOS 9, is a great step towards improving the security of all apps by forcing use of HTTPS for network