PGP and SSH keys on a Yubikey NEO

January 2, 2015 33 Comments

With the new year, I decided it was time to make a new PGP key. I wanted to keep this key on a Yubikey NEO and NEO-n for every day use. By using hardware tokens like the Yubikey, the private PGP keys never need to be stored on my computer. The PGP keys on the Yubikey can also be used for SSH public-key authentication.

My current PGP key can always be found at https://esev.com/pgp.key.

Master keys, Subkeys, and User IDs

OpenPGP keys normally have three parts: a single master key, one or more subkeys, and one or more user ids.

The master key is the most important key. Having the private half of the master key proves that you own the OpenPGP key. The master key is used to add/remove subkeys as well as to sign/certify other people's keys. You don't need to have the master key present for everyday signing and encryption. If possible, the master key should be kept offline and only used when adding or revoking subkeys or when certifying another person's PGP key.

Subkeys make maintenance of a OpenPGP key easier. Subkeys can be used for signing data, encrypting data, and/or for authentication. The lifetime and purpose (encrypt,sign,authenticate) of a subkey is controlled by the master key. Subkeys can be added and removed from the PGP key at any time by the owner of the master key.

Subkeys can be installed on a computer that does not have access to the master key. On that computer, the subkeys will be used for encryption/decryption and signing. If the subkeys (or computer) are ever stolen, the master key can then be used to revoke the stolen subkeys and to add new subkeys to the PGP key. This can all be done without generating a new PGP key as long as the master key was not also stolen.

For more information about subkeys, see the Debian wiki page about Subkeys.

User IDs are used to identify the owner of the OpenPGP key. The User ID normally contains the name and email address of the person who owns the PGP key. User IDs are added to a PGP key using the master key. When another person signs your PGP key, they sign both the public master key and the User ID parts of the PGP key.

Generating the master key:

Normally, when generating an OpenPGP key with GnuPG, a master key is created and an encryption key is added as a subkey. The master key can sign data and certify (sign) subkeys; and the encryption subkey is used to receive encrypted messages. The example below shows what the key looks like when choosing the defaults when creating the key.

pub  2048R/AAAAAAAA expires: 2y usage: SC  
sub  2048R/BBBBBBBB expires: 2y usage: E

In the example above, AAAAAAAA is the master key. Its usage is set to allow the key to Sign data and Certify subkeys. BBBBBBBB is a subkey restricted to being used only for Encryption.

I don't really want my master key stored on the Yubikey because if the Yubikey is lost, or my laptop stolen, I would have to revoke the master key and recreate a new PGP key. Instead, I'm going to generate and store the master key on a offline USB drive that is kept in a location that only I can access.

pub 3072R/AAAAAAAA expires: 1y usage: C

I've also removed the ability for the master key to sign data. I don't plan to have the master key available for daily use and only want the master key to be used for certifying/revoking subkeys and for certifying other people's PGP keys. Generating the key like this requires the use of the --expert flag for GnuPG.

# Make sure to store the master key on the USB drive
> mv .gnupg .gnupg.orig
> ln -s /media/USB .gnupg

# Set GnuPG to prefer strong hash and encryption algorithms
echo "cert-digest-algo SHA512" >> .gnupg/gpg.conf
echo "default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed" >> .gnupg/gpg.conf

> gpg --expert --gen-key
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
Your selection? 8

Possible actions for a RSA key: Sign Certify Encrypt Authenticate
Current allowed actions: Certify

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished 

Your selection? s
Your selection? e
Your selection? q

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 3072
Requested keysize is 3072 bits
Please specify how long the key should be valid.
         0 = key does not expire
        = key expires in n days
      w = key expires in n weeks
      m = key expires in n months
      y = key expires in n years
Key is valid for? (0) 1y
Key expires at Fri 01 Jan 2016 07:15:54 PM PST
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Eric Severance
Email address: esev@esev.com
Comment:
You selected this USER-ID:
    "Eric Severance <esev@esev.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o

