开箱 | 国产开源硬件安全密钥 Canokey Pigeon

前言

之前没赶上 cf 优惠券的活动,然后又一直心水 Yubikey,奈何国内的价格太贵,闲鱼的价格也一直不降反涨,无意间发现了国产开源硬件 Canokey 的存在,似乎可以平替 Yubikey,在清华开源站中也可以查到相关的资料:金枪鱼之夜:从 YutriKey 到 CanoKey

简单开箱

目前提供两种商业产品和两种开发产品,均使用的相同内核:canokey-core

我这款是 Canokey Pigeon,经典鸽子

开箱开箱:

包装

样子大概长这样:

反面

正面

插入亮灯

介绍

Canokey 支持的协议很多,类似与 Yubikey 5 NFC,支持的协议如下:

  • FIDO2 / U2F
  • OpenPGP
  • PIV
  • NDEF
  • OATH(TOTP、HTOP)

对于平替 Yubikey 的需求,协议方面是足够了,但是由于硬件方面的问题,NFC 的实现效果可能不尽人意,具体可以参照以下两个 discussion:

官方文档中也有各个协议的详细说明,同时由于协议互通,Yubikey 的一些配置文档也通用()

相关名词解释

下面用容易理解的话对相关名词进行解释,目的是为了让读者有一个大致的了解,具体实现原理或详细信息等可以自行查找相关资料了解

非对称加密、对称加密

简单理解就是非对称加密是通过算法生成一对密钥对,分为公钥和私钥,公钥用来加密,私钥用来解密,对称加密则是一个钥匙既可以加密也可以解密

当然理论上公钥和私钥是可以互换的,但是由于算法会偏向于一方,使得双方长度不相同,一般来说私钥长度较长,长度不同使得破解公钥的难度小于破解私钥的难度,基于这方面原因公钥和私钥是不可以互换的

2FA / MFA

2FA:Two-Factor authentication,双因素认证

MFA:Muti-Factor authentication,多因素认证

为了让用户登录网站时不单单将安全托付于单一的密码上,开发者选择了使用额外因素的方式来保护用户账号安全,防止因为密码泄露而整个账户遭到泄露

OATH、OTP

OATH:Open Authentication,开放认证

OTP:One-Time Password,一次性密码

OTP 是开发者使用两步验证最常用的手段,顾名思义一次性密码只能使用一次,OTP 分为以下两类:

  • TOTP:Time-based One-Time Password,基于时间戳的一次性密码
  • HOTP:HMAC-based One-Time Password,基于 HMAC 算法加密的一次性密码

