SSH keys on a yubikey

There is something oddly satisfying about having my private ssh keys only on a hardware device where they cannot be directly accessed.

For the past 6 months I've been using a yubikey for SSH access to my servers and github. In this configuration the private key only exists on the yubikey and cannot be transferred to the host computer. All cryptographic operations that require the private key are preformed on the yubikey. Here's how I set it up.

Install software

Assuming you have homebrew and brew cask installed:

brew cask install yubikey-neo-manager  
brew install gnupg2 gpg-agent pinentry-mac  

Configure the yubikey

  • Start yubikey-neo-manager
  • Insert your yubikey
  • Select the device
  • and click Change connection mode to enable CCID

I disabled all other modes since I only cared about using this as an OpenGPG smart card.

Create a key

gpg2 --card-edit  
gpg/card> admin  
gpg/card> generate  

At this point a new keypair is on your yubikey. Your public key will be added to your gpg keychain. Note that you won't be able to retrieve your public or (private key) from the yubikey. You may want to backup or publish your public key to a key server - but I'll leave that as an exercise to the reader.

gpg/card> quit  

To turn your newly minted GPG public key into an ssh formatted public key:

gpgkey2ssh <key id>  

and you will get...

ssh-rsa AAAa1NUMBERSandLETTERS COMMENT  

I suggest changing to COMMENT to something more descriptive, like your name or email address then save that off your github or digitalocean.

Add the following to your .bash_profile so that your ssh agent (gpg-agent) will know to also use your yubikey.

gpg-agent &> /dev/null  
if [ "$?" -ne "0" ]; then  
  gpg-agent --daemon &> /dev/null
fi

if [ -f "${HOME}/.gpg-agent-info" ]; then  
  . "${HOME}/.gpg-agent-info"
  export GPG_AGENT_INFO
  export SSH_AUTH_SOCK
  export SSH_AGENT_PID
fi  
export GPG_TTY=$(tty)  

And setup your ~/.gnupg/gpg-agent.conf to be:

# from brew install pinentry-mac
pinentry-program /usr/local/bin/pinentry-mac

# enables SSH support (ssh-agent)
enable-ssh-support

# writes environment information to ~/.gpg-agent-info
write-env-file  
use-standard-socket

# default cache timeout of 600 seconds
default-cache-ttl 600  
max-cache-ttl 7200  

Restart your terminal and test it out.

$ ssh-add -l
2048 6e:2f:6d:0d:ad:23:1c:1e:f0:e1:05:4e:f8:de:ad:1f cardno:000603633910 (RSA)  
$ ssh -T [email protected]
Hi tam7t! You've successfully authenticated, but GitHub does not provide shell access.  

Fixing issues

Occasionally the gpg-agent daemon seems to lock up causing ssh to timeout and commands like ssh-add -l to hang. I've added the following functions to my .bash_profile to fix these 💩.

kill-gpg-agent() {  
  killall -9 gpg-agent
}

start-gpg-agent() {  
  eval $(gpg-agent --daemon)
}

restart-gpg-agent() {  
  kill-gpg-agent
  echo 'remove your key'
  sleep 3
  start-gpg-agent
}

I had to do restart-gpg-agent about 4-5 times a day originally, but in the past month I've only had to do it a handful of times.