public and secret key created and signed.
pub   3072R/B8EFD59D 2015-01-02 [expires: 2016-01-02]
      Key fingerprint = 856B 1F1C EAD0 1FE4 5C4C  6E97 961F 708D B8EF D59D
uid                  Eric Severance <esev@esev.com>

Now that you have the master key, it's good practice to create a revocation certificate. If you ever lose your PGP key, or forget the passphrase, you can use publish the revocation certificate to inform others that your key is no longer in use.

> gpg --gen-revoke B8EFD59D > /media/USB/B8EFD59D-revocation-certificate.asc

Create a revocation certificate for this key? (y/N) y
Please select the reason for the revocation:         
  0 = No reason specified
  1 = Key has been compromised
  2 = Key is superseded
  3 = Key is no longer used
  Q = Cancel
(Probably you want to select 1 here)
Your decision? 3
Enter an optional description; end it with an empty line:
> Using revocation certificate that was generated when key B8EFD59D was
> first created.  It is very likely that I have lost access to the
> private key.
> 
Reason for revocation: Key is no longer used
Using revocation certificate that was generated when key B8EFD59D was
first created.  It is very likely that I have lost access to the
private key.
Is this okay? (y/N) y
                     
ASCII armored output forced.
Revocation certificate created.

Please move it to a medium which you can hide away; if Mallory gets
access to this certificate he can use it to make your key unusable.
It is smart to print this certificate and store it away, just in case
your media become unreadable.  But have some caution:  The print system of
your machine might store the data and make it available to others!

Generating the encryption subkey

The next step is to create an encryption subkey. I chose to generate the encryption key using GnuPG, rather than with the Yubikey for a couple of reasons.

  1. Private keys that are generated on the Yubikey cannot be removed from the Yubikey. This has a benefit that the private key is never physically on the computer, but it also has the disadvantage that access to all encrypted data is lost if the Yubikey is ever stolen or lost or a new key is generated.
  2. I have multiple Yubikeys and I would like them all to share the same encryption key. If each Yubikey had its own encryption key then people would need to choose which key to use when sending encrypted messages (or remember to choose all keys). On the receiving side, I would need to make sure I have the correct Yubikey plugged in when decrypting a message. Having a single encryption key avoids these issues.
> gpg --edit-key B8EFD59D

gpg> addkey
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
Your selection? 6
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
        = key expires in n days
      w = key expires in n weeks
      m = key expires in n months
      y = key expires in n years
Key is valid for? (0) 1y
Key expires at Fri 01 Jan 2016 07:23:39 PM PST
Is this correct? (y/N) y
Really create? (y/N) y

pub  3072R/B8EFD59D  created: 2015-01-02  expires: 2016-01-02  usage: C   
                     trust: ultimate      validity: ultimate
sub  2048R/EE86E896  created: 2015-01-02  expires: 2016-01-02  usage: E   
[ultimate] (1). Eric Severance <esev@esev.com>

gpg> save

Make a backup of the secret keys

The encryption key was the last key that will be generated with GnuPG. The remaining keys will be generated directly on the Yubikey. Importing the encryption key into the Yubikey is a destructive process. It will remove the secret key from the GnuPG keyring. This is a good time to make a backup of the secret keys.

> gpg --export-secret-key B8EFD59D > \
    /media/USB/B8EFD59D-2015-01-01-EE86E896-secret.pgp

Generate the signing and authentication subkeys

The subkeys for signing and authentication will be unique for each Yubikey. This allows the subkeys to be generated directly on the Yubikey, where the private key cannot be accessed from the computer.

Before using GnuPG with the Yubikey, download the ykpersonalize tool and make sure the eject flag is set to 82 for OTP and CCID compatibility.

> ykpersonalize -m82
Firmware version 3.3.0 Touch level 1290 Program sequence 2

The USB mode will be set to: 0x82

Commit? (y/n) [n]: y

