Yubikey 的使用和配置

最近拿到了几个 Yubikey,总结一下在 2022 年我是如何配置 Yubikey 的。

使用 Yubikey OTP 认证

这是一个 Yubikey 最基础的认证方式之一,当 Yubikey 插入到 USB 接口之后触摸金属点会自动输入一个长度为 44 的字符串和一个回车符号,这 44 个字符由 12 位始终一致的编号和 32 位随时间动态改变的编码组成。使用时需要在对应平台预先填写 44 位字符串,在登录时验证新的字符串即可,以 LastPass 为例,最多支持绑定 5 个 Yubikey,在设置里面绑定之后,登录账号时会显示一个一样的文本输入框,触摸金属点会输入密码并带上回车自动登录。Bitwarden/Vaultwarden 也支持这种方式。

picture 1

Yubikey OTP 的实现原理可以看OTPs Explained,他的缺点是需要依赖 YubiCloud 的服务器进行验证,这带来了可用性的问题,以及隐私和安全性问题

使用 WebAuthn 认证

之所以先提 WebAuthn 是因为它是一个中立的标准,也是目前体验相对流畅的认证方式,WebAuthn 是一个W3C 标准,目前主流浏览器已经支持,尤其特别值得一提的是在移动端比如 iOS Safari 和 Android Chrome 已经完全支持,这样使得真正能够在全平台使用。WebAuthn 不仅仅适配 Yubikey 这样的独立设备,也适配手机的指纹和面部识别,甚至是 Windows Hello 这样的 Windows 系统的认证方式,这样就可以在不同的平台上使用同样的认证方式,而不需要为每个平台单独配置。

WebAuthn.io 上可以直接测试 WebAuthn 的支持情况,和大多数认证方式一样,需要先注册,下面是 Chrome 测试的情况,默认不会使用 Yunbikey,需要手动选择 USB 安全密钥。

picture 2

picture 3

然后在选择 USB 安全密钥这一项的时候可能会出现两种情况,一种是让你设置 PIN 一种是直接验证通过,这是因为 FIDO2 和 FIDO U2F 的区别:

WebAuthn 保持了对旧版 FIDO U2F 的兼容性,参考这里,目前测试发现 Google 账号、Github 账号、Cloudflare 账号都是使用的 FIDO U2F 作为二次认证,而不是 FIDO2,但是使用的是 WebAuthn 的接口。WebAuthn 在移动端也有良好的兼容性,测试发现如果在 PC 浏览器通过 USB 方式给 Google 账号绑定 Yubikey,在 iOS Safari 上会提示使用 NFC 方式进行认证。下图是 Cloudflare 账号在 PC 的登录界面。

picture 5

参考资料:

使用 OATH(TOTP) 认证

TOTP(Time-Based One-Time Password) 基于时间的一次性密码 (RFC6238),可能是除短信外最常见的多因素验证方式,常见的使用方式是直接使用 Google Authenticator、Microsoft Authenticator 扫描网站提供的二维码,下次需要登录的时候查看 Authenticator 生成的 6 位密码,密码每隔 30 秒发生变化,这种验证方式非常普遍和常用。

Yubico Authenticator 的使用方式稍微有点不同,区别在于使用 Yubico Authenticator 之后需要通过 NFC 或者其他方式连接 Yubikey,然后在 Yubico Authenticator 中添加账号,账号信息直接保存在 Yubikey 中,下次需要查看的时候需要继续连接 NFC 或者其他方式。根据其 FAQ 的描述,一个 Yubikey 最多支持存储 32 个 TOTP 账号。

其实大多数场合直接使用 Google Authenticator、Microsoft Authenticator 这样的 App 足够满足安全性和便捷性的要求,Yubico Authenticator 的使用方式更加复杂,但是存储在 Yubikey 相比 App 可能更加安全,同时可以避免 App 被清除数据或者手机被破坏导致的数据丢失。一般来说建议使用 Google Authenticator、Microsoft Authenticator 之类的工具或者 Yubico Authenticator 都需要注意备份:

参考资料:

使用 FIDO U2F 认证 SSH

OpenSSH 从 8.2 开始支持 FIDO/U2F,可以使用 Yubikey 作为 SSH 的二次认证,这样可以避免使用密码登录 SSH,同时也可以避免使用 SSH Key 登录的时候需要输入密码的问题。需要注意的是 OpenSSH 8.2 发布于 2020-02-14,意味着之前很多发行版不支持。

注意:macOS 内置的 openssh(路径一般为 /usr/bin/ssh ,以下简称为 macos/openssh) 不支持 FIDO/U2F,所以需要通过 brew 安装最新版的 openssh(路径一般为 /opt/homebrew/bin/ssh ,以下简称 brew/openssh),但是 brew/openssh 不支持 Keychain 这意味着如果 ssh key 设置了密码,每次使用或者添加到 ssh-agent 时都需要输入密码。

