RSA/ECC密码算法实现细节、关于密码产品在大陆境内的合法性等问题不在本文讨论范围。

手上的Yubikey 5 NFC一直以来都是我2FA的设备之一,这阵子也偶然了解到CanoKey这款产品,也是和Yubikey比较类似,看了不少关于GPG/PGP相关的文章,于是乎想要自己实践一下。#(深思)

比较让人熟知的Yubikey,或者硬件密钥的用法都是U2F、TOTP/HOTP之类的。我手上的这款Yubikey 5 NFC也支持PIV和OpenPGP标准。值得注意的是,PIV和OpenPGP的密钥存储区不共享,分别是X.509和GPG密钥。X.509和OpenPGP两者即使能互相转换,也不应该混为一谈,OpenPGP对S/MIME的X.509有一些支持,但不能逐字使用X.509 PrivateKey。在我看来X.509和OpenPGP在使用上的区别主要体现在,X.509被设计成有一套严格的层次化的证书颁发机构,即为CA,机构背书下的绝对信任。而OpenPGP是任何人都可以签名,从而证明其他人密钥证书的有效性,是属于比较偏社交化的。

在Windows上和PIV以及OpenPGP进行交互都不是那么的方便,这边推荐OpenSC这个库,来帮助实现简单的证书读取、链接等功能。#(赞一个)

列出密钥

gpg --fingerprint --keyid-format long -K

创建密钥

生成主密钥

gpg --full-gen-key --expert

在这一步中,会选择密钥的算法、有效期、UserID、Email等信息。

请注意密钥算法是否被你的硬件密钥所支持,同时记得备份好吊销执照。

在这一步中比较重要的是,选择主密钥的功能。大家有不同的分配。一个密钥可以担任Encr、Auth、Sign、Certify,这四种功能,分别为加密、鉴权、签名、认证。我的做法是,主密钥仅保留认证C功能,然后分别签发出E、A、S三种子密钥,存放在硬件密钥中。主密钥做好脱机备份,大部分时间的物理隔离即可。

生成子密钥

gpg --quick-add-key <fingerprint> rsa3072 encr 2y 
gpg --quick-add-key <fingerprint> rsa3072 auth 2y
gpg --quick-add-key <fingerprint> rsa3072 sign 2y

<fingerprint>需要填写的是主密钥的指纹,这个可以通过列出密钥来查看。
后面依次指定子密钥的加密算法、用途、有效时长。

导出备份

上述所有步骤都是在本机上进行的,也可以直接使用OpenPGP Card规范中,在硬件中生成密钥的功能。
我们需要导出公钥以及各密钥的私钥,以便进行其他工作。

# 公钥
gpg -ao public-key.pub --export <fingerprint>

# 主密钥,请务必保存好。
# 注意<fingerprint>后面的 !
# 表示只导出这一个私钥,若没有的话默认导出全部私钥。
gpg -ao sec-key.asc --export-secret-key <fingerprint>!

# 子密钥
gpg -ao sign-key.asc --export-secret-key <fingerprint>!
gpg -ao auth-key.asc --export-secret-key <fingerprint>!
gpg -ao encr-key.asc --export-secret-key <fingerprint>!

导入OpenPGP Card

# 查看智能卡设备状态
gpg --card-status
# 写入GPG
gpg --edit-key <fingerprint> # 主密钥
# 选中第一个子密钥
key 1
# 写入到智能卡
keytocard

## 依次进行,注意选择各个插槽以及密钥用途的对应。

# 保存修改并退出
save

# 再次查看设备状态,可以看到相关槽位有密钥信息了。
gpg --card-status

OpenPGP导出用于SSH用途

在继续行文之前,也要提一下,本节内容具有一定争议。主要体现在以下两点:

  1. OpenPGP密钥是否可以和SSH所用的密钥进行互换?
  2. 操作是否具有一定的安全风险?

对于上述第一个问题,SSH认证一般都使用的是RSA密钥,但从目前的OPENSSL以及OPENSSH支持范围来看,无论是ECC还是RSA算法,都是支持的。此外和X.509体系不同的是,PGP和SSH密钥「这里使用了非标准的表述方式」都是解决主观或客观无背书下的相互信任。
第二个问题,有人认为,PGP体系的公钥公开会影响安全。但拥有公钥并不能推导出私钥。
但两种情况下我认为会在某些角度影响安全性。

  1. 主私钥泄密情况下,公钥吊销是否能及时同步到目标机上?答案是否,目前SSH还不是这个机制
  2. 在多处使用同一个私钥是否会导致问题?