I like to delete the GnuPG secret key and reimport it from a backup each time I initialize the Yubikey. This makes sure the master and encryption keys are present in the GnuPG secret keyring.

# Refresh the GnuPG secret keyring from the backup
> gpg --delete-secret-key B8EFD59D
> gpg --import < /media/USB/B8EFD59D-2015-01-01-EE86E896-secret.pgp

> gpg --edit-key B8EFD59D

# First, create the signing key
gpg> addcardkey

 Signature key ....: [none]
 Encryption key....: [none]
 Authentication key: [none]

Please select the type of key to generate:
   (1) Signature key
   (2) Encryption key
   (3) Authentication key
Your selection? 1

Please specify how long the key should be valid.
         0 = key does not expire
        = key expires in n days
      w = key expires in n weeks
      m = key expires in n months
      y = key expires in n years
Key is valid for? (0) 1y
Key expires at Fri Jan  1 22:08:14 2016 PST
Is this correct? (y/N) y
Really create? (y/N) y  
                      
pub  3072R/B8EFD59D  created: 2015-01-02  expires: 2016-01-02  usage: C   
                     trust: ultimate      validity: ultimate
sub  2048R/EE86E896  created: 2015-01-02  expires: 2016-01-02  usage: E   
sub  2048R/79BF574F  created: 2015-01-02  expires: 2016-01-02  usage: S   
[ultimate] (1). Eric Severance <esev@esev.com>

# Do the same for the authentication key
gpg> addcardkey

 Signature key ....: 546D 6A7E EB4B 5B07 B3EA  7373 12E2 68AD 79BF 574F
 Encryption key....: [none]
 Authentication key: [none]

Please select the type of key to generate:
   (1) Signature key
   (2) Encryption key
   (3) Authentication key
Your selection? 3
                 
Please specify how long the key should be valid.
         0 = key does not expire
        = key expires in n days
      w = key expires in n weeks
      m = key expires in n months
      y = key expires in n years
Key is valid for? (0) 1y
Key expires at Fri Jan  1 22:09:41 2016 PST
Is this correct? (y/N) y
Really create? (y/N) y  
                      
pub  3072R/B8EFD59D  created: 2015-01-02  expires: 2016-01-02  usage: C   
                     trust: ultimate      validity: ultimate
sub  2048R/EE86E896  created: 2015-01-02  expires: 2016-01-02  usage: E   
sub  2048R/79BF574F  created: 2015-01-02  expires: 2016-01-02  usage: S   
sub  2048R/934AE2EE  created: 2015-01-02  expires: 2016-01-02  usage: A   
[ultimate] (1). Eric Severance <esev@esev.com>

# Use toggle and key to select the private encryption key
gpg> toggle
gpg> key 1
          
sec  3072R/B8EFD59D  created: 2015-01-02  expires: 2016-01-02
ssb* 2048R/EE86E896  created: 2015-01-02  expires: never     
ssb  2048R/79BF574F  created: 2015-01-02  expires: 2016-01-02
                     card-no: 0006 12345678
ssb  2048R/934AE2EE  created: 2015-01-02  expires: 2016-01-02
                     card-no: 0006 12345678
(1)  Eric Severance <esev@esev.com>

# Then move the encryption key from the GnuPG keyring to the Yubikey
gpg> keytocard
 Signature key ....: 546D 6A7E EB4B 5B07 B3EA  7373 12E2 68AD 79BF 574F
 Encryption key....: [none]
 Authentication key: DCE4 7FEA 4A72 E525 681C  6207 662E 5CA8 934A E2EE

Please select where to store the key:
   (2) Encryption key
Your selection? 2
                 
sec  3072R/B8EFD59D  created: 2015-01-02  expires: 2016-01-02
ssb* 2048R/EE86E896  created: 2015-01-02  expires: never     
                     card-no: 0006 12345678
ssb  2048R/79BF574F  created: 2015-01-02  expires: 2016-01-02
                     card-no: 0006 12345678
