如何用 YubiKey 或 CanoKey 在 Windows 平台生成 GPG 并签名 Github Commit

最近分别从亚马逊和国内某宝购买了 YubiKey 和 CanoKey,于是准备尝试下期待已久的 PGP 加密功能,并将操作过程中踩过的坑记录下来。

yubi-cano

环境准备

首先,最为关键的是准备一台可信赖的 Windows 电脑,绝对不要在公共电脑上生成 PGP。然后安装以下必要软件:

  • Git Bash:用于代替 Windows Powershell 执行 Shell 命令,下载地址:Git - Download for Windows

  • Gpg4win:用于在 Windows 平台上执行 gpg 命令,下载地址:Gpg4win - Download

    • Gpg4win 是 GunPG 在 Windows 平台上的实现。
    • 而 GnuPG (GPG) 又是 PGP 的一个开源实现 (OpenPGP)。

    不同的平台表述可能不同,我们可以粗略的认为 PGP、GPG 与 OpenPGP 指的是同一个东西。

准备工作完成后,即可进入正题。

在电脑上生成密钥对

首先让电脑保持脱机状态(断网),然后创建临时文件夹(本文以 /d/data 表示)并开启火绒的文件夹保护功能(防护中心 -> 高级防护 -> 自定义防护 -> 添加规则),将 /d/data 添加到保护对象,有任何程序访问 /d/data 时触发询问。最后从【文件管理器】进入 /d/data,右键打开 Git Bash,键入命令生成 GPG:

  1. 通过 gpg --homedir ./ --expert --full-gen-key 命令生成 GPG 主秘钥:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    
    $ gpg --homedir ./ --expert --full-gen-key
    gpg (GnuPG) 2.2.41-unknown; Copyright (C) 2022 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.
    
    gpg: keybox '/d/data/pubring.kbx' created
    Please select what kind of key you want:
    (1) RSA and RSA (default)
    (2) DSA and Elgamal
    (3) DSA (sign only)
    (4) RSA (sign only)
    (7) DSA (set your own capabilities)
    (8) RSA (set your own capabilities)
    (9) ECC and ECC
    (10) ECC (sign only)
    (11) ECC (set your own capabilities)
    (13) Existing key
    (14) Existing key from card
    Your selection? 11    # [1] 选择自定义 ECC,用于生成主秘钥
    
    Possible actions for a ECDSA/EdDSA key: Sign Certify Authenticate
    Current allowed actions: Sign Certify
    
    (S) Toggle the sign capability
    (A) Toggle the authenticate capability
    (Q) Finished
    
    Your selection? a     # [2] 选择身份验证功能
    
    Possible actions for a ECDSA/EdDSA key: Sign Certify Authenticate
    Current allowed actions: Sign Certify Authenticate
    
    (S) Toggle the sign capability
    (A) Toggle the authenticate capability
    (Q) Finished
    
    Your selection? q     # [3] 退出选择
    Please select which elliptic curve you want:
    (1) Curve 25519
    (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     # [4] Curve 25519 安全性最高
    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) 0 # 永不过期
    Key does not expire at all
    Is this correct? (y/N) y
    
    GnuPG needs to construct a user ID to identify your key.
    
    Real name: yourname   # [5] 键入用户的名称、邮箱等信息
    Email address: yourmail@xxx.com
    Comment: GPG Key
    You selected this USER-ID:
        "yourname (GPG Key) <yourmail@xxx.com>"
    
    Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o  # [6] 确定
    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: /d/data/trustdb.gpg: trustdb created
    gpg: directory '/d/data/openpgp-revocs.d' created
    gpg: revocation certificate stored as '/d/data/openpgp-revocs.d/1CDAFE3696DB37B9525F52F7A5B7DDAE5D2EA505.rev'
    public and secret key created and signed.
    
    pub   ed25519 2023-12-23 [SCA]
        1CDAFE3696DB37B9525F52F7A5B7DDAE5D2EA505
    uid                      yourname (GPG Key) <yourmail@xxx.com>
    1. 键入 11,选择自定义 ECC;

    2. 键入 a,选择身份验证功能;

    3. 键入 q,退出选择;

    4. 选择安全性最高的算法 Curve 25519;

    5. 键入用户的名称、邮箱等信息;

    6. 键入 o 生成 GPG 密钥,此时会弹出窗口提示用户输入认证密码,这里我们直接点击确认,不使用密码:

      pinentry

    最终,记录上述命令生成的 Key ID 1CDAFE3696DB37B9525F52F7A5B7DDAE5D2EA505

    如果火绒弹出 gpg 或 git 的权限确认窗口,点击允许,其它程序尝试读取一定要拒绝!

    huorong
  2. 通过 gpg --homedir ./ --expert --edit-key {Key ID} 命令生成用于签名的子密钥:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    
    $ gpg --homedir ./ --expert --edit-key 1CDAFE3696DB37B9525F52F7A5B7DDAE5D2EA505
    gpg (GnuPG) 2.2.41-unknown; Copyright (C) 2022 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.
    
    gpg: checking the trustdb
    gpg: marginals needed: 3  completes needed: 1  trust model: pgp
    gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
    sec  ed25519/A5B7DDAE5D2EA505
        created: 2023-12-23  expires: never       usage: SCA
        trust: ultimate      validity: ultimate
    [ultimate] (1). yourname (GPG Key) <yourmail@xxx.com>
    
    gpg> addkey    # [1] 生成子密钥
    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? 10   # [2] 子密钥类型为签名密钥
    Please select which elliptic curve you want:
    (1) Curve 25519
    (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    # [3] 选择 Curve 25519 算法
    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) 0
    Key does not expire at all
    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  ed25519/A5B7DDAE5D2EA505
        created: 2023-12-23  expires: never       usage: SCA
        trust: ultimate      validity: ultimate
    ssb  ed25519/FDCDCB97899ED8C9
        created: 2023-12-23  expires: never       usage: S
    [ultimate] (1). yourname (GPG Key) <yourmail@xxx.com>
    
    gpg> save   # [4] 保存
    1. 键入 addkey,开始生成子密钥;
    2. 键入 10 选择生成签名子密钥 (sign only);
    3. 键入 1 选择 Curve 25519 算法;
    4. 保存子密钥,弹出窗口后仍旧不输入密码。

    最终生成的 FDCDCB97899ED8C9 即是子密钥的 Key ID,子密钥后的 usage: S 表示该密钥用于签名认证。

