A GnuPG key can be secured by storing it on a USB device with OpenPGP Card support such as the Nitrokey Pro.
Once safely stored on the device we can send commands to the device to decrypt, sign and authenticate with the GnuPG key, but there is no way to extract a copy of the key from the device.
The use of the device is protected by a PIN. The device locks if we fail to enter the correct PIN 3 times in a row. There's an Admin PIN that can reset the first PIN, but if we also fail to enter the correct Admin PIN 3 times in a row then the device self-destructs with smoke coming out of it.
Actually I'm not sure about the last bit about the smoke, but OpenPGP Card does have PIN retry counters that must be taken seriously.
Moving a key to the device has little impact on the daily use of GnuPG. Most GnuPG commands work as if the key was still stored in the keyring on the hard drive. In fact if we list the keys in the keyring, it appears the key is still there:
bob@home:~$ gpg -K /home/bob/.gnupg/secring.gpg ---------------------------- sec# 2048R/675E7FA7 2015-03-26 uid Bob Bobmeister <bob@test.com> ssb> 2048R/7AC1F9A2 2015-03-26 ssb> 2048R/1CA3EBA3 2016-02-06
It is just a proxy though. Any GnuPG command we issue that requires the private key is redirected transparently to the device through the OpenPGP Card interface.
I'm going to assume, that you have already created a personal key, as described in this previous post. This is the key we will move to the card.
The OpenPGP Card USB device described in this post is a Nitrokey Pro, which is built on Open Source hardware - no hidden backdoors. The OS is Ubuntu 12.04 with a rather old GnuPG v1.4.11 but even Ubuntu 16.04 still uses the v1.4.x series by default.
If you have only recently wrapped your head around Public Key Cryptography and the idea that keys are split into a public and private key, then unfortunately we now have to complicate matters further by introducing the concept of subkeys.
A freshly created GnuPG key, like the one from the previous post, looks something like this:
bob@home:~$ gpg -k /home/bob/.gnupg/pubring.gpg ---------------------------- pub 2048R/675E7FA7 2015-03-26 uid Bob Bobmeister <bob@test.com> sub 2048R/7AC1F9A2 2015-03-26
Although we strictly remember creating just one key, the keyring clearly shows two. One with ID 675E7FA7 marked "pub" and another with ID 7AC1F9A2 marked "sub".
What are these two keys?
The first key is the Primary Signing Key and the latter is a subkey for encryption.
So why do we have a subkey?
Imagine that one day we want to replace the key. Maybe the private key was compromised or the key size became inadequate with time. If we simply discard the old key and create a new one, we loose all the history of the old key. All the signatures from friends on the public key that are proof of trust, gone. We have to convince our friends to import the new public key into their keyrings and sign it again, a logistical mess.
With subkeys we don't have to throw everything overboard. Our friend's signatures on the key are bound to the Primary Signing Key, as is our user ID. Subkeys are specialized for either encryption, signing or authentication, but they don't carry trust directly. We can replace subkeys individually and push those changes to friends. The changes are automatically merged into our friends keyrings as an update to the existing key, because the subkeys are signed with our Primary Signing Key that our friends already trust.
There are three types of subkey: one for encryption, one for signing and one for authentication. The Nitrokey Pro conveniently has a slot for each type of subkey. To be more precise it is the private part of the subkeys that are stored on the Nitrokey Pro.
The Primary Signing Key is not stored on the Nitrokey Pro but will be moved to a safe backup. Using the private Primary Signing Key from its backup media is discussed in a separate post.
In this post we go through the following steps to move our subkeys to the Nitrokey Pro.
The public keys stay put in the keyring on our hard drive, only the private subkeys are replaced with proxies in the keyring.
So let's get started.
Check if you have a file named /etc/libccid_Info.plist? If it exists, then you already have the CCID smartcard device driver installed and can skip ahead to the next section. If not then follow the steps below.
Check in /etc/apt/sources.list if the "universe" repository is commented out. If so then add it with this command:
sudo add-apt-repository universe
Now install the CCID smartcard device driver.
sudo apt-get install libccid
If you are running Ubuntu 16.04 LTS then the Nitrokey Pro is already registered in the device list and you can skip this section.
The file /etc/libccid_Info.plist has three arrays named ifdVendorID, ifdProductID and ifdFriendlyName. Add the Nitrokey Pro vendor, productID and friendlyname to the arrays. Although the information is split into three arrays its important they go into the same index of these arrays.
sudo vi /etc/libccid_Info.plist
Press i to enter Vi's edit mode. Add the array entries shown below.
<key>ifdVendorID</key> <array> ... <string>0x20A0</string> <array> <key>ifdProductID</key> <array> ... <string>0x4108</string> <array> <key>ifdFriendlyName</key> <array> ... <string>Nitrokey Pro</string> <array>
The press Esc to exit edit mode and type ":wq" to write the file and quit.
Udev is a core linux service that manages device nodes under the /dev file hierarchy. Configure the permissions that will be required to access the Nitrokey Pro device node by adding a file named /etc/udev/rules.d/40-nitrokey-rules and add the content below.
sudo vi /etc/udev/rules.d/40-nitrokey.rules
Press i to enter edit mode. Then paste this content.
# Nitrokey U2F KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="f1d0" SUBSYSTEM!="usb", GOTO="gnupg_rules_end" ACTION!="add", GOTO="gnupg_rules_end" # USB SmartCard Readers ## Crypto Stick 1.2 ATTR{idVendor}=="20a0", ATTR{idProduct}=="4107", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess" ## Nitrokey Pro ATTR{idVendor}=="20a0", ATTR{idProduct}=="4108", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess" ## Nitrokey Storage ATTR{idVendor}=="20a0", ATTR{idProduct}=="4109", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess" ## Nitrokey Start ATTR{idVendor}=="20a0", ATTR{idProduct}=="4211", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess" ## Nitrokey HSM ATTR{idVendor}=="20a0", ATTR{idProduct}=="4230", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", GROUP+="plugdev", TAG+="uaccess" LABEL="gnupg_rules_end"
Press Esc to exit edit mode then type ":wq" to write and quit.
Give the file the same permissions as other files in this folder:
sudo chmod go+r /etc/udev/rules.d/40-nitrokey.rules
Insert the USB device and run a OpenPGP Card status. If the output doesn't show like this or you find that doing a card status only works with root access then revisit the previous section on setting up Udev:
bob@home:~$ gpg --card-status Application ID ...: D2760001240102010005000039000000 Version ..........: 2.1 Manufacturer .....: ZeitControl Serial number ....: 00003900 Name of cardholder: [not set] Language prefs ...: de Sex ..............: unspecified URL of public key : [not set] Login data .......: [not set] Private DO 1 .....: [not set] Private DO 2 .....: [not set] Signature PIN ....: forced Key attributes ...: 2048R 2048R 2048R Max. PIN lengths .: 32 32 32 PIN retry counter : 3 0 3 Signature counter : 0 Signature key ....: [none] Encryption key....: [none] Authentication key: [none] General key info..: [none]
First order of the day is to change the default PIN and Admin PIN. The PIN is required when we decrypt, sign and authenticate. The Admin PIN is used to administrate the card and can reset the PIN retry counter.
There is also a "Reset Code" that can be used to reset the PIN retry counter. It is needed if the OpenPGP Card was issued by an organization that didn't provide us with the Admin PIN. We don't need the Reset Code and leave it uninitialized for the Nitrokey Pro.
The default PIN is 123456 and the default Admin PIN is 12345678.
gpg --card-edit 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? 1 Please enter the PIN: 123456 New PIN Enter New PIN: manThisIsALongPost Repeat New PIN: manThisIsALongPost Pin changed. Your selection? 3 Please enter the Admin PIN Enter Admin PIN: 12345678 New Admin PIN: wishICouldMakeItShorter Repeat Admin PIN: wishICouldMakeItShorter PIN changed. Your selection? Q gpg/card> name Cardholder's surname: Bobmeister Cardholder's given name: Bob gpg/card> lang Language preferences: en
gpg/card> quit
Remember we already have a Primary Signing Key and a subkey for encryption. Now let's create a subkey for signing. Although it is possible to create keys directly on the devices, we will create it in the keyring on the hard drive, then back up the keyring before we finally move the private keys to the device.
First we need the ID of the Primary Signing Key
bob@home:~$ gpg -k /home/bob/.gnupg/pubring.gpg ---------------------------- pub 2048R/675E7FA7 2015-03-26 uid Bob Bobmeister <bob@test.com> sub 2048R/7AC1F9A2 2015-03-26
From the output above we find the key ID to be 675E7FA7. Edit this key in GnuPG's interactive mode to add a RSA subkey for signing only and give it a expiration period of 2 years:
bob@home:~$ gpg --edit-key 675E7FA7 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? 4 RSA keys may be between 1024 and 4096 bits long. What keysize do you want? (2048) 2048 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) 2y
GnuPG lists the Primary Signing Key and all subkeys. The last one says "usage: S" for signing. The other key is for "usage: E" encryption.
pub 2048R/675E7FA7 created: 2015-03-26 expires: never usage: SC trust: ultimate validity: ultimate sub 2048R/7AC1F9A2 created: 2015-03-26 expires: never usage: E sub 2048R/1CA3EBA3 created: 2016-02-06 expires: 2018-02-05 usage: S [ultimate] (1). Bob Bobmeister <bob@test.com>
Notice the original encryption subkey doesn't have an expiration period yet. Let's fix that but first don't forget to save:
gpg> save
We already have the key ID of the encryption subkey from the previous output. Let's edit the key:
bob@home:~$ gpg --edit-key 7AC1F9A2
While we selected the key ID of the subkey in the command above, we need to select it again in the interactive mode. First list the keys:
gpg> list pub 2048R/675E7FA7 created: 2015-03-26 expires: never usage: SC trust: ultimate validity: ultimate sub 2048R/7AC1F9A2 created: 2015-03-26 expires: never usage: E sub 2048R/1CA3EBA3 created: 2016-02-06 expires: 2018-02-05 usage: S [ultimate] (1). Bob Bobmeister <bob@test.com>
The subkey we want to change is at index 1 (starting from 0). Choose the key this way:
gpg> key 1 pub 2048R/675E7FA7 created: 2015-03-26 expires: never usage: SC trust: ultimate validity: ultimate sub* 2048R/7AC1F9A2 created: 2015-03-26 expires: never usage: E sub 2048R/1CA3EBA3 created: 2016-02-06 expires: 2018-02-05 usage: S [ultimate] (1). Bob Bobmeister <bob@test.com>
Notice the selected subkey is marked with a star. Now we can change its expiration period:
gpg> expire Changing expiration time for a subkey. 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) 2y Key expires at man 05 feb 2018 21:34:08 CET pub 2048R/675E7FA7 created: 2015-03-26 expires: never usage: SC trust: ultimate validity: ultimate sub* 2048R/7AC1F9A2 created: 2015-03-26 expires: 2018-02-05 usage: E sub 2048R/1CA3EBA3 created: 2016-02-06 expires: 2018-02-05 usage: S [ultimate] (1). Bob Bobmeister <bob@test.com> gpg> save
Again don't forget to save.
Backup the files under ~/.gnupg to an encrypted USB flash drive.
This is the last chance to backup the private keys before they are removed from the hard drive.
To move a subkey to the USB device we enter GnuPG edit mode again, select the subkey and run the command keytocard.
Open edit mode with the Primary Signing Key ID 675E7FA7.
bob@home:~$ gpg --edit-key 675E7FA7 pub 2048R/675E7FA7 created: 2015-03-26 expires: never usage: SC trust: ultimate validity: ultimate sub 2048R/7AC1F9A2 created: 2015-03-26 expires: 2018-02-05 usage: E sub 2048R/1CA3EBA3 created: 2016-02-06 expires: 2018-02-05 usage: S [ultimate] (1). Bob Bobmeister <bob@test.com>
Toggle key mode to see private keys.
gpg> toggle sec 2048R/675E7FA7 created: 2015-03-26 expires: never ssb 2048R/7AC1F9A2 created: 2015-03-26 expires: never ssb 2048R/1CA3EBA3 created: 2016-02-06 expires: never (1) Bob Bobmeister <bob@test.com>
The subkey for decryption is at index 1 (beginning from 0):
gpg> key 1 sec 2048R/675E7FA7 created: 2015-03-26 expires: never ssb* 2048R/7AC1F9A2 created: 2015-03-26 expires: never ssb 2048R/1CA3EBA3 created: 2016-02-06 expires: never (1) Bob Bobmeister <bob@test.com>
Finally run keytocard and select the encryption key slot.
gpg> keytocard Signature key ....: [none] Encryption key....: [none] Authentication key: [none] Please select where to store the key: (2) Encryption key Your selection? 2 gpg: writing new key sec 2048R/675E7FA7 created: 2015-03-26 expires: never ssb* 2048R/7AC1F9A2 created: 2015-03-26 expires: never card-no: 0005 00003900 ssb 2048R/1CA3EBA3 created: 2016-02-06 expires: never (1) Bob Bobmeister <bob@test.com>
The subkey is now safely stored on the USB device.
Moving the private signing subkey is pretty much the same, but first we must deselect the current subkey by running the command key 1 again.
gpg> list sec 2048R/675E7FA7 created: 2015-03-26 expires: never ssb* 2048R/7AC1F9A2 created: 2015-03-26 expires: never card-no: 0005 00003900 ssb 2048R/1CA3EBA3 created: 2016-02-06 expires: never (1) Bob Bobmeister <bob@test.com> gpg> key 1 sec 2048R/675E7FA7 created: 2015-03-26 expires: never ssb 2048R/7AC1F9A2 created: 2015-03-26 expires: never card-no: 0005 00003900 ssb 2048R/1CA3EBA3 created: 2016-02-06 expires: never (1) Bob Bobmeister <bob@test.com> gpg> key 2 sec 2048R/675E7FA7 created: 2015-03-26 expires: never ssb 2048R/7AC1F9A2 created: 2015-03-26 expires: never card-no: 0005 00003900 ssb* 2048R/1CA3EBA3 created: 2016-02-06 expires: never (1) Bob Bobmeister <bob@test.com> gpg> keytocard Signature key ....: [none] Encryption key....: [none] Authentication key: [none] Please select where to store the key: (1) Signature key (3) Authentication key Your selection? 1 gpg: writing new key sec 2048R/675E7FA7 created: 2015-03-26 expires: never ssb 2048R/7AC1F9A2 created: 2015-03-26 expires: never card-no: 0005 00003900 ssb* 2048R/1CA3EBA3 created: 2016-02-06 expires: never card-no: 0005 00003900 (1) Bob Bobmeister <bob@test.com>
Now save the changes:
gpg> save
If we do a card status now, we should see the decryption and signing slots initialized.
bob@home:~$ sudo gpg --card-status Application ID ...: D2760001240102010005000039000000 Version ..........: 2.1 Manufacturer .....: ZeitControl Serial number ....: 00003900 Name of cardholder: Bob Bobmeister Language prefs ...: en Sex ..............: unspecified URL of public key : [not set] Login data .......: [not set] Private DO 1 .....: [not set] Private DO 2 .....: [not set] Signature PIN ....: forced Key attributes ...: 2048R 2048R 2048R Max. PIN lengths .: 32 32 32 PIN retry counter : 3 0 3 Signature counter : 0 Signature key ....: 8AB7 D354 B481 B283 D2B6 731D 4E76 A255 1CA3 EBA3 created ....: 2016-02-06 15:27:55 Encryption key....: 86B8 307E 90D5 1478 1B66 9266 A3EC 32EA 7AC1 F9A2 created ....: 2015-03-26 20:32:30 Authentication key: [none] General key info..: pub 2048R/1CA3EBA3 2016-02-06 Bob Bobmeister <bob@test.com> sec 2048R/675E7FA7 created: 2015-03-26 expires: never ssb> 2048R/7AC1F9A2 created: 2015-03-26 expires: 2018-02-05 card-no: 0005 00003900 ssb> 2048R/1CA3EBA3 created: 2016-02-06 expires: 2018-02-05 card-no: 0005 00003900
Let's do a little test run.
Insert the USB device. Then run the following commands to encrypt a file with our own public encryption key, still present in our keyring on the hard drive. Finally decrypt it with the private key that was moved to the USB device:
bob@home:~$ echo 'hello world' > test.txt bob@home:~$ gpg -o test.txt.gpg -r bob -e test.txt bob@home:~$ gpg -o test2.txt -d test.txt.gpg bob@home:~$ cat test2.txt hello world
Finally we are ready to remove the Primary Signing Key from the keyring on the hard drive.
Don't remove the Primary Signing key before you have backed up the keyring as described in a previous section!
We are going to use the gpg command "--delete-secret-keys" which takes the Primary Signing Key as argument, but also deletes its private subkey proxies. Because we only want to delete the private Primary Signing Key from the keyring, we export the private subkeys before deleting the private Primary Signing Key and import them back afterwards.
First export the subkeys in a file. Append an exclamation mark to the subkey IDs.
bob@home:~$ gpg -o subkeys --export-secret-subkeys 7ac1f9a2! 1ca3eba3!
Then delete the private Primary Signing Key including the private subkeys:
bob@home:~$ gpg --delete-secret-keys 675e7fa7
Now import the private subkeys back into the keyring:
bob@home:~$ gpg --import subkeys
When we list the private keys, the Primary Signing Key now shows a # sign to indicate that the key is not actually available in the keyring.
bob@home:~$ gpg -K sec# 2048R/675E7FA7 2015-03-26 uid Bob Bobmeister <bob@test.com> ssb> 2048R/7AC1F9A2 2015-03-26 ssb> 2048R/1CA3EBA3 2016-02-06
Finally clean up the file with the exported subkeys.
bob@home:~$ shred --remove --zero subkeys
And we are done.