ssb  2048R/934AE2EE  created: 2015-01-02  expires: 2016-01-02
                     card-no: 0006 12345678
(1)  Eric Severance <esev@esev.com>
gpg> save

Repeat the same steps for each Yubikey that will be used with this OpenPGP key.

Save and Distribute the public OpenPGP key

When the master key was created, and each time a subkey was created, a public and private RSA key was also generated. The private keys should remain on the USB drive and on the Yubikey.

The public keys should be distributed to a location where others can find it. I'm choosing to upload them to my website, but an alternative would be to upload them to a public keyserver.

Once a location has been chosen, it's a good idea to embed the location into the PGP key. That way users know where to find the version of the key with the most up-to-date signatures, subkeys, and revocations. GnuPG can also automatically fetch the latest version of the key with --refresh-keys if the location is embedded within the key. The keyserver command embeds a URL to this key within the public PGP key.

> gpg --edit-key B8EFD59D

gpg> keyserver
Enter your preferred keyserver URL: https://www.esev.com/static/B8EFD59D.asc

gpg> showpref
[ultimate] (1). Eric Severance <esev@esev.com>
     Cipher: AES256, AES192, AES, CAST5, 3DES
     Digest: SHA512, SHA384, SHA256, SHA224, SHA1
     Compression: ZLIB, BZIP2, ZIP, Uncompressed
     Features: MDC, Keyserver no-modify
     Preferred keyserver: https://www.esev.com/static/B8EFD59D.asc

gpg> save

# Backup the public key
> gpg --armor --export B8EFD59D > B8EFD59D.asc

# Upload it to the website
# > scp B8EFD59D.asc user@server:public_html/static/B8EFD59D.asc

# Or upload it to a keyserver
> gpg --keyserver hkps://hkps.pool.sks-keyservers.net --send-key B8EFD59D

Remove the master key and update the Yubikey

At this point, the USB drive can be disconnected and the original .gnupg directory restored.

# Remove the symlink pointing to /media/USB
> rm .gnupg

# Replace the original directory
> mv .gnupg.orig .gnupg

The next step is to change the Yubikey PINs and import the public key.

> gpg --card-edit

Application ID ...: D2760001240102000006123456780000
Version ..........: 2.0
Manufacturer .....: Yubico
Serial number ....: 12345678
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: 2048R 2048R 2048R
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 2
Signature key ....: 546D 6A7E EB4B 5B07 B3EA  7373 12E2 68AD 79BF 574F
      created ....: 2015-01-02 06:08:04
Encryption key....: 2D45 A494 1428 C03C 45A9  47C0 19C9 D37E EE86 E896
      created ....: 2015-01-02 03:23:39
Authentication key: DCE4 7FEA 4A72 E525 681C  6207 662E 5CA8 934A E2EE
      created ....: 2015-01-02 06:09:40
General key info..: [none]

gpg/card> admin
Admin commands are allowed

# Change the PIN and Admin PINs
gpg/card> passwd
gpg: OpenPGP card no. D2760001240102000006123456780000 detected

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? 3
PIN changed.     

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

Your selection? q

# Make sure the PIN is entered before signing
gpg/card> forcesig

# Set the URL where the OpenPGP public key can be found.
gpg/card> url
URL to retrieve public key: https://www.esev.com/static/B8EFD59D.asc

# Fetch the public key into the local keyring
gpg/card> fetch
                                                          
gpg/card> quit

# Finally, populate the secret keyring with stub keys that point to the Yubikey
> gpg --card-status
Application ID ...: D2760001240102000006123456780000
Version ..........: 2.0
Manufacturer .....: Yubico
Serial number ....: 12345678
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : https://www.esev.com/static/B8EFD59D.asc
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: 2048R 2048R 2048R
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 2
Signature key ....: 546D 6A7E EB4B 5B07 B3EA  7373 12E2 68AD 79BF 574F
      created ....: 2015-01-02 06:08:04
