GPG+YubiKey

这是一篇配置 YubiKey 来储存 GPG Subkeys (encryption/signing/authentication) 的 memo。关于这的(更详细更靠谱的)一切都可以在 YubiKey-Guide 找到。

这次我使用的硬件是一支 YubiKey 5C NFC + 一支 YubiKey 5 NFC(备用),平台是 macOS。

Update: 14.12.2020

最近发现在如果在弹窗之后再插入智能卡会报 Invalid ID 的错,大概像这样

大概是 GnuPG 的 Bug 吧。

解决方法是 gpg --card-status

Why Subkeys

OpenPGP 的 Subkeys 解决了一些实际问题,能使 PGP 的使用更加安全:

  1. 有了子密钥,我们就可以把主密钥离线保存(安全的地方)。这样一旦某个子密钥泄露可以很轻松地用主密钥吊销子密钥

  2. 可以为在不同的环境的使用签发不同的子密钥

配置流程

macOS 上直接安装一个 GPGTools 大概是比用 homebrew 更简单的方法,很多操作可以直接用 GUI 搞定了。

主密钥

⚠️:最佳实践是找一个安全的离线环境(比如崭新的虚拟机里)来生成你的 Master Primary Key。因为你的主密钥只应该用来签发和注销子密钥,这就意味着主密钥以后也应该一直被安全地离线储存。甚至可以考虑 paperkey

另外:一旦密钥导入了 YubiKey 之后是无法导出的。所以即使也可以直接在 YubiKey 上生成密钥,但是并不推荐,详见后文「备份」。

这次我跳过了生成主密钥的步骤,直接使用了之前旧的密钥(后果自负)。

生成子密钥

$ gpg --expert --edit-key KEYID

Signing

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)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (12) ECC (encrypt only)
  (13) Existing key
  (14) Existing key from card
Your selection? 4
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
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
Key expires at Sat Nov  6 13:41:11 2021 CET
Is this correct? (y/N) y
Really create? (y/N) y
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

sec  rsa4096/2B25A7A2E527F079
     created: 2020-11-06  expires: 2024-11-06  usage: SC
     trust: ultimate      validity: ultimate
ssb  rsa4096/A9A7991F7E188C8A
     created: 2020-11-06  expires: 2024-11-06  usage: E
ssb  rsa4096/AB4760F824F740DB
     created: 2020-11-06  expires: 2021-11-06  usage: S
[ultimate] (1). testkey <email@example.com>

注意选择 Key 类型、Key 长度、有效期。

关于有效期:对于子密钥的应用场景来说不宜太长,可以考虑 1y

Encryption

类型选 (6) RSA (encrypt only),其他同上。

Authentication

GPG 没有提供单独的类型,可以选择 (8) RSA (set your own capabilities) 然后选择/反选,只留下 authenticate capability,其他同上。

保存退出

gpg> save

备份

导出公钥+私钥,自行(离线)保存备份(见前文「⚠️」)

$ gpg --export -a KEYID > public.key
$ gpg --export-secret-key -a KEYID > private.key

(可选)把公钥上传到 key server

$ gpg --send-keys

用 GPGTools 这些操作直接用 GUI 非常简单。

准备 YubiKey

插入 Yubikey

$ gpg --card-edit

更改默认的 PIN 和 Admin PIN (PUK)

默认 PIN: 123456

默认 Admin PIN:12345678

gpg/card> admin
Admin commands are allowed

gpg/card> passwd
gpg: OpenPGP card no. 000000000000000000000000 detected

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

这两个口令要保管好。

Key To Card

⚠️:把密钥上传到 YubiKey 的命令 keytocard不可逆的,再次确认你已经妥善备份了你的主密钥。

$ gpg --edit-key KEYID

通过类似 key 1key 2 的命令来选择/反选子密钥,通过 keytocard 命令来传输,以 Signature key 为例:

gpg> key 1

sec  rsa4096/0xFF3E7D88647EBCDB
    created: 2017-10-09  expires: never       usage: C
    trust: ultimate      validity: ultimate
ssb* rsa4096/0xBECFA3C1AE191D15
    created: 2017-10-09  expires: 2018-10-09  usage: S
ssb  rsa4096/0x5912A795E90DD2CF
    created: 2017-10-09  expires: 2018-10-09  usage: E
ssb  rsa4096/0x3F29127E79649A3D
    created: 2017-10-09  expires: 2018-10-09  usage: A
[ultimate] (1). Dr Duh <doc@duh.to>

gpg> keytocard
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1

You need a passphrase to unlock the secret key for
user: "John Doe <doe@john.com>"
4096-bit RSA key, ID 0xBECFA3C1AE191D15, created 2016-05-24

分别对 Signature/Authentication/Encryption 三个 Key 分别传输之后可以保存退出:

gpg> save

验证

可以删除本机的私钥,然后签名/解密一个文件试试,一切正常的话 GPGTools 会出现要求插入智能卡的 prompt:

使用多个 YubiKey

不太清楚「为多个 YubiKey 导入同一套子密钥而不是重新生成一套」是不是好的实践。但是操作的方法都是类似的。

基本上步骤就是在本机恢复主密钥,再将前面的流程(从「准备 YubiKey」开始)用另一张 YubiKey 再进行一遍。

实际配置完第二张卡之后我发现系统会要求你使用新卡,也就是说没有办法交替使用两张卡。在 YubiKey-Guide 的 issue 里找到如下解决方法:

  1. killall gpg-agent
  2. rm -r ~/.gnupg/private-keys-v1.d/
  3. 插入新的 YubiKey
  4. gpg --card-edit

每次要换卡使用的话就重新执行这些步骤就好了。

Rotating Key

It is good practice to occassionally rotate sub-keys.

当一个子密钥过期时,我们可以选择:A. Renew B. Replace,不管你选择哪一种,都需要用到(离线备份的)主密钥

选择 renew 即为现有的子密钥延长有效期比较方便。

选择 replace 即替换现有子密钥比较麻烦但是更加安全。因为 PGP 并不提供前向保密(Forward Secrecy)。所以选择 replace 意味着新的子密钥用不了了。所以这种方式事实上相当于你「丢失了」子密钥。不过你还是能用你备份的主密钥来进行解密等等。

所以还是得根据实际情况来确定一套合适的流程。