PGP、OpenPGP 和 GPG

首先了解下 PGP、OpenPGP 以及 GPG 的区别😂

PGP

PGP (Pretty Good Privacy) 是一套用于讯息加密、验证的应用程序,主要开发者是 小菲利普·R·齐默尔曼(Philip R. Zimmermann, J. ,PGP 本身是商业应用程序,PGP Inc. 公司拥有 1991 年发布的原始 PGP 加密软件的版权,PGP 的所有权在 1997~2010 年经历多次更迭直到后来被 Symantec 公司收购发展至今

OpenPGP

OpenPGP 是一种 PGP 加密标准,1997 年,小菲利普·R·齐默尔曼 与 PGP.Inc 公司同意 IEFT 制定一项公开的互联网标准即 OpenPGP,术语 “OpenPGP”也可以描述任何支持 OpenPGP 系统的应用程序

GPG

GnuPG (GNU Privacy Guard ,一般称作 GPG) 是一个密码学软件,由 Werner Koch 开发并于 1999 年发布,GnuPG 是遵循 IEFT 制定的 OpenPGP 标准并且与 PGP 保持兼容的自由软件,它确保了 PGP 与 Symantec PGP 工具及 OpenPGP 之间可以进行标准互操作

安装 GPG 软件

绝大部分 Linux 发行版都已经默认安装了 GPG,笔者 目前是 Windows 环境,与其他很多的文章不同,本文并不使用 Gpg4Win,而是使用 Scoop 安装整个过程中所需的软件,如果你使用 Windows 且不知道 Scoop 是什么,可阅读 使用 Scoop 管理 Windows 下的软件和开发环境,在 Windows 下我们推荐使用 Scoop 安装 GPG

# 更新 Scoop 与 Scoop 软件库
$ scoop update
# 安装 GPG
$ scoop install gnupg

安装完成后,打开 Windows Terminal,可以看下 GPG 的信息

$ gpg --version
gpg (GnuPG) 2.3.4  # 这是 GPG 软件版本
libgcrypt 1.9.4
Copyright (C) 2021 g10 Code GmbH
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: C:\Scoop\apps\gnupg\current\home # 这是 GPG 工作的目录
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
AEAD: EAX, OCB
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

生成 GPG 密钥

生成密钥对

GPG 密钥的功能有:

  • [C] Certification key 用于认证(一般只给主密钥)
  • [S] Signature key 用于签名(如:GitHub 签名提交)
  • [E] Encryption key 用于加密
  • [A] Authentication key 用于身份认证(如:SSH 登录服务器、SSH 访问 GitHub 仓库)

在 Canokey 或者 Yubikey 这些智能卡内的 GPG 应用都有独立的 Signature keyEncryption key 以及 Authentication key 区,我们的主密钥 [C] 拥有最高权限,[S]、[E]、[A] 都是被主密钥 [C] 认证过的“子密钥”,只将子密钥写入 Canokey 中,主密钥和撤销证书使用其他不联网的介质保存,这个介质可以是物理上绝对安全的多个 U盘、CD……

生成主密钥

在 Terminal 里输入命令生成我们的主密钥

$ gpg --expert --full-gen-key

可以看到类似的输出让我们选择,这里我们输入 11 选择生成自定义用途的 ECC 密钥

gpg (GnuPG) 2.3.4; Copyright (C) 2021 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA
   (2) DSA and Elgamal
   (3) DSA (sign only)   # DSA 算法(仅用于签名)
   (4) RSA (sign only)   # RSA 算法(仅用于签名)
   (7) DSA (set your own capabilities)  # DSA 算法(自定义用途)
   (8) RSA (set your own capabilities)  # RSA 算法(自定义用途)
   (9) ECC (sign and encrypt) *default* # ECC 算法(用于签名和加密)*默认
  (10) ECC (sign only)                  # ECC 算法(仅用于签名)
  (11) ECC (set your own capabilities)  # ECC 算法(自定义用途)
  (13) Existing key           # 现有的密钥
  (14) Existing key from card # 智能卡中现有的密钥
Your selection? 11

接下来会选择密钥的功能,对于主密钥,[C] 是必须的,笔者 这里并没有取消主密钥的签名功能,你可以输入 S 取消签名功能

# ECDSA/EdDSA 密钥的可实现的功能: 签名(Sign) 认证(Certify) 身份验证(Authenticate) 
Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Sign Certify # 目前启用的功能: 签名(Sign) 认证(Certify

   (S) Toggle the sign capability          # 签名功能开关
   (A) Toggle the authenticate capability  # 身份验证功能开关 
   (Q) Finished                            # 已完成 

Your selection? Q

ECC 使用的“椭圆曲线”加密算法,这里及后面都推荐使用 Curve 25519

Please select which elliptic curve you want: # 请选择您想要使用的椭圆曲线:
   (1) Curve 25519 *default*      # 默认
   (2) Curve 448
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
Your selection? 1                 # 推荐 Curve 25519

接下里就是输入密钥的有效期以及用户标识:姓名、电子邮件地址、注释等信息了😂

Please specify how long the key should be valid. # 请设定这个密钥的有效期限
         0 = key does not expire                 # 密钥永不过期
      <n>  = key expires in n days               # 密钥在 n 天后过期
      <n>w = key expires in n weeks              # 密钥在 n 周后过期
      <n>m = key expires in n months             # 密钥在 n 月后过期
      <n>y = key expires in n years              # 密钥在 n 年后过期
Key is valid for? (0) 0                          # 笔者比较懒,主密钥设置的永不过期
Key does not expire at all                       # 密钥永远不会过期
Is this correct? (y/N) y                         # 这些内容正确吗? (y/N) y

# GnuPG 需要构建用户标识以辨认您的密钥
GnuPG needs to construct a user ID to identify your key. 

Real name: Xxx                # 真实姓名: Xxx
Email address: xxx@xxx.com    # 电子邮件地址: xxx@xxx.com
Comment:                      # 注释:
You selected this USER-ID:    # 您选定了此用户标识:
    "Xxx <xxx@xxx.com>"

# 更改姓名(N)、注释(C)、电子邮件地址(E)或确定(O)/退出(Q)? O
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O

# 我们需要生成大量的随机字节。在质数生成期间做些其他操作(敲打键盘、移动鼠标、读写硬盘之类的)将会是一个不错的主意;这会让随机数发生器有更好的机会获得足够的熵
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.

# C://Scoop//apps//gpg//current//home/trustdb.gpg:建立了信任度数据库
gpg: C://Scoop//apps//gpg//current//home/trustdb.gpg: trustdb created

# 密钥 MASTERKEYID12345 被标记为绝对信任
gpg: key MASTERKEYID12345 marked as ultimately trusted
gpg: directory 'C://Scoop//apps//gpg//current//home/openpgp-revocs.d' created

# 吊销证书已被存储为 'C://Scoop//apps//gpg//current//home/openpgp-revocs.d/DB32C2542C0939ADEDEAF078MASTERKEYID12345.rev'
gpg: revocation certificate stored as 'C://Scoop//apps//gpg//current//home/openpgp-revocs.d/DB32C2542C0939ADEDEAF078MASTERKEYID12345.rev'
public and secret key created and signed.

Note that this key cannot be used for encryption.  You may want to use
the command "--edit-key" to generate a subkey for this purpose.
pub   ed25519 2022-01-08 [SC]
      DB32C2542C0939ADEDEAF078MASTERKEYID12345 # 密钥指纹 fingerprint
uid                      Xxx <xxx@xxx.com>

# 生成过程中会要求输入一个密码(passphrase)来保护主密钥的私钥,这个密码不要太简单
┌─────────────────────────────────────────────────────┐
│ Please enter the passphrase to                      │
│ protect your newkey                                 │
│                                                     │
│ Passphrase:_______________________________________  │
│                                                     │
│      <OK>                             <Cancel>      │
└─────────────────────────────────────────────────────┘

默认生成的“吊销证书”也请妥善备份(如果有特殊需求也可以在下一步生成吊销证书)

生成吊销证书

$ gpg --gen-revoke -ao master-revoke.rv [用户ID] # 此过程会要求输入前面设置的主密钥私钥的密码(passphrase)
# 比如上面示例的 Xxx <xxx@xxx.com>
$ gpg --gen-revoke -ao master-revoke.rv xxx@xxx.com
# 输出类似下面
sec  ed25519/MASTERKEYID12345 2022-01-08 Xxx <xxx@xxx.com>

要为这个密钥创建一个吊销证书吗?(y/N) y
请选择吊销的原因:
  0 = 未指定原因
  1 = 密钥已泄漏
  2 = 密钥被替换
  3 = 密钥不再使用
  Q = 取消
(也许您会想要在这里选择 1)
您的决定是什么? 3
请输入描述(可选);以空白行结束:
> 
吊销原因:密钥不再使用
(未给定描述)
这样可以吗? (y/N) y
已创建吊销证书。

请把这个文件转移到一个您可以藏起来的介质上;如果坏人获取到了这
份证书的话,那么他就能使用它并让您的密钥无法继续使用。把此证书
打印出来再存放到安全的地方也是很好的方法,以免您的保存媒体变得
不可读。但是千万小心:您机器上的打印系统可能会在打印过程中储存
这些数据,并使得其他人看到!

设置主 UID

# 用法:gpg --quick-set-primary-uid [keyid/fingerprint] [uid],比如:
$ gpg --quick-set-primary-uid MASTERKEYID12345 'Xxx <xxx@xxx.com>'

生成子密钥

这里生成子密钥(Sub Key)也在 GPG 中完成,因为在 Canokey 中生成的密钥无法备份,而且由于性能限制,支持生成密钥的算法类型有限,下面分别生成 Signature, Encryption, Authentication 三种子密钥

# 使用专家模式编辑刚才生成的主密钥
$ gpg --expert --edit-key [keyid/fingerprint]
# 比如上面演示中的密钥主密钥 MASTERKEYID12345
$ gpg --expert --edit-key MASTERKEYID12345
gpg (GnuPG) 2.3.4; Copyright (C) 2021 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

sec  ed25519/MASTERKEYID12345
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
[ultimate] (1). Xxx <xxx@xxx.com>
生成 Signature key
# 开始生成 Signature key 用于签名的子密钥
gpg> addkey
Please select what kind of key you want:
   (1) RSA and RSA
   (2) DSA and Elgamal
   (3) DSA (sign only)   # DSA 算法(仅用于签名)
   (4) RSA (sign only)   # RSA 算法(仅用于签名)
   (7) DSA (set your own capabilities)  # DSA 算法(自定义用途)
   (8) RSA (set your own capabilities)  # RSA 算法(自定义用途)
   (9) ECC (sign and encrypt) *default* # ECC 算法(用于签名和加密)*默认
  (10) ECC (sign only)                  # ECC 算法(仅用于签名)
  (11) ECC (set your own capabilities)  # ECC 算法(自定义用途)
  (13) Existing key           # 现有的密钥
  (14) Existing key from card # 智能卡中现有的密钥
Your selection? 10            # 选择 ECC 仅用于签名
Please select which elliptic curve you want: # 请选择您想要使用的椭圆曲线:
   (1) Curve 25519 *default*  # 默认
   (2) Curve 448
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
Your selection? 1             # 推荐 Curve 25519
Please specify how long the key should be valid. # 请设定这个密钥的有效期限
         0 = key does not expire                 # 密钥永不过期
      <n>  = key expires in n days               # 密钥在 n 天后过期
      <n>w = key expires in n weeks              # 密钥在 n 周后过期
      <n>m = key expires in n months             # 密钥在 n 月后过期
      <n>y = key expires in n years              # 密钥在 n 年后过期
Key is valid for? (0) 0                          # 笔者设置的是10年,建议更短一点
Key does not expire at all                       # 密钥永远不会过期
Is this correct? (y/N) y                         # 这些内容正确吗? (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  ed25519/MASTERKEYID12345
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  ed25519/SIGNATUREKEYID67
     created: 2022-01-08  expires: 2032-01-06  usage: S   
[ ultimate ] (1). Xxx <xxx@xxx.com>
gpg> save       # 生成结束一定要记得输入 save 保存!
生成 Encryption key

类似上面生成 Signature key 子密钥的过程,Encryption key 只给 [E] 功能

sec  ed25519/MASTERKEYID12345
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  ed25519/SIGNATUREKEYID67
     created: 2022-01-08  expires: 2032-01-06  usage: S  
ssb  cv25519/ENCRYPTIONKEYID8
     created: 2022-01-08  expires: 2032-01-06  usage: E 
[ ultimate ] (1). Xxx <xxx@xxx.com>
生成 Authentication key

类似上面生成 Signature key 子密钥的过程,Authentication key 只给 [A] 功能

sec  ed25519/MASTERKEYID12345          # 主密钥 [SC]
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  ed25519/SIGNATUREKEYID67          # 子密钥 Signature key
     created: 2022-01-08  expires: 2032-01-06  usage: S  
ssb  cv25519/ENCRYPTIONKEYID8          # 子密钥 Encryption key
     created: 2022-01-08  expires: 2032-01-06  usage: E 
ssb  ed25519/AUTHENTICATION90          # 子密钥 Authentication key
     created: 2022-01-08  expires: 2032-01-06  usage: A 
[ ultimate ] (1). Xxx <xxx@xxx.com>

列出所有的 Key

# 列出所有的 key 信息
$ gpg --fingerprint -K --keyid-format LONG
C:\Scoop\apps\gpg\current\home\pubring.kbx
------------------------------------------
sec  ed25519/MASTERKEYID12345 2022-01-08 [SC]
     Key fingerprint = DB32 C254 2C09 39AD EDEA F078 MAST ERKE YID1 2345
uid                 [ ultimate] Xxx <xxx@xxx.com>
ssb  ed25519/SIGNATUREKEYID67 2022-01-08 [S] [expires: 2032-01-06]
ssb  cv25519/ENCRYPTIONKEYID8 2022-01-08 [E] [expires: 2032-01-06]
ssb  ed25519/AUTHENTICATION90 2022-01-08 [A] [expires: 2032-01-06]

这时候生成 Key 的过程就结束啦~

备份 Key 的各种信息

在将三种子密钥写入我们的 Canokey 前,建议备份一下一些密钥信息,子密钥在写入 Canokey 后在本地计算机存储的信息就会被删除🤣

# 导出主密钥公钥 master-pub-key.pgp 
$ gpg -ao master-pub-key.pgp --export [主密钥ID]
# 导出主密钥公钥
$ gpg -ao sign-pub-key.pgp --export [签名子密钥ID]
$ gpg -ao encrypt-pub-key.pgp --export [加密子密钥ID]
$ gpg -ao authenticate-pub-key.pgp --export [认证子密钥ID]

# 导出私钥信息
$ gpg -ao master-secret-key.pgp --export-secret-key [主密钥ID]  # 主密钥私钥信息,需要绝对安全的保存
$ gpg -ao sign-secret-key.pgp --export-secret-key [签名子密钥ID]
$ gpg -ao encrypt-secret-key.pgp --export-secret-key [加密子密钥ID]
$ gpg -ao authenticate-secret-key.pgp --export-secret-key [认证子密钥ID]

同时备份之前生成的吊销证书文件,这些信息建议使用多种不联网介质妥善保存😪

将子密钥写入 Canokey

Canokey 的默认 PIN123456

默认 Admin PIN12345678

现在将 Canokey 插入计算机的 USB 口中,打开 Terminal

$ gpg --card-status # 查看智能卡状态
Reader ...........: canokeys.org OpenPGP PIV OATH 0
Application ID ...: C123001234123456B1C001111B123400
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: CanoKeys
Serial number ....: 01232A56
Name of cardholder: [not set]
Language prefs ...: [not set]
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 64 64 64
PIN retry counter : 3 0 3
Signature counter : 0
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

开始写入 Signature, Encryption, Authentication 三种子密钥

$ gpg --expert --edit-key MASTERKEYID12345
gpg (GnuPG) 2.3.4; Copyright (C) 2021 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

sec  ed25519/MASTERKEYID12345          # 主密钥 [SC]
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  ed25519/SIGNATUREKEYID67          # 子密钥 Signature key
     created: 2022-01-08  expires: 2032-01-06  usage: S  
ssb  cv25519/ENCRYPTIONKEYID8          # 子密钥 Encryption key
     created: 2022-01-08  expires: 2032-01-06  usage: E 
ssb  ed25519/AUTHENTICATION90          # 子密钥 Authentication key
     created: 2022-01-08  expires: 2032-01-06  usage: A 
[ ultimate ] (1). Xxx <xxx@xxx.com>

gpg> key 1  # 选择第一个子密钥[S]

sec  ed25519/MASTERKEYID12345
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb* ed25519/SIGNATUREKEYID67  # ssb 后面有个*表示这个 key 被选中了
     created: 2022-01-08  expires: 2032-01-06  usage: S  
ssb  cv25519/ENCRYPTIONKEYID8
     created: 2022-01-08  expires: 2032-01-06  usage: E 
ssb  ed25519/AUTHENTICATION90
     created: 2022-01-08  expires: 2032-01-06  usage: A 
[ ultimate ] (1). Xxx <xxx@xxx.com>

gpg> keytocard             # 将密钥写入 Canokey
Please select where to store the key: # 请选择在哪里存储密钥
   (1) Signature key       # 签名密钥
   (3) Authentication key  # 身份验证密钥
Your selection? 1          # 将[S]密钥写入 Canokey 的 Signature key 区域

# 会要求输入解锁主密钥私钥保护的密码(passphrase)以及 Canokey 的 Admin PIN
# 写完 Signature key 后继续写入其他子密钥

gpg> key 1 #  取消选择第一个子密钥

sec  ed25519/MASTERKEYID12345
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  ed25519/SIGNATUREKEYID67
     created: 2022-01-08  expires: 2032-01-06  usage: S  
ssb  cv25519/ENCRYPTIONKEYID8
     created: 2022-01-08  expires: 2032-01-06  usage: E 
ssb  ed25519/AUTHENTICATION90
     created: 2022-01-08  expires: 2032-01-06  usage: A 
[ ultimate ] (1). Xxx <xxx@xxx.com>

gpg> key 2 #  选择第二个子密钥[E]

sec  ed25519/MASTERKEYID12345
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  ed25519/SIGNATUREKEYID67
     created: 2022-01-08  expires: 2032-01-06  usage: S  
ssb* cv25519/ENCRYPTIONKEYID8  # ssb 后面有个*表示这个 key 被选中了
     created: 2022-01-08  expires: 2032-01-06  usage: E 
ssb  ed25519/AUTHENTICATION90
     created: 2022-01-08  expires: 2032-01-06  usage: A 
[ ultimate ] (1). Xxx <xxx@xxx.com>

gpg> keytocard             # 将密钥写入 Canokey
Please select where to store the key: # 请选择在哪里存储密钥
   (2) Encryption key      # 加密密钥
Your selection? 2          # 将[E]密钥写入 Canokey 的 Encryption key 区域

gpg> key 2 #  取消选择第二个子密钥

sec  ed25519/MASTERKEYID12345
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  ed25519/SIGNATUREKEYID67
     created: 2022-01-08  expires: 2032-01-06  usage: S  
ssb  cv25519/ENCRYPTIONKEYID8
     created: 2022-01-08  expires: 2032-01-06  usage: E 
ssb  ed25519/AUTHENTICATION90
     created: 2022-01-08  expires: 2032-01-06  usage: A 
[ ultimate ] (1). Xxx <xxx@xxx.com>

gpg> key 3 #  选择第三个子密钥[A]

sec  ed25519/MASTERKEYID12345
     created: 2022-01-08  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  ed25519/SIGNATUREKEYID67
     created: 2022-01-08  expires: 2032-01-06  usage: S  
ssb  cv25519/ENCRYPTIONKEYID8
     created: 2022-01-08  expires: 2032-01-06  usage: E 
ssb* ed25519/AUTHENTICATION90   # ssb 后面有个*表示这个 key 被选中了
     created: 2022-01-08  expires: 2032-01-06  usage: A 
[ ultimate ] (1). Xxx <xxx@xxx.com>

gpg> keytocard             # 将密钥写入 Canokey
Please select where to store the key: # 请选择在哪里存储密钥
   (3) Authentication key      # 身份认证密钥
Your selection? 2          # 将[E]密钥写入 Canokey 的 Authentication key 区域

# 最后保存,写入完成
gpg> save # 千万不要忘记这步!!!

写入完成后,再次使用命令验证下写入是否正确

$ gpg --card-status # 查看智能卡状态
Reader ...........: canokeys.org OpenPGP PIV OATH 0
Application ID ...: C123001234123456B1C001111B123400
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: CanoKeys
Serial number ....: 01232A56
Name of cardholder: [not set]
Language prefs ...: [not set]
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 64 64 64
PIN retry counter : 3 0 3
Signature counter : 0
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: AB11 7854 7543 3456 8SF6  A43F SIGN ATUR EKEY ID67
      created ....: 2022-01-08 02:12:00
Encryption key....: 1298 ABCD 7527 3849 8743  6A94 ENCR YPTI ONKE YID8
      created ....: 2022-01-08 02:13:31
Authentication key: 5839 7365 9217 8482 0987  AA06 AUTH ENTI CATI ON90
      created ....: 2022-01-08 02:14:08
General key info..: sub  ed25519/AB85E2D677CA9A84 2022-01-08 Xxx <xxx@xxx.com>
sec   ed25519/MASTERKEYID12345  created: 2022-01-08  expires: never
ssb>  ed25519/SIGNATUREKEYID67  created: 2022-01-08  expires: 2032-01-06 # ssb后面的>表示密钥已经转移到卡内
                                card-no: F1D0 01234A34
ssb>  cv25519/ENCRYPTIONKEYID8  created: 2022-01-08  expires: 2032-01-06 # ssb后面的>表示密钥已经转移到卡内
                                card-no: F1D0 01234A34
ssb>  ed25519/AUTHENTICATION90  created: 2022-01-08  expires: 2032-01-06 # ssb后面的>表示密钥已经转移到卡内
                                card-no: F1D0 01234A34

现在所有密钥就已经正确的写入到 Canokey 里面了😋

Canokey 为 GitHub 提交签名

Signature key 对应的 GPG 公钥添加到 GitHub GPG keys 里,签名的时候触摸一下 Canokey 或者输入 PIN 即可(根据设置的不同策略🤔)

# 设置 gpg 程序的路径
$ git config --global gpg.program "C:/Scoop/apps/gnupg/current/bin/gpg.exe"
# 类似于“指针”将 key 指向 Canokey 上
$ gpg --card-status
# 指定用于 git 提交时签名的 GPG key
$ git config --global user.signingkey SIGNATUREKEYID67 # Signature keyid
# 以后提交的时候加上 -S 参数进行 GPG 签名,比如
$ git commit -S -m "first commit" 
# 强制当前仓库使用 GPG key 签名
$ git config commit.gpgsign true
# 强制全局使用 GPG key 签名
$ git config --global commit.gpgsign true
# 强制 GPG key 签名后提交时就不需要加 -S 参数了,比如
$ git commit -m "second commit"
# 查看 git 提交时的签名信息
$ git log --show-signature

Canokey 用来 SSH 登录

前面生成的 OpenPGP 的 Authentication key 是不能直接用在 SSH 认证登录上的,如果我们想用 Canokey 的 Authentication key 登录 GitHub 或者服务器,还需要一点操作,因为 Linux 和 macOS 下几乎是开箱即用的状态,笔者 这里只说下 Windows 下的两种比较简单的方案,两种方式可以根据自己实际需求选择一种使用即可

使用 win-gpg-agent 处理[推荐]

win-gpg-agent 是一组开源的简单工具,使用 Golang 编写,它可以让我们在 Windows 10/11 上能更加轻松地使用 GPG 和 SSH

# 安装 win-gpg-agent
$ scoop bucket add extras
$ scoop install sudo win-gpg-agent
# 停止 Windows 自带的 SSH 服务
$ sudo Stop-Service ssh-agent
$ sudo Set-Service -StartupType Disabled ssh-agent

对应你的 Scoop 安装软件的 实际路径,修改配置文件 C:\Scoop\apps\win-gpg-agent\1.6.0\agent-gui.conf

# 比如笔者的配置文件修改如下
gpg:
  install_path: "${SystemDrive}\\Scoop\\apps\\gnupg\\current"
  # Before gnupg 2.3.2 release home directory could be properly link-ed
  #   homedir: "${USERPROFILE}\\Scoop\\apps\\gnupg\\current\\home"
  # With gnupg 2.3.3 release (due to the changes in T5537) use this instead
  #   homedir: "${USERPROFILE}\\Scoop\\persist\\gnupg\\home"
  #   socketdir: ""
  # After gnupg 2.3.4 release we have to switch to
  homedir: "${SystemDrive}\\Scoop\\apps\\gnupg\\current\\home"
  socketdir: "${SystemDrive}\\Scoop\\apps\\gnupg\\current\\gnupg"
gui:
  debug: false
  setenv: true
  openssh: native
  # Use line below to enable SSH_AUTH_SOCK to point to cygwin socket
  #   openssh: cygwin

其中的 ${SystemDrive} 代表系统盘盘符(比如:C:),${USERPROFILE} 代表用户目录(比如:C:\Users\username

将你的 Canokey(或者其他支持 OpenPGP 应用的智能卡设备)插入计算机,现在从开始菜单运行 win-gpg-agent 程序,打开终端

# 刷新智能卡状态
$ gpg --card-status
# 查看用于 SSH 的子密钥的公钥信息
$ ssh-add -L # 下面是输出
ssh-ed25519 AAAAD4G5xKKpgMDvVktSNTpQhASYNS591AKW9jOaNk2eJzuWQCCAAIE5aTx1lZDII/GA cardno:F1D0 01232A56

将输出的 ssh-ed25519 这行信息复制保存到 GitHub 的 SSH keys (或者其他需要登录的服务器)的信任公钥里即可,可以测试一下自己的私有仓库,出现 Pinentry (go) 弹窗输入 PIN 解锁智能卡即可

win-gpg-agent

为了不必每次都要手动开启 win-gpg-agent 程序,我们还可以设置下开机自启 win-gpg-agent

按住组合键 Win + R 输入命令 shell:startup 并运行会打开启动目录,在这里新建一个快捷方式指向 win-gpg-agent 程序即可

图示已经添加过启动选项

PS:如果你想要通过 WSL、Cygwin、MSYS2、win32-openssh 等工具来使用 Canokey 等智能卡的 OpenPGP 应用的 SSH 功能,可参考 win-gpg-agent 文档

使用 PuTTY 处理[备选]

在 Windows 下使用 PuTTY 来处理 SSH Agent 也是个不错的方案,但是可能比较复杂一点

# 类似于“指针”将 key 指向 Canokey 上
$ gpg --card-status
# 终端进入 GPG 的工作目录,Scoop 安装的 GPG 默认工作目录为:C:\Scoop\apps\gnupg\current\home
$ cd C:\Scoop\apps\gnupg\current\home
# 编辑 GPG 配置文件(没安装 vim 就手动新建/编辑)
$ vim gpg-agent.conf
# 加入以下内容并保存
enable-putty-support
enable-ssh-support
default-cache-ttl 600
max-cache-ttl 7200

# 更改 git 设置,使用 PuTTY pageant 处理 SSH 
$ git config --global core.sshcommand 'plink -agent'
# 查看 GPG 密钥的 Keygrip
$ gpg -K --with-keygrip
# 复制 Authentication key 的 Keygrip 数值(一串数字)
$ vim sshcontrol 
# 将 Authentication key 的 Keygrip 数值写入 sshcontrol 文件里保存
# 关闭 gpg-agent
$ gpg-connect-agent killagent /bye
# 开启 gpg-agent
$ gpg-connect-agent /bye
# 现在列出公钥
$ gpg --list-public-keys
C:\Scoop\apps\gnupg\current\home\pubring.kbx
------------------------------------------
pub   ed25519 2022-01-08 [SC]
      23ABCD638264ADC9483FE09A53857534534CA5B6
uid           [ultimate] Xxx <xxx@xxx.com>
sub   ed25519 2022-01-08 [S] [expires: 2032-01-06]
sub   cv25519 2022-01-08 [E] [expires: 2032-01-06]
sub   ed25519 2022-01-08 [A] [expires: 2032-01-06]

# 导出公钥为 SSH 的公钥格式
$ gpg --export-ssh-key
ssh-ed25519 AAAAD4G5xKKpgMDvVktSNTpQhASYNS591AKW9jOaNk2eJzuWQCCAAIE5aTx1lZDII/GA openpgp:0x3CF3456E

ssh-ed25519 开头的这行内容完整地添加到 GitHub 的 SSH keys 里(其他服务器如 Linux SSH 远程访问同理),SSH 登陆的时候触摸一下 Canokey 或者输入 PIN 即可(根据设置的不同策略🤔)

以 GitHub 为例,假设你本地已经配置好 Git 的参数(用户名、邮箱,邮箱必须跟 GitHub 保持一致),可以在终端里先手动尝试下使用 PuTTY pageant :$ plink.exe -agent -v git@github.com

PuTTY pageant

如果是自己常用的计算机点击 Accept 接受即可

以后使用 Canokey/Yubikey(Yubikey 应该同样适用🤔)通过 SSH 登录 GitHub 或者其他服务器时,当服务器要求验证公钥时,PuTTY 会将请求转发给 GPG,GPG 会提示您输入 PIN 并使用 Canokey/YubiKey 授权登录

Enter PIN

更换电脑/重装系统后恢复

笔者 上述生成密钥的过程是在完全离线的 Windows 计算机上完成的,并且生成密钥、备份密钥信息、写入 Canokey 后就格式化重装了系统🤣,所以在新的系统上,还需要:

# 安装好所需软件后,将之前备份的公钥导入到新系统里
$ gpg --import master-pub-key.pgp
# 信任主密钥
$ gpg --expert --edit-key [主密钥id/fingerprint]
gpg> trust  # 设置信任主密钥

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5  # 完全信任
Do you really want to set this key to ultimate trust? (y/N) y    # 确认

gpg> save         # 最后记得保存
gpg> quit         # 退出

# 完成后记得重启下 GPG Agent
$ gpg-connect-agent killagent /bye
$ gpg-connect-agent /bye

现在可以插入你之前写入密钥的 Canokey/Yubikey 了

# 查看智能卡状态
$ gpg --card-status
# 编辑卡片设置
$ gpg --card-edit
gpg/card> fetch  # 将子密钥指向 Canokey

gpg/card> quit   # 退出 

再次使用命令 $ gpg --card-status

$ gpg --card-status # 查看智能卡状态
Reader ...........: canokeys.org OpenPGP PIV OATH 0
Application ID ...: C123001234123456B1C001111B123400
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: CanoKeys
Serial number ....: 01232A56
Name of cardholder: [not set]
Language prefs ...: [not set]
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 64 64 64
PIN retry counter : 3 0 3
Signature counter : 0
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: AB11 7854 7543 3456 8SF6  A43F SIGN ATUR EKEY ID67
      created ....: 2022-01-08 02:12:00
Encryption key....: 1298 ABCD 7527 3849 8743  6A94 ENCR YPTI ONKE YID8
      created ....: 2022-01-08 02:13:31
Authentication key: 5839 7365 9217 8482 0987  AA06 AUTH ENTI CATI ON90
      created ....: 2022-01-08 02:14:08
General key info..: sub  ed25519/AB85E2D677CA9A84 2022-01-08 Xxx <xxx@xxx.com>
sec#  ed25519/MASTERKEYID12345  created: 2022-01-08  expires: never  # sec后面的#表示主密钥的私钥不在此计算机上,这是安全的
ssb>  ed25519/SIGNATUREKEYID67  created: 2022-01-08  expires: 2032-01-06 # ssb后面的>表示密钥已经转移到卡内
                                card-no: F1D0 01234A34
ssb>  cv25519/ENCRYPTIONKEYID8  created: 2022-01-08  expires: 2032-01-06 # ssb后面的>表示密钥已经转移到卡内
                                card-no: F1D0 01234A34
ssb>  ed25519/AUTHENTICATION90  created: 2022-01-08  expires: 2032-01-06 # ssb后面的>表示密钥已经转移到卡内
                                card-no: F1D0 01234A34

设置 GPG 命令别名

在 Windows 下的 PowerShell 里可以使用别名来简化 GPG 相关的命令,将以下内容加到 PowerShell 配置文件里

# GPG for Canokeys: https://www.dejavu.moe/posts/canokey-openpgp/
# 终止 gpg-agent 命令别名: killgpg
function killgpg{gpg-connect-agent killagent /bye}
# 启动 gpg-agent 命令别名: startgpg
function startgpg{gpg-connect-agent /bye}
# 查看 gpg 智能卡状态 命令别名: card
function card{gpg --card-status}

Enjoy!😋 (文中信息都已脱敏处理,本文篇幅比较长,由于之前自己生成的过程中记录的比较乱,难免存在少部分可能不准确或者错误的地方,欢迎指正👻)

参考资料: