本文最后更新于 103 天前,其中的信息可能已经有所发展或是发生改变。
很久之前,我就了解到可以使用硬件安全密钥来增加安全性,早先了解到的密钥是 YubiKey,但由于价格和各种原因摸了,且 22 年 Cloudflare 的优惠我没上车,所以计划搁置了很久。
直到差不多去年年底的时候,正巧我看到了硬件密钥的相关文章,就趁此机会又了解了很多厂商的硬件密钥。最终,我花费了 ¥169 购入 Canokey,进入密钥折腾之路。
Canokey 简介
Canokey 支持以下功能:
- FIDO2 / U2F / WebAuthn
- OpenPGP
- OATH
- PIV
- HOTP/TOTP
- NDEF(NFC)
可以看得出来 Canokey 基本相当于 YubiKey 的开源实现,功能和 YubiKey 相差不大。
配置 Canokey
首次插入 Canokey 后,浏览器会弹出提示——请前往 console.canokeys.org 进行连接。此处的链接为 Canokey 官方提供的管理工具,可以修改 Canokey 的 PIN 和配置相关功能。
初始 PIN 可以参考此处。
OpenPGP
配置此功能需要安装 GPG,对于 Linux,绝大多数发行版均已默认安装了 GPG,本文基于 Windows 环境,需要自行安装 GPG,此处默认已安装 GPG。
确认 GPG 版本信息
gpg --version
gpg (GnuPG) 2.4.3
libgcrypt 1.10.2
Copyright (C) 2023 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:\Users\fhyuncai\AppData\Roaming\gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2
确认是否能识别到 Canokey
gpg --card-status
Reader ...........: canokeys.org OpenPGP PIV OATH 0
Application ID ...: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: CanoKeys
Serial number ....: 01234567
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]
生成主密钥
gpg --expert --full-gen-key
gpg (GnuPG) 2.4.3; Copyright (C) 2023 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 # 输入 11 生成自定义用途的 ECC 密钥
选择密钥的功能,对于主密钥 Certify (认证) 是必须的。
Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Sign Certify
(S) Toggle the sign capability # 启用签名
(A) Toggle the authenticate capability # 启用身份验证
(Q) Finished # 结束
Your selection? S # 默认会启用 Signature (签名) 功能,需要取消勾选 (非必要)
Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Certify
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? Q # 完成
选择加密算法并配置密钥相关信息
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
GnuPG needs to construct a user ID to identify your key.
Real name: FHYunCai # 姓名
Email address: fhyuncai@xxx.com # 邮箱
Comment: # 注释
You selected this USER-ID:
"FHYunCai <fhyuncai@xxx.com>"
# 更改姓名(N)、注释(C)、电子邮件地址(E)或确定(O)/退出(Q)
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.
gpg: directory 'C:\\Users\\fhyuncai\\AppData\\Roaming\\gnupg\\openpgp-revocs.d' created
gpg: revocation certificate stored as 'C:\\Users\\fhyuncai\\AppData\\Roaming\\gnupg\\openpgp-revocs.d\\ABCDEF1234567890ABCDEF12M123456789012345.rev' # 吊销密钥生成路径
public and secret key created and signed.
pub ed25519 2024-05-27 [C]
ABCDEF1234567890ABCDEF12M123456789012345
uid FHYunCai <fhyuncai@xxx.com>
生成子密钥
现在需要基于主密钥来生成三种类型的子密钥(Signature, Encryption, Authentication)
# 列出密钥 ID
gpg --fingerprint -K --keyid-format LONG
[keyboxd]
---------
sec ed25519/M123456789012345 2024-05-27 [C]
Key fingerprint = ABCD EF12 3456 7890 ABCD EF12 M123 4567 8901 2345
uid [ultimate] FHYunCai <fhyuncai@xxx.com>
# 编辑主密钥
gpg --expert --edit-key M123456789012345 # 此处 M123456789012345 为密钥 ID
gpg (GnuPG) 2.4.3; Copyright (C) 2023 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/M123456789012345
created: 2024-05-27 expires: never usage: C
trust: ultimate validity: ultimate
[ultimate] (1). FHYunCai <fhyuncai@xxx.com>
生成 Signature 子密钥
gpg> addkey
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 # 选择 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>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) 3y # 3年后过期
Key expires at 2027/5/27 23:33:33 # 密钥将于 2027/5/27 23:33:33 过期
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/M123456789012345
created: 2024-05-27 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/S123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: S
[ultimate] (1). FHYunCai <fhyuncai@xxx.com>
gpg> save # 记得保存
以此类推,分别生成 Encryption 和 Authentication 子密钥,最终密钥列表如下
gpg --fingerprint -K --keyid-format LONG
[keyboxd]
---------
sec ed25519/M123456789012345 2024-05-27 [C]
Key fingerprint = ABCD EF12 3456 7890 ABCD EF12 M123 4567 8901 2345
uid [ultimate] FHYunCai <fhyuncai@xxx.com>
ssb cv25519/E123456789012345 2024-05-27 [E] [expires: 2027-05-27]
ssb ed25519/A123456789012345 2024-05-27 [A] [expires: 2027-05-27]
ssb ed25519/S123456789012345 2024-05-27 [S] [expires: 2027-05-27]
备份私钥
由于转移子密钥后会将本地私钥删除且无法从 Canokey 提取,需要提前备份私钥,顺便将主密钥提取并转移至安全区域。
gpg -ao master-secret-key.asc --export-secret-key [主密钥ID] # 主密钥私钥信息,推荐保存到离线介质
gpg -ao sign-secret-key.asc --export-secret-key [签名子密钥ID]
gpg -ao encrypt-secret-key.asc --export-secret-key [加密子密钥ID]
gpg -ao authenticate-secret-key.asc --export-secret-key [认证子密钥ID]
转移子密钥至 Canokey
gpg --expert --edit-key M123456789012345
gpg (GnuPG) 2.4.3; Copyright (C) 2023 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/M123456789012345 # 主密钥
created: 2024-05-27 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/S123456789012345 # 子密钥 Signature key
created: 2024-05-27 expires: 2027-05-27 usage: S
ssb cv25519/E123456789012345 # 子密钥 Encryption key
created: 2024-05-27 expires: 2027-05-27 usage: E
ssb ed25519/A123456789012345 # 子密钥 Authentication key
created: 2024-05-27 expires: 2027-05-27 usage: A
[ultimate] (1). FHYunCai <fhyuncai@xxx.com>
gpg> key 1 # 选中第一个子密钥 [S]
sec ed25519/M123456789012345
created: 2024-05-27 expires: never usage: C
trust: ultimate validity: ultimate
ssb* ed25519/S123456789012345 # ssb 后面有 * 表示选中这个 key
created: 2024-05-27 expires: 2027-05-27 usage: S
ssb cv25519/E123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: E
ssb ed25519/A123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: A
[ultimate] (1). FHYunCai <fhyuncai@xxx.com>
gpg> keytocard # 将密钥写入 Canokey
Please select where to store the key:
(1) Signature key # 签名密钥
(3) Authentication key # 身份验证密钥
Your selection? 1 # 将 [S] 密钥写入 Canokey 的 Signature 区域
# 此处要求输入解锁主密钥的 PIN 以及 Canokey 的 Admin PIN
gpg> key 1 # 取消选中第一个子密钥
sec ed25519/M123456789012345
created: 2024-05-27 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/S123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: S
ssb cv25519/E123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: E
ssb ed25519/A123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: A
[ultimate] (1). FHYunCai <fhyuncai@xxx.com>
gpg> key 2 # 选中第二个子密钥 [E]
sec ed25519/M123456789012345
created: 2024-05-27 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/S123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: S
ssb* cv25519/E123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: E
ssb ed25519/A123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: A
[ultimate] (1). FHYunCai <fhyuncai@xxx.com>
gpg> keytocard # 将密钥写入 Canokey
Please select where to store the key:
(2) Encryption key # 加密密钥
Your selection? 2 # 将 [E] 密钥写入 Canokey 的 Encryption 区域
gpg> key 2 # 取消选中第二个子密钥
sec ed25519/M123456789012345
created: 2024-05-27 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/S123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: S
ssb cv25519/E123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: E
ssb ed25519/A123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: A
[ultimate] (1). FHYunCai <fhyuncai@xxx.com>
gpg> key 3 # 选中第三个子密钥 [A]
sec ed25519/M123456789012345
created: 2024-05-27 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/S123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: S
ssb cv25519/E123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: E
ssb* ed25519/A123456789012345
created: 2024-05-27 expires: 2027-05-27 usage: A
[ultimate] (1). FHYunCai <fhyuncai@xxx.com>
gpg> keytocard # 将密钥写入 Canokey
Please select where to store the key:
(3) Authentication key # 身份认证密钥
Your selection? 2 # 将 [E] 密钥写入 Canokey 的 Authentication 区域
gpg> save # 保存后写入完成
写入完成后验证一下
gpg --card-status
Reader ...........: canokeys.org OpenPGP PIV OATH 0
Application ID ...: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: CanoKeys
Serial number ....: 01234567
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 ...: ed25519 cv25519 ed25519
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 ....: ABCD EF12 3456 7890 ABCD EF12 S123 4567 8901 2345
created ....: 2024-05-27 23:33:33
Encryption key....: ABCD EF12 3456 7890 ABCD EF12 E123 4567 8901 2345
created ....: 2024-05-27 23:33:33
Authentication key: ABCD EF12 3456 7890 ABCD EF12 A123 4567 8901 2345
created ....: 2024-05-27 23:33:33
General key info..: sub ed25519/S123456789012345 2024-05-27 FHYunCai <fhyuncai@xxx.com>
sec ed25519/M123456789012345 created: 2024-05-27 expires: never
ssb> cv25519/S123456789012345 created: 2024-05-27 expires: 2027-05-27
card-no: F1D0 01313506
ssb> ed25519/E123456789012345 created: 2024-05-27 expires: 2027-05-27
card-no: F1D0 01313506
ssb> ed25519/A123456789012345 created: 2024-05-27 expires: 2027-05-27
card-no: F1D0 01313506
至此,所有密钥就已经正确的写入到 Canokey 里面了。
Canokey 丢失 / 重置后重新写入
由于 GPG 会绑定智能卡的编号,将密钥写入 Canokey 后无法再次写入,所以需要在以下目录删除相关密钥文件。
# Windows
%USERPROFILE%\AppData\roaming\gnupg\private-keys-v1.d
# Linux
~/.gnupg/private-keys-v1.d
之后导入备份的私钥后,再根据前面的步骤将子密钥写入 Canokey 即可。
解决使用 GPG 后无法使用 Web Console 管理
使用以下代码解除占用:
gpgconf --kill gpg-agent
待更新
果然所有人到手一个 canokey 之后都会选择写一篇博客吗(x
其实已经过了好久了(