brew install openssh

然后通过 ssh-keygen 生成 ~/.ssh/id_ed25519_sk

ssh-keygen -t ed25519-sk -C name@example.com
# 其他方式和参数建议阅读 https://aditsachde.com/posts/yubikey-ssh/

生成之后更新 vim ~/.ssh/config 让 github 使用这个文件,使用 IgnoreUnknown UseKeychain 可以让 brew/openssh 不报错使得这份配置文件同时兼容 macos/openssh,不过并不能让 brew/openssh 使用 Keychain。

Host github.com
  IgnoreUnknown UseKeychain
  UseKeychain yes
  HostName github.com
  User git
  AddKeysToAgent yes
  IdentityFile ~/.ssh/id_ed25519_sk

通过 ssh-add 添加到 ssh-agent

eval `ssh-agent`
ssh-add ~/.ssh/id_ed25519_sk
ssh-add -l

添加完成之后可以进行 git 操作测试或者使用 ssh -T 测试是否正常工作,注意验证 key 的时候可能屏幕不会提示但是 Yubikey 会有灯光闪烁提示,闪烁时触摸即可认证。

ssh -T git@github.com

总之目前(2022 年)直接使用 FIDO U2F 认证 SSH 的方式还不太成熟,主要问题是 macOS 兼容问题,以及目标服务器 openssh 的支持问题。但是如果不使用 macOS 和较旧的服务器,这种方式是非常好用的。

参考资料:

使用 GPG

备份和配置

使用 Yubikey 可以直接存储 GPG 私钥,甚至可以直接在 Yubikey 内生成私钥,但是出于备份和迁移考虑一般不会这么干,因为存储在 Yubikey 的 GPG 私钥无法被读取,但是可能被重置。即使是在外部生成的 GPG 私钥,在存储到 Yubikey 前一定要注意备份,因为存储到卡片的过程是直接将私钥转移到卡片。下面的过程主要是导入现有的 key 到 Yubikey 内。操作前可以使用 GUI 工具或者运行以下命令导出 key 进行备份(备份 1),1234ABC 为对应 key ID。

gpg --export-secret-key --armor 1234ABC

查看卡片状态

gpg --card-status

输出如下,其中 Signature key Encryption key Authentication key 分别表示 Yubikey 支持的签名、加密、认证三个证书,下面的状态表示此 Yubikey 尚未存储任何 Key。

Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: ******
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: ******
Name of cardholder: [未设定]
Language prefs ...: [未设定]
Salutation .......:
URL of public key : [未设定]
Login data .......: [未设定]
Signature PIN ....: 非强制
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
KDF setting ......: off
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

然后查看系统已导入的 key,找到对应 key 的 ID,下面为 123ABC

gpg --list-secret-keys
/Users/phyng/.gnupg/pubring.kbx
-------------------------------
sec   rsa4096 2018-11-20 [SC]
      123ABC***
uid             [ 绝对 ] *** (***) <***@gmail.com>
ssb   rsa4096 2018-11-20 [E]
ssb   rsa4096 2022-12-19 [A]

创建子密钥

因为 Yubikey 5(C) NFC 只支持存储 3 个 GPG 私钥,这里将主密钥用于 SC 即签名和认证功能,然后创建两个子密钥分别用于加密和鉴权。可以按照下面的方法添加子密钥。

证书配置好后,可以导出备份(备份 2)这样后续操作失败可以无限恢复,然后再写入到 Yubikey。

写入到 Yubikey

使用下面编辑对应 key,将 123ABC 替换为对应 key 的 ID,再次强调迁移私钥前前请保证当前私钥已经妥善备份并且测试过备份可以正常恢复使用!!!

gpg --expert --edit-key 123ABC***

进入编辑界面之后,分步进行以下操作,注意操作过程可能会问 GPG 私钥设置的密码,以及 Yubikey GPG 管理 PIN,默认为 12345678,需要注意的是设置好之后使用卡的时候会询问用户 PIN ,默认为 123456,如果输错次数超过设定次数会导致锁定。下面的操作将主密钥和 2 个子密钥分别存储到了 Yubikey 对应位置。

下面是执行过程记录

gpg --expert --edit-key 123ABC***
gpg (GnuPG/MacGPG2) 2.2.40; 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.

私钥可用。

sec  rsa4096/31***
     创建于:2018-11-20  有效至:永不       可用于:SC
     信任度:绝对        有效性:绝对
ssb  rsa4096/A9***
     创建于:2018-11-20  有效至:永不       可用于:E
ssb  rsa4096/FA***
     创建于:2022-12-19  有效至:永不       可用于:A
[ 绝对 ] (1). *** (***) <***@gmail.com>

gpg> keytocard
真的要移动主密钥吗?(y/N) y
请选择在哪里存储密钥:
  (1) 签名密钥
  (3) 身份验证密钥