将私钥导入物理秘钥

将物理秘钥插入 USB 端口,然后打开任务管理器,强制关闭可能存在的 gpg-agent.exe,执行 gpg --homedir ./ --edit-key {Key ID} 命令将私钥导入物理秘钥:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ gpg --homedir ./ --edit-key 1CDAFE3696DB37B9525F52F7A5B7DDAE5D2EA505
gpg (GnuPG) 2.2.41-unknown; Copyright (C) 2022 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/A5B7DDAE5D2EA505
     created: 2023-12-23  expires: never       usage: SCA
     trust: ultimate      validity: ultimate
ssb  ed25519/FDCDCB97899ED8C9
     created: 2023-12-23  expires: never       usage: S
[ultimate] (1). yourname (GPG Key) <yourmail@xxx.com>

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

sec  ed25519/A5B7DDAE5D2EA505
     created: 2023-12-23  expires: never       usage: SCA
     trust: ultimate      validity: ultimate
ssb* ed25519/FDCDCB97899ED8C9  # 被选择的密钥前边会有个 *
     created: 2023-12-23  expires: never       usage: S
[ultimate] (1). yourname (GPG Key) <yourmail@xxx.com>

gpg> keytocard   # [2] 写入物理秘钥
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1  # [3] 指定密钥类型,为签名密钥

sec  ed25519/A5B7DDAE5D2EA505
     created: 2023-12-23  expires: never       usage: SCA
     trust: ultimate      validity: ultimate
ssb*  ed25519/FDCDCB97899ED8C9
     created: 2023-12-23  expires: never       usage: S
[ultimate] (1). yourname (GPG Key) <yourmail@xxx.com>

gpg> save  #  [4] 持久化
  1. 键入 key 1 选择第一个密钥,被选中的密钥前边会有个 *
  2. 键入 keytocard 开始写入物理秘钥;
  3. 键入 1 指定密钥类型为签名密钥;
  4. 键入 save 完成持久化,写入时会弹窗提示输入 GPG ADMIN PIN,默认为 12345678

写入完成后,执行 gpg --card-status 查看写入结果:

1
2
3
4
5
6
7
$ gpg --card-status
...
Application type .: OpenPGP
...
Signature key ....:  1CDA FE36 9BDB 37B9 525F 12F1 52E9 BE9C 2B84 6A88
      created ....: 2023-12-23 07:56:22
...

