之前买了两个 YubiKey,一直只使用 OTP 和 FIDO 功能,今天把 GPG 也配置上。

本文基于 macOS 进行配置。

YubiKey

配置

首先安装:brew install gnupg

YubiKey 的配置

你可以使用 ykman openpgp reset --force 来直接重置 OpenPGP 应用,但这会清除卡上已有的 OpenPGP 密钥、证书和相关配置。执行前请确认已经做好离线备份。

修改默认 PIN

如果是第一次使用或者刚重置,需要先修改默认 PIN。

1
2
3
4
5
6
7
8
9
10
# 插入 YubiKey
# 确保 smartcard 守护进程运行
gpg --card-status
# 如果第一次用,先改 Admin PIN 和 User PIN
gpg --card-edit
gpg/card> admin
gpg/card> passwd
# 1 - change PIN (默认 123456,改成 ≥6 位)
# 3 - change Admin PIN (默认 12345678,改成 ≥8 位)
gpg/card> quit

修改密钥算法

这一步需要在写入密钥前完成。YubiKey 5.2.3 及以上固件支持 Curve25519;签名和认证槽实际使用 Ed25519,加密槽实际使用 X25519。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gpg --card-edit
gpg/card> admin
gpg/card> key-attr
Changing card key attribute for: Signature key
请选择存储在卡中的密钥类型:
(1) RSA
(2) ECC
你的选择? 2
请选择我们想要的椭圆曲线:
(1) Curve 25519
你的选择? 1
Changing card key attribute for: Encryption key
你的选择? 2
你的选择? 1
Changing card key attribute for: Authentication key
你的选择? 2
你的选择? 1
gpg/card> quit

GPG 的配置

创建主密钥

首先使用 gpg --full-generate-key --expert 生成主密钥,选择 (11) ECC (set your own capabilities)(1) Curve 25519,用途仅保留 Certify。主密钥只用于管理身份和子密钥,日常签名、加密和 SSH 认证都交给子密钥。

生成完成后记录主密钥指纹:

1
gpg --list-secret-keys --with-subkey-fingerprint --with-keygrip

本文后面出现的 <FINGERPRINT> 均替换为主密钥指纹;Keygrip 只在 SSH 配置的 sshcontrol 中使用。

生成子密钥

子密钥这里设置为 1 年有效期。写入 YubiKey 后,私钥材料无法再从 YubiKey 导出;如果没有提前备份,后续就不能再复制到另一把 YubiKey。过期前可以用离线保存的主密钥延长子密钥有效期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 进入密钥编辑模式(替换为实际主密钥指纹)
gpg --edit-key --expert <FINGERPRINT>
# 1. 生成签名子密钥(S)
addkey
(11) ECC (set your own capabilities)
切换:仅保留 Sign(S)
(1) Curve 25519
有效期:1y
# 2. 生成加密子密钥(E)
addkey
(12) ECC (encrypt only)
(1) Curve 25519
有效期:1y
# 3. 生成认证子密钥(A)
addkey
(11) ECC (set your own capabilities)
切换:仅保留 Authenticate(A)
(1) Curve 25519
有效期:1y
# 4. 保存
save

写入 YubiKey

在这里,我会写入多个 YubiKey。正常情况下,keytocard 成功并 save 后,本地的子密钥私钥会被替换为指向该卡的 stub,所以如果想要将同一份子密钥写入多个 YubiKey,需要先备份子密钥,并且每写一把新卡都从备份恢复一次。

备份密钥

1
2
3
gpg --armor --export-secret-keys <FINGERPRINT> > master-secret.asc
gpg --armor --export-secret-subkeys <FINGERPRINT> > subkeys-secret.asc
gpg --armor --export <FINGERPRINT> > public.asc

写入 YubiKey

恢复密钥

如果是第一次写入 YubiKey,可以直接使用当前密钥环;如果要写入第二把或更多 YubiKey,建议使用临时 GNUPGHOME 从备份恢复密钥后再写入。

1
2
3
4
5
6
mkdir ./tmp-gpghome
chmod 700 ./tmp-gpghome
export GNUPGHOME="$PWD/tmp-gpghome"

gpg --import ./public.asc
gpg --import ./subkeys-secret.asc

插入 YubiKey

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gpg --edit-key --expert <FINGERPRINT>
list
key 1
keytocard
1
key 1

key 2
keytocard
2
key 2

key 3
keytocard
3

save

清理并备份

先导出信任数据库和吊销证书:

1
2
gpg --export-ownertrust > ownertrust.txt
gpg --gen-revoke <FINGERPRINT> > revoke-cert.asc

然后删除本地的主密钥私钥,只保留 YubiKey 子密钥 stub:

1
gpg --delete-secret-keys '<FINGERPRINT>!'

这里的 ! 表示只删除主密钥的私钥部分,不删除子密钥 stub。最后将 master-secret.ascsubkeys-secret.ascpublic.ascownertrust.txtrevoke-cert.asc 离线保存。其中 public.asc 可以公开。

使用

SSH

首先检查是否开启 SSH 支持:

1
grep -qxF 'enable-ssh-support' ~/.gnupg/gpg-agent.conf || echo 'enable-ssh-support' >> ~/.gnupg/gpg-agent.conf

然后重启 agent:

1
2
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent

添加环境变量:

可以添加到 Shell 启动脚本

1
2
export GPG_TTY="$(tty)"
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"

如果 ssh-add -L 没有输出认证子密钥,需要把认证子密钥的 Keygrip 写入 sshcontrol

1
2
3
gpg --list-secret-keys --with-keygrip <FINGERPRINT>
echo '<AUTH_SUBKEY_KEYGRIP>' >> ~/.gnupg/sshcontrol
gpgconf --kill gpg-agent

最后通过 ssh-add -L 导出公钥,并放入到服务器的 ~/.ssh/authorized_keys 中即可。