您的选择是? 1

sec  rsa4096/31***
     创建于:2018-11-20  有效至:永不       可用于:SC
     信任度:绝对        有效性:绝对
ssb  rsa4096/A9***
     创建于:2018-11-20  有效至:永不       可用于:E
ssb  rsa4096/FA***
     创建于:2022-12-19  有效至:永不       可用于:A
[ 绝对 ] (1). *** (***) <***@gmail.com>

gpg> key 1

sec  rsa4096/31***
     创建于:2018-11-20  有效至:永不       可用于:SC
     信任度:绝对        有效性:绝对
ssb* rsa4096/A9***
     创建于:2018-11-20  有效至:永不       可用于:E
ssb  rsa4096/FA***
     创建于:2022-12-19  有效至:永不       可用于:A
[ 绝对 ] (1). *** (***) <***@gmail.com>

gpg> keytocard
请选择在哪里存储密钥:
  (2) 加密密钥
您的选择是? 2

sec  rsa4096/31***
     创建于:2018-11-20  有效至:永不       可用于:SC
     卡号: A***
     信任度:绝对        有效性:绝对
ssb* rsa4096/A9***
     创建于:2018-11-20  有效至:永不       可用于:E
ssb  rsa4096/FA***
     创建于:2022-12-19  有效至:永不       可用于:A
[ 绝对 ] (1). *** (***) <***@gmail.com>

gpg> key 2

sec  rsa4096/31***
     创建于:2018-11-20  有效至:永不       可用于:SC
     卡号: A***
     信任度:绝对        有效性:绝对
ssb* rsa4096/A9***
     创建于:2018-11-20  有效至:永不       可用于:E
ssb* rsa4096/FA***
     创建于:2022-12-19  有效至:永不       可用于:A
[ 绝对 ] (1). *** (***) <***@gmail.com>

gpg> key 1

sec  rsa4096/31***
     创建于:2018-11-20  有效至:永不       可用于:SC
     卡号: A***
     信任度:绝对        有效性:绝对
ssb  rsa4096/A9***
     创建于:2018-11-20  有效至:永不       可用于:E
ssb* rsa4096/FA***
     创建于:2022-12-19  有效至:永不       可用于:A
[ 绝对 ] (1). *** (***) <***@gmail.com>

gpg> keytocard
请选择在哪里存储密钥:
  (3) 身份验证密钥
您的选择是? 3

sec  rsa4096/31***
     创建于:2018-11-20  有效至:永不       可用于:SC
     卡号: A***
     信任度:绝对        有效性:绝对
ssb  rsa4096/A9***
     创建于:2018-11-20  有效至:永不       可用于:E
     卡号: A***
ssb* rsa4096/FA***
     创建于:2022-12-19  有效至:永不       可用于:A
[ 绝对 ] (1). *** (***) <***@gmail.com>

gpg> quit
要保存变更吗?(y/N) y

再次使用 gpg --card-status 查看卡的状态,可以看到

Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: ****
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: ****
Name of cardholder: [未设定]
Language prefs ...: [未设定]
Salutation .......:
URL of public key : [未设定]
Login data .......: [未设定]
Signature PIN ....: 非强制
Key attributes ...: rsa4096 rsa4096 rsa4096
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
KDF setting ......: off
Signature key ....: **** **** **** **** ****  **** 31** **** **** ****
      created ....: 2018-11-20 03:32:15
Encryption key....: **** **** **** **** ****  **** A9** **** **** ****
      created ....: 2018-11-20 03:32:15
Authentication key: **** **** **** **** ****  **** FA** **** **** ****
      created ....: 2022-12-19 00:07:40
General key info..: pub  rsa4096/31** 2018-11-20 *** (***) <***@gmail.com>
sec>  rsa4096/31***  创建于:2018-11-20  有效至:永不
                                卡号: ****
ssb>  rsa4096/A9***  创建于:2018-11-20  有效至:永不
                                卡号: ****
ssb>  rsa4096/FA***  创建于:2022-12-19  有效至:永不
                                卡号: ****

使用 ykman 管理卡片

可以通过 ykman 管理卡片比如修改重试次数或者重置 GPG 设置。

# 参考文档 https://docs.yubico.com/software/yubikey/tools/ykman/OpenPGP_Commands.html

# 修改密码重试次数
> ykman openpgp access set-retries 10 10 10

# 查看信息
> ykman openpgp info
OpenPGP version:            3.4
Application version:        5.4.3
PIN tries remaining:        10
Reset code tries remaining: 0
Admin PIN tries remaining:  10
Signature PIN:              Always
Touch policies:
  Signature key:      Off
  Encryption key:     Off
  Authentication key: Off
  Attestation key:    Off

