跳到主要内容
  1. 所有文章/

Canokey 签名和认证应用指南

·6717 字·约 14 分钟

PGP、OpenPGP 和 GPG #

先简单了解一下 PGP、OpenPGP 以及 GPG 的区别

PGP #

PGP (Pretty Good Privacy) 是一套用于信息加密、验证的应用程序,主要开发者是 小菲利普·R·齐默尔曼,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,在 Windows 下我们推荐使用 Scoop 仅安装 GPG 程序,如果你使用 Windows 且不知道 Scoop 是什么,可阅读 使用 Scoop 管理 Windows 下的软件和开发环境

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

查看您的计算机上安装的 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

生成 PGP 密钥 #

生成密钥对 #

PGP 密钥的功能有:

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

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

生成主密钥 #

先输入下面命令生成主密钥

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 密钥使用的「椭圆曲线」加密算法

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>'

生成子密钥 #

Canokey 由于芯片性能限制,支持在密钥上直接生成的算法类型有所限制,并且在硬件密钥上直接生成也不方便备份,因此建议先在当前计算机上生成、备份后再转移到 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 之前,建议备份一下一些原始密钥信息到安全地存储介质中

# 导出主密钥公钥 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 的 OpenPGP 应用和 Yubikey 一样,初始 PIN 为:

  • 默认 PIN123456
  • 默认 Admin PIN12345678

将 Canokey 插入计算机,开始写入密钥

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 三种子密钥到对应的 OpenPGP 应用卡槽内

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 插入计算机,运行 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
Pinentry (go)

设置下 win-gpg-agent 开机自动运行,组合键 Win + R 打开 运行 输入命令 shell:startup 进入启动项目录,新建一个快捷方式指向 win-gpg-agent

开机自动运行
开机自动运行

想要通过 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 的参数,可以在终端里先手动尝试下使用 PuTTY pageant :plink.exe -agent -v git@github.com

PuTTY pageant
PuTTY pageant

如果是自己常用的计算机点击 Accept 接受,以后使用时,PuTTY 会将请求转发给 GPG,GPG 会提示您输入 PIN 并使用 Canokey/YubiKey 登录

Enter PIN
Enter PIN

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

在新的系统上,只需要:

# 安装好所需软件后,导入公钥到计算机
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 # 查看智能卡状态
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://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}

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

参考资料:

Dejavu Moe
作者
Dejavu Moe
Not for success, just for growing.