我个人硬件密钥想要解决的是场景是,使用非可信设备进入可信区进行操作的问题,所以只要控制好私钥副本的传播即可(硬件密钥无法拷出私钥)。

使用openpgp2ssh工具,位于monkeysphere包内,Linux和MacOS均有提供

key=FFFFFFFF # 指纹

# 导出gpg格式私钥
gpg --export-secret-key "$key" > id_rsa.bak

# 记得移除私钥密码
# * gpg --edit-key "$key"
# * Issue passwd and remove the password
# * Quit, and save changes

# 导出
gpg --export "$key" | openpgp2ssh "$key" > ~/.ssh/id_rsa.pub
gpg --export-secret-key "$key" | openpgp2ssh "$key" > ~/.ssh/id_rsa

# Then, we change the password for the SSH secret key
ssh-keygen -f ~/.ssh/id_rsa -p

# Now reimport the original key (deletion is required or for some reason it fails to reimport as encrypted)
gpg --delete-secret-key "$key"
gpg --import < id_rsa.bak
rm id_rsa.bak

或者下述更容易理解的方式
请注意,openpgp2ssh工具目前无法处理加密的私钥,会提示:We cannot handle encrypted secret keys. Skipping!

gpg --edit-key FFFFFFFF # 我们需要先移除密钥上的密码
gpg --export FFFFFFFF | openpgp2ssh FFFFFFFF > id_rsa.pub # generating public key
gpg --export-secret-key FFFFFFFF | openpgp2ssh FFFFFFFF > id_rsa # generating private key
gpg --delete-secret-key FFFFFFFF # cleanup

使用OpenPGP私钥创建X.509

本节内容使用了Yubikey的PIV功能,存储先前所述的子密钥,用于部分验证场景。
由于在Win平台上,部分软件并不能调用OpenPGP Card,只能调用PIV区域的密钥,所以gpg-agent就略显手足无措。

创建X.509请求

openssl req -new -key auth_rsa.key -out auth_crt.csr

期间会需要输入一些证书信息,这些本质上是给CA用于签发的,我们按需填写即可。

自行签发

openssl x509 -req -days 3650 -in auth_crt.csr -signkey auth_rsa.key -out auth_rsa.crt

在SSH中使用

Putty SSH 等原生软件

公钥导出在先前内容中都有提到,不再阐述。

gpg-agent --daemon --enable-ssh-support

killall gpg-agent # kill as you need

需要将输出的结果放入环境变量,使用起来如下图,就很Nice#(赞一个)

ios端如何激活emby_安全

我们可以很轻而易举的在SSH、Git等场景下使用。

在xShell中使用

需要用到PIV区,以及WinCryptSSHAgent软件
(https://github.com/buptczq/WinCryptSSHAgent/)

XShell官方的方法是使用PCKS#11验证,要装插件来实现,本节就不再阐释了。
请注意导入的密钥位数,PIV区支持的密钥位数和OpenPGP不一致,一般最大只支持2048,子密钥都是3072的就只能干瞪眼了。
WinCryptSSHAgent需要在XShell打开前启动

# 导入证书文件 9a 槽位是用于认证的
yubico-piv-tool -s 9a -a import-certificate -i mykey.crt
# 导入密钥文件,触摸策略是每次都touch,可选never、cached
yubico-piv-tool -s 9a -a import-key --touch-policy=always -i private.key

上述操作的主要目的是向槽位分别写入公钥与私钥。若写入后提示无法使用该智能卡,可能是在写入时有问题,可以尝试清空9a槽后重新执行导入操作。

后记

本文主要记录了Yubikey这类硬件密钥配合SSH场景认证的使用方式,事实上Yubikey的部分功能并不是智能卡和OpenPGP Card的完整实现,部分gpg/card内的功能并不能百分百匹配上,作为认证场景使用,对我来说可能是目前Yubikey最主要的一个用处了。