# 重置 GPG
# 警告:重置 GPG 功能非常危险,无需任何密码即可通过下面的命令抹掉 3 个 GPG key,如果需要重新配置 key 的时候可以使用,如果没有备份私钥重置之后私钥就没有了。

ykman openpgp reset

通过 GPG 命令行修改相关密码,进入编辑界面后输入 admin 进入管理员模式,再输入 help 可以查看全部支持的子命令。

> gpg --card-edit

gpg/card> admin
管理员命令可用

gpg/card> help
quit           退出此菜单
admin          显示管理员命令
help           显示此帮助
list           列出所有可用数据
name           更改卡持有人的姓名
url            更改拉取密钥的 URL
fetch          根据卡中指定的 URL 获取密钥
login          更改登录名
lang           更改语言偏好
salutation     变更卡片持有人的称呼
cafpr          更改一个 CA 指纹
forcesig       切换签名强制使用 PIN 标志
generate       生成新的密钥
passwd         更改或解锁 PIN 的菜单
verify         验证 PIN 并列出所有数据
unblock        使用重置码解锁 PIN
factory-reset  销毁所有密钥和数据
kdf-setup      针对 PIN 身份验证设置 KDF
key-attr       更改密钥属性

gpg/card> passwd
gpg: 检测到 OpenPGP 卡,号码为 ******

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

您的选择是?

正常默认插卡第一次使用时需要输入用户 PIN,后续和使用存在电脑内的证书一样不需要输入 PIN,也可以额外设置每次操作是否需要触摸确认,一般来说没有必要。

配置备份 Yubikey

如果需要配置备份卡,最简单的方式是使用备份 2 恢复到备份卡片,参考按以下步骤(再次强调操作前备份好私钥!!!)

下面是关联操作前后的记录

# 拔掉卡片 B,重新插入卡片 A 时,key 的卡片序列号依然显示为 B
> gpg --list-secret-keys
/Users/phyng/.gnupg/pubring.kbx
-------------------------------
sec>  rsa4096 2018-11-20 [SC]
      ***31***
      卡片序列号 = B***
uid             [ 绝对 ] *** (***) <***@gmail.com>
ssb>  rsa4096 2018-11-20 [E]
ssb>  rsa4096 2022-12-19 [A]

# 运行此命令同步到卡片 A
> gpg-connect-agent "scd serialno" "learn --force" /bye
S SERIALNO A***
OK
S PROGRESS learncard k 0 0
S PROGRESS learncard k 0 0
S PROGRESS learncard k 0 0
OK
> gpg --list-secret-keys
/Users/phyng/.gnupg/pubring.kbx
-------------------------------
sec>  rsa4096 2018-11-20 [SC]
      ***31***
      卡片序列号 = A***
uid             [ 绝对 ] *** (***) <***@gmail.com>
ssb>  rsa4096 2018-11-20 [E]
ssb>  rsa4096 2022-12-19 [A]

备份和安全性问题

从上述过程可以看出,如果备份 2 存在即使 Yubikey 全部丢失也能正常恢复密钥的使用权,所以可以将备份 2 导出到理想存储或者其他你认为安全的地方。另外需要注意,Yubikey 只保护了存储在其上面的 GPG key 无法被导出,但是可以简单的通过 ykman openpgp reset 无需任何密码重置抹掉 GPG key。

GPG 配置参考资料

使用 GPG 认证 SSH

安装依赖

brew install pinentry-mac

编辑 GPG 配置文件

vim ~/.gnupg/gpg-agent.conf

default-cache-ttl 600
max-cache-ttl 7200

pinentry-program /opt/homebrew/bin/pinentry-mac
enable-ssh-support

编辑 ~/.zshrc~/.bashrc 加入以下内容保证 shell 启动时能够正确加载 gpg-agent ,使用 source ~/.zshrc 刷新或者直接打开新的 shell 测试是否加载成功

export GPG_TTY="$(tty)"
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent

修改 SSH 配置文件 vim ~/.ssh/config 取消之前的 IdentityFile

Host *
  IgnoreUnknown UseKeychain
  UseKeychain yes
  AddKeysToAgent yes
  # IdentityFile ~/.ssh/id_rsa
  UserKnownHostsFile ~/.ssh/known_hosts

使用 ssh-copy-id 复制到远程服务器,测试是否正常

ssh-copy-id user@server.example.com
ssh -v user@server.example.com

或者使用 gpg --export-ssh-key 生成公钥,然后手动复制到远程服务器,在某些场合下可能会更方便

gpg --export-ssh-key 123ABC*** > ~/.ssh/gpg.pub
chmod 600 ~/.ssh/gpg.pub
cat ~/.ssh/gpg.pub

插入备用 Yubikey,通过运行 gpg-connect-agent "scd serialno" "learn --force" /bye 同步后,测试 SSH 联通性即可。

参考资料

其他主题