Encryption key....: 2D45 A494 1428 C03C 45A9  47C0 19C9 D37E EE86 E896
      created ....: 2015-01-02 03:23:39
Authentication key: DCE4 7FEA 4A72 E525 681C  6207 662E 5CA8 934A E2EE
      created ....: 2015-01-02 06:09:40
General key info..: pub  2048R/79BF574F 2015-01-02 Eric Severance <esev@esev.com>
sec#  3072R/B8EFD59D  created: 2015-01-02  expires: 2016-01-02
ssb>  2048R/EE86E896  created: 2015-01-02  expires: 2016-01-02
                      card-no: 0006 12345678
ssb>  2048R/79BF574F  created: 2015-01-02  expires: 2016-01-02
                      card-no: 0006 12345678
ssb>  2048R/934AE2EE  created: 2015-01-02  expires: 2016-01-02
                      card-no: 0006 12345678

Notice how the master key has "sec#". The "#" at the end means that the private key is not present. It is stored on the USB drive (which should be removed and stored in a safe location now). You can also verify this with gpg -K

> gpg -K
sec#  3072R/B8EFD59D 2015-01-02 [expires: 2016-01-02]
uid                  Eric Severance <esev@esev.com>
ssb>  2048R/EE86E896 2015-01-02
ssb>  2048R/79BF574F 2015-01-02
ssb>  2048R/934AE2EE 2015-01-02

Setup SSH authentication

You may have noticed that a separate subkey was generated on the Yubikey for authentication. The authentication subkey can be used with OpenSSH to login to a server with public key authentication. The GnuPG gpg-agent has a flag, --enable-ssh-support, that allows it to function as a ssh-agent.

> gpg-agent --daemon --enable-ssh-support --write-env-file ~/.gpg-agent-info
GPG_AGENT_INFO=/tmp/gpg-Z74lEJ/S.gpg-agent:25585:1; export GPG_AGENT_INFO;
SSH_AUTH_SOCK=/tmp/gpg-KS5kJr/S.gpg-agent.ssh; export SSH_AUTH_SOCK;
SSH_AGENT_PID=25585; export SSH_AGENT_PID;

Copy-and-paste the environment variables into your terminal to enable support for the gpg-agent. The variables are also stored in ~/.gpg-agent-info which can be sourced in .bash_profile when logging in.

OpenPGP public keys can be converted into SSH public keys using gpgkey2ssh. Specify the id of the authentication subkey when running gpgkey2ssh.

> gpgkey2ssh 934AE2EE
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA ... oSFl8ZpqJ COMMENT

This key can then be added to ~/.ssh/authorized_keys on a remote server. The private key, stored on the Yubikey, will be used when connecting to the remote server.

Key Sizes and Expirations

Currently, 2048-bit is the largest key size supported on the Yubikey. According to Digicert, 2048-bit RSA keys are strong enough to last more than a lifetime. Digicert's "The Strength of an SSL Certificate page" has a video and a link to the math behind this logic.

I'm hoping to never have to generate another master key. I've increased the key size on the master key to 3072-bits, which is recommended by NIST, in SP 800-57 Part 1, for keys that should last beyond the year 2031.

It is possible to generate 4096-bit RSA keys with GnuPG but multiple sources on the GnuPG mailing list have suggested RSA keys beyond 3072-bits are not adding much in terms of additional security to justify the additional CPU resources required. Beyond 3072-bits, it is better to use Elliptic Curve (EC/ECC) keys instead of RSA. Unfortunately, today ECC keys aren't widely supported in OpenGPG. Maybe by 2031 we'll be using ECC keys in OpenPGP and it will make sense to generate a new key then.

I've chosen a key lifetime of one year based on some best practices for OpenPGP keys. There are others who have made the argument for non-expiring master keys, but I think setting an expiration on the master key will encourage users to update the key at least once per year to receive new/revoked subkeys.

Additional Resources

Categories: Tutorial