Signature key 正确显示,说明密钥导入成功。

导出 GPG 公钥

若要通过 GPG 签名 Github Commit,需要导出 GPG 的公钥:

1
2
$ gpg --homedir ./ --armor --export {Key ID} > gpg-public-key.asc
# eg: gpg --homedir ./ --armor --export 1CDAFE3696DB37B9525F52F7A5B7DDAE5D2EA505 > gpg-public-key.asc

将公钥 gpg-public-key.asc 中的内容分复制到 Github 设置,便可通过 GPG 密钥签名 Commit 了。

严重警告!

隐私第一!!

不要将公钥上传到任何的 Key Server,如果确有上传的必要,也不要使用自己的真实姓名!!

将 GPG 公钥导入电脑

为了让本机识别上述生成的公钥,我们需要执行以下命令将公钥导入电脑:

1
2
3
4
5
$ gpg --import {path-to}/gpg-public-key.asc
gpg: /c/Users/xxxxxx/.gnupg/trustdb.gpg: trustdb created
gpg: key A5B7DDAE5D2EA505: public key "yourname (GPG Key) <yourmail@xxx.com>" imported
gpg: Total number processed: 1
gpg:               imported: 1

然后,执行以下命令信任该公钥:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ gpg --edit-key 1CDAFE3696DB37B9525F52F7A5B7DDAE5D2EA505
# 如果在另外的电脑导入公钥,我们往往会不记得完整摘要,这种情况只写最后十六个字符即可
# eg: gpg --edit-key A5B7DDAE5D2EA505

gpg> trust # 输入 trust,信任秘钥
Your decision? 5  # 输入 5,即 "I trust ultimately"
Do you really want to set this key to ultimate trust? (y/N) y # 选择 y

Please note that the shown key validity is not necessarily correct
unless you restart the program.
gpg> quit # 退出程序

接下来,重新插拔物理秘钥,执行 gpg --card-status 命令,成功显示出物理秘钥的 GPG 信息就表示导入成功了。

备份 GPG 工程

/d/data 中的内容加密压缩后保存到安全的地方:

1
2
3
S.gpg-agent=          S.gpg-agent.ssh=    pubring.kbx   trustdb.gpg
S.gpg-agent.browser=  openpgp-revocs.d/   pubring.kbx~
S.gpg-agent.extra=    private-keys-v1.d/  tofu.db

备份完成后,销毁本地的 /d/data

通过物理密钥签名 Github Commit

首先,将生成的 gpg-public-key.asc 中的内容 配置 到 Github:

github setting

再然后,执行以下命令配置 Git:

1
2
3
4
git config --global user.email yourmail@xxx.com
git config --global user.name yourname
git config --global gpg.program "C:\Program Files\Git\usr\bin\gpg.exe"
git config --global user.signingkey FDCDCB97899ED8C9! # 感叹号不能丢
  • gpg.program由于我们使用的是 Git Bash,所以这里指定 Git 下的 gpg.exe。如果要使用 PowerShell,这里可以改成 GunPG 下的工具。
  • user.signingkey 为上文生成的签名子密钥 Key ID,注意子密钥末尾的 ! 感叹号不能丢

最后,移动到待提交的工程目录下,使能当前工程的 commit.gpgsign,便可在 git commit 时触发 GPG 签名校验(可能会弹窗要求输入 GPG PIN,默认为 123456):

1
git config commit.gpgsign true

提交代码后,如果 gpg 设置的 email 与 Github 账户的 email 一样,便可显示 Verified 认证:

github

修改物理秘钥的 GPG PIN

为了安全,我们在完成以上配置后最好修改物理秘钥的默认 PIN 码。首先将秘钥插入 USB,然后打开 Git Bash 窗口,键入以下命令:

1
gpg --edit-card

显示出秘钥信息后,紧接着输入 admin

1
2
gpg/card> admin
Admin commands are allowed  # 系统提示管理员模式启动

最后输入 passwd 即可按提示修改 PIN:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
gpg/card> passwd # 1. 输入修改密码命令
gpg: OpenPGP card no. D276000124010304F1D00131341F0000 detected

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 1 # 2. 选择修改 PIN,然后操作弹窗完成 PIN 的修改(默认 PIN 为 123456)
PIN changed.      #    系统提示修改成功

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? q # 3. 选择退出
gpg/card> quit    #    退出 gpg 命令

至此,本文的主要内容就介绍完了!


参考资料:

0%