大致原理是客户端和服务器端均有一个共同密钥,客户端将共同密钥与时间因子经过特定的算法生成代码发送给服务器,服务器端也使用共同密钥与当前的时间因子通过相同的算法生成代码,将两个代码进行比对,如果相同则认证成功,当然如果你把密钥给别人,别人也是能和你算出同样的结果的(

FIDO、FIDO2

FIDO 联盟制定了 FIDO 标准,为了统一由各家 2FA 带来的差异而设立的,毕竟谁也不想换一个 2FA 验证登录就需要多装一个驱动之类的麻烦事,于是 FIDO 标准为了改良传统的 2FA 的体验出现了

FIDO2 是新一代的 FIDO 标准

U2F、UAF

U2F 标准可以认为是 FIDO 标准之一,它是制作类似于 Yubikey / Canokey 之类硬件的标准,可以是 USB 形态的,也可以是 NFC 形态,简单来说是物理形态,免去了用户需要安装第三方驱动或者输入 OTP 代码进行认证的麻烦

既然有物理形态,那么就有非物理形态,UAF 则是通过指纹、语音、虹膜等进行认证的方案

WebAuthn

WebAuthn 标准是 FIDO2 标准的一部分,FIDO2 相比于 U2F 增加了单因素认证的功能,这便是 WebAuthn 标准,这意味着可以实现真正的免密登录,只需要插入物理设备或者通过生物特征进行认证即可

PGP、OpenPGP、GnuPG

PGP 全称是 Pretty Good Privacy,是一个被设计用来加密信息,保护隐私的软件

OpenPGP 是一个标准,现在提到的 PGP 基本上是指 OpenPGP

OpenPGP 是在 PGP 基础上定义的开放标准,GnuPG 则是对 OpenPGP 标准的完备实现

OpenPGP 支持多种算法,其中非对称加密的 RSA 和 ECC 用的比较多,RSA 兼容性好,ECC 体积小、速度快、具有更好的安全性

具体实现原理可以自行搜索,这里不再赘述,对于安全性,经过这么多年的检验,对于个人信息保护还是有保证的

OpenPGP 可以用来给消息、邮件之类的加解密,git commit 认证,ssh 免验证登录等

PIV

PIV 即个人身份验证,是美国政府标准,使用智能卡存储用于签名 / 加密的密钥,主要用于非 Web 场景,可以用来进行验证、签名、加密、认证等

NDEF

NDEF:NFC Data Exchange Format,即 NFC 数据交换格式

Canokey 使用

Web Console

新版本的 Web Console 支持的功能已经变得很多了,大部分功能直接可以使用

Web Console 必须使用支持 WebUSB 的浏览器,例如 chromium 系浏览器,Firefox 不支持

插入 Canokey 访问 https://console.canokeys.org 即可,点击右上角的刷新按钮对 Canokey 进行读取

先进入设置界面,输入自定义 PIN:

输入自定义 PIN

侧边栏设置

相关设置如下,可以查看 Canokey 的相关信息,以及对相关操作做一些简单设置和重置

设置

设置

注意:上述修改的 PIN 仅仅为 Admin panel 的 PIN,与下方所有的 PIN 没有任何关系

OTP

先来介绍如何在 Canokey 中使用 OTP,由于现在绝大多数平台支持的是 TOTP,本文仅介绍 TOTP

类似于 Google Authenticator,Aegis 等两步验证软件,Canokey 可以直接在 Web Console 中进行添加,并且可以单独为每个 TOTP 设置触摸策略(最多可以保存 100 个):

OTP 设置

当然使用 ykman 4.0 进行配置也是 ok 的,具体操作参考官方文档

对于 5.x 版本的 Yubico Authenticator,可以使用 custom reader 直接进行配置:

custom reader

image-20230410123748758

历史版本可以在这里下载:https://developers.yubico.com/yubioath-flutter/Releases/

Android 只有 6.x 的版本,虽然移除了 custom reader 的选项,不能通过 type-c 接口进行识别,但通过 NFC 还是能够进行绑定的,开始识别率感人,但是掌握了识别的位置还是能很快识别成功的(手机上禁止截屏,这里就不放图了)

官方表示以后也有开发专属 app 的计划,敬请期待吧

FIDO2 / U2F

WebAuthn 测试网站:https://webauthn.io/

测试

测试

测试

使用方法很简单,插入 Canokey 在支持的网站上打开两步验证后选择硬件密钥即可,支持的网站例如:Microsoft、Google、Facebook 等,密钥将会自动注册并要求输入 PIN,下次输入 PIN 就可作为二步验证登录了

目前 Microsoft 账户支持使用硬件密钥实现单因素登录,即无需密码直接登录,可以进入账户的其他安全选项部分进行添加

支持网站列表:https://2fa.directory/int/

OpenPGP

除了上述的 TOP 和 FIDO2,这部分算是最主要的应用场景,和 Yubikey 一样,Canokey 同样提供三个子密钥插槽,分别用作签名、认证和解密,对于密钥生成的操作也类似,大致就是一份主密钥,三份子密钥,主密钥只保留验证功能,其他功能使用子密钥

以下操作均在 Win 下进行,使用的工具为 Gpg4Win

相关指令

 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
# card related
# try below to make sure gpg works with canokey
gpg --card-status
# use this for editting card info and config
# and/or generating keys
gpg --edit-card
# generate key
gpg --expert --full-generate-key
# get key infos
gpg --list-keys --with-fingerprint --with-subkey-fingerprint [keyid or user id]
gpg --list-keys --with-keygrip [keyid or user id]
gpg --list-sigs [keyid or user id]
# edit key
# add uid/subkey in the interactive shell
# keytocard or addcardkey
gpg --edit-key <keyid or user id>
# import/export key
gpg --import file
gpg --armor --output file --export <keyid or user id>
gpg --armor --output file --export-secret-keys <keyid or user id>
gpg --delete-keys <keyid or user id>
# sign and verify
gpg --armor --sign file
gpg --sign-key --ask-cert-level <key id>
gpg --armor --detach-sign file
gpg --clear-sign file
gpg --verify file.asc
# encrypt and decrypt
gpg --armor --encrypt --recipient <keyid or user id>
gpg --decrypt file
# misc
gpgconf --kill gpg-agent
gpg-connect-agent reloadagent /bye
gpgconf --list-dirs agent-socket
gpgconf --list-dirs agent-extra-socket
gpgconf --list-dirs agent-ssh-socket

生成主密钥

 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
79
80
81
gpg --expert --full-gen-key # 生成主密钥

gpg (GnuPG) 2.4.0; 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)
   (4) RSA (sign only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
   (9) ECC (sign and encrypt) *default*
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (13) Existing key
  (14) Existing key from card
Your selection? 11 # 这里使用 ECC 算法,注意括号中的内容为自行设置密钥功能

Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Sign Certify # 目前主密钥拥有 Sign 和 Certify 功能,主密钥只需要 Certify 功能即可,其他功能使用子密钥

   (S) Toggle the sign capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? s # 取消 Sign 功能

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 # 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: XXXXXX
Email address: XXXXXX@XXX.com
Comment: # 可以不填
You selected this USER-ID:
    "XXXXXX <[email protected]>"

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.
# Windows 下弹出窗口输入密码,注意保存
gpg: directory 'C:\\Users\\lsilencej\\AppData\\Roaming\\gnupg\\openpgp-revocs.d' created
gpg: revocation certificate stored as 'C:\\Users\\lsilencej\\AppData\\Roaming\\gnupg\\openpgp-revocs.d\\D6B340E4E2AF36A9A5978360A12123716A6F3444.rev' # 自动生成吊销证书,注意保存
public and secret key created and signed.

pub   ed25519 2023-04-10 [C]
      XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # 公钥
uid                      XXXXXX <XXXXXX@XXX.com> # uid

生成子密钥

 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
gpg --fingerprint --keyid-format long -K # 查看目前的密钥

C:\Users\lsilencej\AppData\Roaming\gnupg\pubring.kbx
----------------------------------------------------
sec   ed25519/A12123716A6F3444 2023-04-10 [C]
      Key fingerprint = XXXX XXXX XXXX XXXX XXXX  XXXX XXXX XXXX XXXX XXXX # 密钥
uid                 [ultimate] XXXXXX <[email protected]>

# 生成不同功能的子密钥,<fingerprint> 为上面查看的密钥,2y 表示有效期为 2 年,不填写默认不过期
# 注意将密钥中间的空格去掉,每次操作都会弹出窗口,需要输入之前输入的密码
gpg --quick-add-key <fingerprint> cv25519 encr 2y # 注意:由于 ECC 实现的原因,加密算法和其余两个功能不一样
gpg --quick-add-key <fingerprint> ed25519 auth 2y
gpg --quick-add-key <fingerprint> ed25519 sign 2y

gpg --fingerprint --keyid-format long -K # 查看目前的密钥
C:\Users\lsilencej\AppData\Roaming\gnupg\pubring.kbx
----------------------------------------------------
sec   ed25519/A12123716A6F3444 2023-04-10 [C]
      Key fingerprint = XXXX XXXX XXXX XXXX XXXX  XXXX XXXX XXXX XXXX XXXX
uid                 [ultimate] XXXXXX <[email protected]>
ssb   cv25519/B397DE74ADB1461B 2023-04-10 [E] [expires: 2025-04-09]
ssb   ed25519/96D21F146844DE42 2023-04-10 [A] [expires: 2025-04-09]
ssb   ed25519/F9BBC38695CD8E36 2023-04-10 [S] [expires: 2025-04-09]

# 加密密钥用于加密文件和信息,签名密钥用于给自己信息签名,认证密钥主要用于 ssh 登录

UID 设置

UID 可以用于 git commit 签名,这也是 github 力推的 verified signature

可以单独添加 git 使用的 UID:

1
2
3
4
5
6
7
gpg --quick-add-uid <fingerprint> 'XXXXXX <[email protected]>' # 开启了 github 邮箱隐私保护

# gpg 会将最近添加的 UID 作为主 UID,也可以通过下面的命令手动指定,注意邮箱用 <> 包括
gpg --quick-set-primary-uid <fingerprint> 'XXXXXX <[email protected]>'

# 可以通过这条命令对子密钥或 uid 进行修改,将 id 替换为相应位置
gpg --edit-key <keyid or user id>

备份

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 导出公钥
gpg -ao public-key.pub --export A12123716A6F3444 # 后面的为 keyid,可以列出所有密钥查看

# 私钥备份均以 ! 结尾,表示只导出这一个私钥,否则默认导出全部私钥
# 导出主密钥
gpg -ao sec-key.asc --export-secret-key A12123716A6F3444!
# 导出子密钥
gpg -ao sign-key.asc --export-secret-key F9BBC38695CD8E36!
gpg -ao auth-key.asc --export-secret-key 96D21F146844DE42!
gpg -ao encr-key.asc --export-secret-key B397DE74ADB1461B!

备份策略:

  • 主密钥只保留一份,建议备份在一个全盘加密的 U 盘中,然后放在一个绝对安全的地方
  • 子密钥可以复制多份,通过 U 盘导入各个设备,专密专用,日常使用推荐用智能卡(比如 Yubikey),还能免去每次输密码的麻烦
  • 撤销凭证可以和主密钥放在一起备份一份, 另外单独备份一份(这样丢失密钥,起码还可以撤销)

导入 Canokey

 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
gpg --card-status # 查看智能卡设备状态
gpg --edit-card # 编辑智能卡信息

gpg/card> admin # 进入 admin
Admin commands are allowed

gpg/card> passwd # 修改 PIN(默认 123456)以及 Admin PIN(默认 12345678),密码忘记只能重置

# 下面写入 Canokey,注意操作不可逆,且本地密钥会删除,请做好备份再操作
gpg --fingerprint --keyid-format long -K # 查看主密钥 keyid

gpg --edit-key A12123716A6F3444 # 为上述命令显示的 sec-key id

gpg> key 1 # 选中第一个子密钥
gpg> keytocard # 写入 card
gpg> key 1 # 取消选中

gpg> key 2
gpg> keytocard
gpg> key 2

gpg> key 3
gpg> keytocard
gpg> key 3

# 保存修改
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 ....: XXXXXXXX
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 ....: XXXX XXXX XXXX XXXX XXXX  XXXX XXXX XXXX XXXX XXXX
      created ....: 2023-04-10 08:49:21
Encryption key....: XXXX XXXX XXXX XXXX XXXX  XXXX XXXX XXXX XXXX XXXX
      created ....: 2023-04-10 08:47:47
Authentication key: XXXX XXXX XXXX XXXX XXXX  XXXX XXXX XXXX XXXX XXXX
      created ....: 2023-04-10 08:49:07
General key info..: sub  ed25519/F9BBC38695CD8E36 2023-04-10 XXXXXXXX <[email protected]>
sec   ed25519/A12123716A6F3444  created: 2023-04-10  expires: never
ssb>  cv25519/B397DE74ADB1461B  created: 2023-04-10  expires: 2025-04-09
                                card-no: F1D0 XXXXXXXX
ssb>  ed25519/96D21F146844DE42  created: 2023-04-10  expires: 2025-04-09
                                card-no: F1D0 XXXXXXXX
ssb>  ed25519/F9BBC38695CD8E36  created: 2023-04-10  expires: 2025-04-09
                                card-no: F1D0 XXXXXXXX

子密钥为 ssb> 表示本地只有一个指向 card-no: F1D0 XXXXXXXX 的智能卡指针,已经不存在私钥,现在可以直接删除主密钥,请再次确认已经备份完成

1
gpg --delete-secret-keys A12123716A6F3444

同时为了安全,你还可以将 gpg 的工作目录删除,Win:%APPDATA%\gnupg,Linux/macOS: ~/.gunpg

使用 Canokey

1
2
3
4
5
6
7
8
9
# 导入公钥
gpg --import public-key.pub

# 设置子密钥指向 Canokey
gpg --edit-card
gpg/card> fetch

# 查看本地私钥
gpg --fingerprint --keyid-format long -K

此时可以看到子密钥已经指向 Canokey,已经可以正常使用了,其他配置可以去 Web Console 进行配置

可能存在多个智能卡,切换后密钥指向错误的问题,可以使用以下命令刷新序列号:

1
2
# 刷新 Card serial no
gpg-connect-agent "scd serialno" "learn --force" /bye

Git Commit 签名

1
git config --global user.signingkey F9BBC38695CD8E36 # 上述 Sign 密钥的 keyid

git commit 时添加 -S 参数即可使用 gpg 进行签名,也可在配置中设置自动 gpg 签名

1
git config commit.gpgsign true

提交到 Github 还需要前往 GitHub SSH and GPG keys 中添加公钥,添加完成后可以通过访问 https://github.com/<yourid>.gpg 来获取公钥

SSH Agent

以下操作在 Archlinux 环境下进行,Windows 操作类似,使用工具 WinCrypt SSH Agent 可以实现在 WSL 中进行配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ cd ~/.gnupg

# 获取配置文件
$ wget https://raw.githubusercontent.com/drduh/config/master/gpg-agent.conf

# 查看过滤掉注释后的配置文件
$ grep -ve "^#" gpg-agent.conf
enable-ssh-support
default-cache-ttl 60
max-cache-ttl 120
pinentry-program /usr/bin/pinentry-curses

# 在 shell rc 配置文件中添加以下环境变量,我的是 zsh,即 ~/.zshrc 配置,注意等号两边无空格
export GPG_TTY="$(tty)"
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent

# 查看 ssh 公钥
ssh-add -L

将公钥添加至服务器的 ~/.ssh/authorized_keys 文件内即可,输入 ssh user@host,即可弹出输入 PIN 页面,输入 PIN 即可正常登录

如果一直出现

1
sign_and_send_pubkey: signing failed: agent refused operation

可能是由于这是 systemd 启动的 agent,而不是 X session 启动的,所以要么没有,要么是不可用的 tty,可以通过以下命令进行更新

1
gpg-connect-agent updatestartuptty /bye

PIV

PIV 目前还没进行研究,不过看博客可以实现 BitLocker 全盘加密,Archlinux 的免密登录等,以后有时间再慢慢研究吧

总结

总的来说,Canokey 也算是给我带来了一点小小的惊喜,基本满足我的需要,也缓解了没有买到 Yubikey 的遗憾,毕竟功能都差不多,甚至连文档都能互相用(),产品基本没什么太大的缺陷,对于日常使用还是足够的,使用 GPG 进行 Git Commit 签名以及 SSH 免验证登录体验还算不错,如果有类似的需求的话可以考虑考虑这款平替 Yubikey 的产品

参考

金枪鱼之夜:从 YutriKey 到 CanoKey

Canokeys

Document For Canokey

Canokeys Github

Canokey 指南:OTP,FIDO2,PGP 与 PIV

YubiKey-Guide