可能是最好的 Yubikey + GPG/SSH 智能卡教程

Hacks Oct 27, 2016

首先,经过了六个小时的栽坑和爬坑,我可能得开一个地图炮:

目前中文互联网真的找不到好教程。

当然这也其实没什么,因为我等会要开一个更大的地图炮:

英文互联网也不咋地。

好了,绕了太多坑,就此打住。

GPG 那些事儿

想必地球人都知道,PGP (Pretty Good Privacy)可能是世界上最优秀的非对称加密工具,公私钥体系真正避免了对称加密会带了的密钥泄漏的问题。而且基于去中心的模型,绕过了 CA,让每个人都能接触到 Web of Trust (WOT)。而 GPG (GNU Privacy Guard)则是 PGP 的开源 GNU 实现,不要钱。

感谢 CCTV,感谢 RMS。最要感谢的,是那些精通给产品取名艺术的人,他们没有当产品经理可能是上天的旨意。

如果不清楚 PGP 是咋工作的,维基百科走起。当然不知道的也估计不会再看这篇文章,不谈了。

为什么我们需要三个新的子密钥

显然,这篇文章的目的是把 GPG 密钥扔到 Yubikey(或其他由人类生产的智能卡)里并且使其能够工作。从安全的角度来说,你需要三个新的子密钥,分别是:

  • E (Encryption) 加密
  • S (Signing) 签署
  • A (Authentication) 认证

因为智能卡会丢,所以建议在电脑上生成,导出私钥离线备份好,然后扔到 Yubikey 里去。这不是多此一举,因为扔进去就出不来了。正确的 PGP 使用习惯很重要,用 Master Key 签署是一件很不好的事情。在可能的情况下,用子密钥干活,Master key 仅在子密钥过期的时候用来生成一个新的。除非特殊情况,否则不要生成有效期为 forever 的密钥。我就用的一年,正好,renew 六十次就差不多嗝屁了。

如果你不用 SSH,那你就不用生成那个认证密钥了,SE 够用。

GPG 发行版

建议所有的 Mac 用户都安装一个 GPGtools。brew 应该也行,不知道有些包会不会缺。

Linux 用户,你是最棒的。

生成子密钥

显然,我们该生成三对新的子密钥了。这里假设你之前已经生成了 GPG 主密钥。如果没有,自己生成一下也很简单,不谈了。

gpg --expert --edit-key [email protected]

生成签名子密钥:

gpg> addkey
Key is protected.

You need a passphrase to unlock the secret key for
user: "WhoAmI <[email protected]>"
4096-bit RSA key, ID 0x233333333333, created 2016-10-28

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)
Your selection? 4
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want?(密钥长度) (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 28 Oct 2017 12:00:00 PM UTC
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.

等一会就好了,接着照葫芦画瓢,生成加密用的:

gpg> addkey
Key is protected.

You need a passphrase to unlock the secret key for
user: "WhoAmI <[email protected]>"
4096-bit RSA key, ID 0x233333333333, created 2016-10-28

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)
Your selection? 6
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want?(密钥长度) (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 28 Oct 2017 12:00:00 PM UTC
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.

认证(auth)密钥:

gpg> addkey
Key is protected.

You need a passphrase to unlock the secret key for
user: "WhoAmI <[email protected]>"
4096-bit RSA key, ID 0x233333333333, created 2016-10-28

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)
Your selection? 8

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

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

Your selection? s

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

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

Your selection? e

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

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

Your selection? a

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

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

Your selection? q
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want?(密钥长度) (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 28 Oct 2017 12:00:00 PM UTC
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.

很好,保存一下退出

gpg> save

建议导出一下公私钥,离线存储到一个安全的地方。

gpg --export -a "[email protected]" > public.key
gpg --export-secret-key -a "[email protected]" > private.key

备份!
备份!
如果有必要,可以将公钥上传到公钥服务器,命令是这个

gpg --send-keys

Yubikey 初始化

首先,插入 Yubikey,

gpg --card-edit

修改个密码吧,注意有两个,一个是 PIN,一个是 Admin PIN,默认密码分别是 12345612345678Reset Code 也建议设一个,初始化的时候用。

gpg/card> passwd
gpg: OpenPGP card no. xxxxxxxxxxxxxxxxxxxx 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

其他的 URL,用户名字就不说了,喜欢的话就改吧。

如果有强迫症,可以用 newcardkey 直接在卡里生成新密钥。但是显然这会导致私钥完全无法导出,所以不详谈了。

将私钥装载到 Yubikey

gpg --edit-key "[email protected]"

会显示出你的主密钥和子密钥。

gpg> toggle

key【空格】数字 定位到子密钥,先装载 signing 子密钥。数字按实际的来,不一定是1

gpg> key 1
gpg> keytocard
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1
```
好了。先反选签名密钥,然后装载加密密钥:
```
gpg> key 1
gpg> key 2
gpg> keytocard
Please select where to store the key:
   (2) Encryption key
Your selection? 2
```
同样,反选加密密钥,装载认证密钥至 Yubikey:
```
gpg> key 2
gpg> key 3
gpg> keytocard
Please select where to store the key:
   (3) Authentication key
Your selection? 3
```
最后保存:
```
gpg> save
```

这样就可以了!

私钥想备份你现在也没办法了。
#### 删除,以及测试
可以删除本地 GPG 钥匙环里的公私钥,用 Yubikey 测试一下。如果可以的话,以上的就没问题了。

删除公钥:
```
gpg --delete-keys "[email protected]"
```
删除私钥:
```
gpg --delete-secret-keys "[email protected]"
```
这样电脑相当于不认识这个密钥了。插入 Yubikey,执行以下命令从公钥服务器获取公钥:
```
gpg --card-edit
gpg/card> fetch
gpg/card> quit
```
运行 `gpg --card-status`,系统会建立一个像是指针的东西,指向智能卡。

测试签署一下:
```
gpg -s testfile
```
没问题的话就可以了。如果还想帅一点,Yubico 有一个 `ykman` 工具可以让 Yubikey 仅在手指按后才会执行签名/加密/认证操作。至此,GPG 配置完毕。
### SSH!
如果有 VPS,那么用 Yubikey 连接是一件很装逼的事情,不可错过。

需要指出的是,绕了好多坑,而且教程特别少。会尽量详细一些。

安装 `gpg-agent`:
```
brew install gpg-agent
```
将以下脚本加入到 shell 的 rc 里。我用的是 zsh,在 `~/.zshrc`
```
if [ -f "${HOME}/.gpg-agent-info" ]; then
     . "${HOME}/.gpg-agent-info"
       export GPG_AGENT_INFO
       export SSH_AUTH_SOCK
       export SSH_AGENT_PID
fi
```
将以下内容添加到 `~/.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
enable-ssh-support
write-env-file
```
接下来就是坑了我好久的地方。这个命令默认没有,隐藏得很深。而且 bug 多,没有文档,要严格按照步骤来。
```
/usr/local/MacGPG2/bin/gpgkey2ssh DD091C21 > GPGSSH.pub
```
`DD091C21` 是我的认证子密钥,替换为自己的。直接运行会报错,必须按照这个格式。

这会输出一个符合 openssh 标准的公钥,将这个放在 VPS 的 `~/.ssh/authorized_keys` 里。至于如何配置 VPS 使用公钥认证,不再赘述。

注销重新登录用户,确保 `gpg-agent` 有运行。插入 Yubikey,
```
gpg --card-status
```
这步很重要,一定要执行。我在这里浪费了一个多小时。
```
ssh-add -L
```
看看有没有正常工作,如果有,会输出你的公钥。

如果以上都没有问题,那么恭喜,已经可以使用 Yubikey 来 ssh 了。像往常一样,`ssh usename@hostname` 就可以连接 VPS。如果用 `ykman` 配置了 `touch`,在灯光闪烁的时候摁一下即可,太炫酷了。

#### Clarification
`gpg-agent` 意义是什么?到底是用 `ssh` 来连接还是用 `gpg`?是不是还要一个 `openssh` 格式的私钥?这是少有教程提及的问题,也是我当时所疑惑的问题。

- 连接依然使用的是 `ssh`。和以往没有任何区别。
- 认证过程中,相当于 `openssh` 把公私钥钥匙环的部分“承包”给了 `gpg-agent`。`gpg-agent` 代替了 `ssh-agent` 的环境参数,接管了这部分内容。因此,密钥的解锁、认证,都是按照 `gpg` 的规则来。
- 不需要 `openssh` 格式的私钥,只需要导出一个公钥。使用方法和正常的公钥一样。私钥即是 `gpg` 的 "Authentication" 子密钥。
- 需要注意的是,`pgp` 的 Master key 是一个签名密钥,可以放在签名槽里。但这不是一个好的做法。
- 一定要备份主密钥__私钥__。这样一旦子密钥丢失,也还有机会吊销。


好累啊,坑太多。

aLPHAtOAD

太年轻,太简单,有时候幼稚。