Yubikey使用总结
Yubikey介绍
官方是这样介绍Yubikey的:
A YubiKey is a small hardware device that offers two-factor authentication with a simple touch of a button. YubiKeys are built strong enough for the largest enterprises, while remaining simple enough for anyone to use. The YubiKey NEO offers both contact (USB) and contactless (NFC, MIFARE) communications. YubiKeys can support FIDO U2F, Yubico-OTP, OATH-OTP, OATH-HOTP, OATH-TOTP, OpenPGP, and PIV, and one security key can support an unlimited number of applications without the need for drivers, client software, or batteries.
U2F
Yubikey的U2F功能开箱即用。
目前支持U2F的浏览器有Chrome, Firefox, Opera。推荐使用Chrome; Firefox需要在浏览器输入about:config
然后查找u2f双击这一行启用; Opera没用过不知道是不是需要设置才能使用。
据我所知,目前支持U2F的网站/服务有 :
Google
Facebook
Dropbox
Github
Gitlab
Bitbucket
Fastmail
Lastpass
Dashlane
Keeper
Bitfinex.com
Salesforce
只要根据网站提示开启二次验证(2FA)并绑定你的key, 下次登录账户时,你首先需要像往常一样输入用户名和密码,然后根据提示用你的key进行二次验证。这样即使你的帐号密码被别人知道了,只要key在你手里,那么你的帐号依然是安全的。
OTP
OTP 功能开箱即用 一次一密,需要联网认证,官方有提供认证服务器,当然也可以自己搭建认证服务器。
- OTP:
KEY_ID+AES(AES_KEY, SECRET, COUNT++)
即生成的密码包含明文的KEY_ID和对称加密的SECRET和计数器。第一次使用前需要把KEY_ID,AES_KEY,SECRET提交至验证服务器(Yubico提供或者自己搭建),之后应用程序每次通过服务器验证密码的可靠性(解码后SECRET对应、COUNT增大(防止重放攻击))。 - Static: 静态密码。顾名思义,每次生成固定的一串密码。
- Challenge-Response:
HMAC(SECRET, INPUT)
即可以通过HID接口给定一个输入,输入HMAC的计算结果。输入需要本地代码实现。 - HOTP:
HMAC(SECRET, COUNTER++)
算法与Challenge-Response类似,然而使用累加计数器代替了输入,并且HTOP是一个标准协议,许多网站和设备都兼容该标准。
每个Yubikey都有两个slot,出厂时默认OTP配置在slot 1,短按操作即可触发OTP认证; slot 2可配置 静态密码
Challenge-Response
HOTP
中的一种,长按(2-5s)可触发。
注意yubikey的短按和长按不是指纹识别。
PGP
PGP (Pretty Good Privacy)可能是世界上最优秀的非对称加密工具,公私钥体系真正避免了对称加密会带了的密钥泄漏的问题。而且基于去中心的模型,绕过了 CA,让每个人都能接触到 WOT (Web of Trust)。而 GPG (GNU Privacy Guard)则是 PGP 的开源 GNU 实现。
我们需要把3个PGP子密钥放到key里并使用,实现一定的安全性。
3个子密钥分别是:
S (Signing)
E (Encryption)
A (Authentication)
分别实现签名、加密、认证功能。建议主密钥只用来生成子密钥使用,生成子密钥后,请先备份好所有的私钥;然后将3个子密钥导入yubikey。从安全角度讲,最好直接在yubikey里生成子密钥,因为yubikey里的密钥是取不出的。但是,凡是都有万一,万一你的yubikey丢了…… 所以,事先离线备份下私钥。
如何生成密钥对?以linux为例
生成密钥对
$gpg2 --full-generate-key
gpg (GnuPG) 2.2.4; Copyright (C) 2017 Free Software Foundation, Inc.
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 (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
Your selection? 1
## 这里选择默认的RSA密钥对
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
## 我们选择RSA 4096 bit 如果你用的是NEO,请选择2048 bit
Requested keysize is 4096 bits
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) 1y
## 有效期 这个随意
Key expires at Sat 12 Jan 2019 09:15:37 PM CST
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name: linux
Email address: linux@linuxorg.com
Comment:
You selected this USER-ID:
"linux <linux@linuxorg.com>"
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: key 00EFA8162571FB59 marked as ultimately trusted
gpg: revocation certificate stored as '/home/linux/.gnupg/openpgp-revocs.d/FC65EF983F06878DCD0B626D00EFA8162571FB59.rev'
public and secret key created and signed.
pub rsa4096 2018-01-12 [SC] [expires: 2019-01-12]
FC65EF983F06878DCD0B626D00EFA8162571FB59
uid linux <linux@linuxorg.com>
sub rsa4096 2018-01-12 [E] [expires: 2019-01-12]
## 创建过程结束
## 上面生成了加密子密钥,标识为E;我们开始生成其他的两个密钥
$gpg2 --expert --edit-key 2571FB59
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: next trustdb check due at 2019-01-12
sec rsa4096/00EFA8162571FB59
created: 2018-01-12 expires: 2019-01-12 usage: SC
trust: ultimate validity: ultimate
ssb rsa4096/51EFF717B45316A7
created: 2018-01-12 expires: 2019-01-12 usage: E
[ultimate] (1). linux <linux@linuxorg.com>
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
Your selection? 4
## 选择算法 这里仍然选择RSA
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
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) 1y
Key expires at Sat 12 Jan 2019 09:23:41 PM CST
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 rsa4096/00EFA8162571FB59
created: 2018-01-12 expires: 2019-01-12 usage: SC
trust: ultimate validity: ultimate
ssb rsa4096/51EFF717B45316A7
created: 2018-01-12 expires: 2019-01-12 usage: E
ssb rsa4096/7FD895A29DBC18B5
created: 2018-01-12 expires: 2019-01-12 usage: S
[ultimate] (1). linux <linux@linuxorg.com>
## 此时一个子密钥是E标识 另一个是S标识 下面继续生成A标识
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
Your selection? 8
Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions: Sign Encrypt
(S) Toggle the sign capability
(E) Toggle the encrypt capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? s
Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions: Encrypt
(S) Toggle the sign capability
(E) Toggle the encrypt capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? e
Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions:
(S) Toggle the sign capability
(E) Toggle the encrypt capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? a
## 以上是取消 S E 标识,添加 A 标识
Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions: Authenticate
(S) Toggle the sign capability
(E) Toggle the encrypt capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? q
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
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) 1y
Key expires at Sat 12 Jan 2019 09:25:34 PM CST
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 rsa4096/00EFA8162571FB59
created: 2018-01-12 expires: 2019-01-12 usage: SC
trust: ultimate validity: ultimate
ssb rsa4096/51EFF717B45316A7
created: 2018-01-12 expires: 2019-01-12 usage: E
ssb rsa4096/7FD895A29DBC18B5
created: 2018-01-12 expires: 2019-01-12 usage: S
ssb rsa4096/C24EC8048C9A9DC4
created: 2018-01-12 expires: 2019-01-12 usage: A
[ultimate] (1). linux <linux@linuxorg.com>
gpg> save
## 至此,我们需要的3个子密钥全部生成完成。
## 主密钥指纹 00EFA8162571FB59
## 加密子密钥指纹 51EFF717B45316A7
## 签名子密钥指纹 7FD895A29DBC18B5
## 认证子密钥指纹 C24EC8048C9A9DC4
## 我们来看下我们的密钥环
$gpg2 --list-keys
/home/linux/.gnupg/pubring.kbx
--------------------------------
pub rsa4096 2018-01-12 [SC] [expires: 2019-01-12]
FC65EF983F06878DCD0B626D00EFA8162571FB59
uid [ultimate] linux <linux@linuxorg.com>
sub rsa4096 2018-01-12 [E] [expires: 2019-01-12]
sub rsa4096 2018-01-12 [S] [expires: 2019-01-12]
sub rsa4096 2018-01-12 [A] [expires: 2019-01-12]
## 看的更清楚一点
$gpg2 --list-keys --keyid-format LONG
/home/linux/.gnupg/pubring.kbx
--------------------------------
pub rsa4096/00EFA8162571FB59 2018-01-12 [SC]
FC65EF983F06878DCD0B626D00EFA8162571FB59
uid [ultimate] linux <linux@linuxorg.com>
sub rsa4096/51EFF717B45316A7 2018-01-12 [E] [expires: 2019-01-12]
sub rsa4096/7FD895A29DBC18B5 2018-01-12 [S] [expires: 2019-01-12]
sub rsa4096/C24EC8048C9A9DC4 2018-01-12 [A] [expires: 2019-01-12]
## 这里有个问题,我的主密钥是1年有效期,难不成每年都来一套?
$ gpg2 --expert --edit-key 2571FB59
sec rsa4096/00EFA8162571FB59
created: 2018-01-12 expires: 2019-01-12 usage: SC
trust: ultimate validity: ultimate
ssb rsa4096/51EFF717B45316A7
created: 2018-01-12 expires: 2019-01-12 usage: E
ssb rsa4096/7FD895A29DBC18B5
created: 2018-01-12 expires: 2019-01-12 usage: S
ssb rsa4096/C24EC8048C9A9DC4
created: 2018-01-12 expires: 2019-01-12 usage: A
[ultimate] (1). linux <linux@linuxorg.com>
gpg> expire
Changing expiration time for the primary key.
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
sec rsa4096/00EFA8162571FB59
created: 2018-01-12 expires: never usage: SC
trust: ultimate validity: ultimate
ssb rsa4096/51EFF717B45316A7
created: 2018-01-12 expires: 2019-01-12 usage: E
ssb rsa4096/7FD895A29DBC18B5
created: 2018-01-12 expires: 2019-01-12 usage: S
ssb rsa4096/C24EC8048C9A9DC4
created: 2018-01-12 expires: 2019-01-12 usage: A
[ultimate] (1). linux <linux@linuxorg.com>
gpg> save
## 好了,这样主密钥永不过期。每年只需要更新子密钥即可。
PGP加固(optional)
编辑或者添加 ~/.gnupg/gpg.conf
文件
auto-key-locate keyserver
#keyserver hkps://hkps.pool.sks-keyservers.net
#keyserver-options no-honor-keyserver-url
#keyserver-options ca-cert-file=/etc/sks-keyservers.netCA.pem
#keyserver-options no-honor-keyserver-url
#keyserver-options debug
#keyserver-options verbose
personal-cipher-preferences AES256 AES192 AES CAST5
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
cert-digest-algo SHA512
s2k-cipher-algo AES256
s2k-digest-algo SHA512
charset utf-8
fixed-list-mode
no-comments
no-emit-version
keyid-format 0xlong
list-options show-uid-validity
verify-options show-uid-validity
with-fingerprint
use-agent
require-cross-certification
备份私钥
$gpg2 --armor --output public.asc --export 2571fb59
## 导出主密钥公钥
$gpg2 --armor --output private.asc --export-secret-keys 2571fb59
## 导出主密钥私钥 需要提供密码
$gpg2 --armor --output private-subkeys.asc --export-secret-subkeys 2571fb59
## 导出子密钥私钥 需要提供密码
## 然后把这三个文件放到一个你认为安全的地方,比如加密后放到Dropbox,存到U盘锁在抽屉里...
上传公钥
$gpg2 --keyserver hkp://pgp.mit.edu --send-keys 2571fb59
这样就把公钥信息上传到pgp.mit.edu
,通过交换机制,所有公钥服务器最终都会有你的公钥信息。
设置OPENPGP卡
yubikey 有3个密码 PIN, admin PIN, reset code
默认PIN密码:123456
默认admin PIN密码: 12345678
请牢记
初始化yubikey
$gpg2 --card-edit
gpg/card> admin
Admin commands are allowed
## 进入管理模式
gpg/card> passwd
gpg: OpenPGP card no. D27600012401020100060XXXXXXX0000 detected
1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit
Your selection? 1
PIN changed.
## 首先输入默认PIN密码123456 然后输入两次新密码(不低于6位)
1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit
Your selection? 3
## 同样的方法修改admin PIN(不低于8位)
Your selection? q
## help 命令查看更多可用指令
## 其他信息也可以填写 其中url建议保存公钥信息网址 建议填写
gpg/card> q
错误解析:
gpg2 –import 提示错误 permission denied
解决: echo "export GPG_TTY=$(tty)" >> ~/.bashrc
gpg2 –card-edit 当change PIN时候提示错误
解决:
使用命令 gpg2 --pinentry-mode loopback --card-edit
或者
echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf
导入私钥到yubikey
$gpg2 --edit-key 2571fb59
sec rsa4096/00EFA8162571FB59
created: 2018-01-12 expires: never usage: SC
trust: ultimate validity: ultimate
ssb rsa4096/51EFF717B45316A7
created: 2018-01-12 expires: 2019-01-12 usage: E
ssb rsa4096/7FD895A29DBC18B5
created: 2018-01-12 expires: 2019-01-12 usage: S
ssb rsa4096/C24EC8048C9A9DC4
created: 2018-01-12 expires: 2019-01-12 usage: A
[ultimate] (1). linux <linux@linuxorg.com>
gpg> key 1
sec rsa4096/00EFA8162571FB59
created: 2018-01-12 expires: never usage: SC
trust: ultimate validity: ultimate
ssb* rsa4096/51EFF717B45316A7
created: 2018-01-12 expires: 2019-01-12 usage: E
ssb rsa4096/7FD895A29DBC18B5
created: 2018-01-12 expires: 2019-01-12 usage: S
ssb rsa4096/C24EC8048C9A9DC4
created: 2018-01-12 expires: 2019-01-12 usage: A
[ultimate] (1). linux <linux@linuxorg.com>
## 此时注意看 多了一个* 表示此key被选中
gpg> keytocard
Please select where to store the key:
(2) Encryption key
Your selection? 2
## 导入后 再次执行 key 1 反选
## 依次导入key 2 和 key 3
gpg> save
## 这样就可以了 私钥信息不能提取出来,这就是为什么钥先备份再导入的原因
测试
## 首先删除本地信息
$gpg2 --delete-secret-keys 2571FB59
$gpg2 --delete-keys 2571FB59
## 如果你之前在url字段设置了自己公钥信息地址
$gpg2 --card-edit
gpg/card> fetch
gpg/card> q
## 此时就可以查看到信息
$gpg2 --card-status
Yubikey重置
方法一:
$gpg2 --card-edit
gpg/card> admin
Admin commands are allowed
gpg/card> factory-reset
方法二:
SSH via PGP
可以将PGP公钥信息转换成SSH格式
$gpg2 --export-ssh-key 2571fb59
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC3PSouOM2gALjoHMroM6p/FdH5FmUXju7p5mnGBGVEIRhq9f8GHsoBjzmFw3gkM+tc7bvThmSVM9jFk0E1C5lE//jNRAzm94OWO1g2MBwEBeaNUsa1CEipgxOl5EnRLr+0hAr65TgQ9iVbxJRP/q9l8jx+4/zangzD76Bu0dc4kVtJsoiGvnvtfIjvAqQMzZw1Sbws7EDmbGCa2+93eTjlVoba9zA6lx8DOeRMjZ0fOXstlGxVWj52JA2Y+ZySAQR4FQUOub7rklxNnYg6dckP9cCtJ6x9r8VtHzcpjUy16pmGZIjLFc+5r0wEYbsR2v6xB0G/mf3g+qPNZ3uMlqSYhUNnINMbh1cbVsTqaXQzWtcJP9k4+LaAzglRgV0jSCCQou3yb0MKmMPgUHtYwqeffHQk78CAWbWz3k+uXxFmx0WN++P5jj19p72llm820erfl2lG9zG5AUCyc/BV52FPefyNU3AQRi99+dN5qt2dMOTQ2YhpAzgL7Pj5D5zk3qck7HGrT3uu90mnguPRkAA+MuZivdWapE3lW/a4GXeWYslIEBKcqyHsODt4cC+RAflLE5bTTaWqemaCeYgPGtVjjjfQbZVkKOfdujRsrKM1f4ruujaxYSSzx4ahOsPem76zfJ8sSjgVshoJBBMAvN+7u+tOLqjgv6DtdT6whMjhLw== openpgp:0x8C9A9DC4
上面命令输出的即是SSH格式的PGP认证信息。 将以上信息输出到 ~/.ssh/id_rsa.pub
,然后复制到目标机器的authorized_keys
, 即可利用PGP实现SSH认证。
$gpg2 --export-ssh-key 2571fb59 >> ~/.ssh/id_rsa.pub
$ssh-copy-id [user@]hostname
事实上还是ssh的功能,默认的ssh认证使用的是ssh-agent,这里只不过使用gpg-agent代替ssh-agent完成认证。
要通过gpg-agent实现ssh-agent的功能,还需要编辑两个文件
Shell rc文件 ~/.bashrc
或者 ~/.zshrc
# for GPG
if [ -f "${HOME}/.gpg-agent-info" ]; then
. "${HOME}/.gpg-agent-info"
export GPG_AGENT_INFO
export SSH_AUTH_SOCK
export SSH_AGENT_PID
fi
export GPG_TTY=$(tty)
# for Arch Linux
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpg-connect-agent updatestartuptty /bye
# for linux
# export SSH_AUTH_SOCK="${HOME}/.gnupg/S.gpg-agent.ssh"
# gpgconf --launch gpg-agent
~/.gnugp/gpg-agent.conf
enable-ssh-support
default-cache-ttl 1800
max-cache-ttl 3600
write-env-file
use-standard-socket
之后logoff或者重启机器
PAM模块
PAM (Pluggable Authentication Module)
Arch用户直接从AUR安装pam_u2f
$sudo pacman -S libu2f-host
$yaourt -S libu2f-server pam_u2f
配置:
注册设备
pamu2fcfg -u<username>
这时 Yubikey 上的LED会闪烁提示,轻触 输出格式如下
<username>:<KeyHandle1>,<UserKey1>
将输出的内容放到 ~/.config/Yubico/u2f_keys
编辑需要的PAM模块(sudo为例)
auth sufficient pam_u2f.so cue
加cue参数会打印一行话提示你轻触Yubikey进行验证
附pamu2fcfg用法
OPTIONS
debug
Enables debug output
debug_file
Filename to write debug to, file must exist and be a regular file. STDERR is default
origin=origin
Set the origin for the U2F authentication procedure. If no value is specified, the origin "pam://$HOSTNAME" is used.
appid=appid
Set the application ID for the U2F authentication procedure. If no value is specified, the same value used for origin is taken ("pam://$HOSTNAME" if also origin is not specified).
authfile=file
Set the location of the file that holds the mappings of user names to keyHandles and user keys. The format is username:keyHandle1,public_key1:keyHandle2,public_key2:… the default location of the file is $XDG_CONFIG_HOME/Yubico/u2f_keys. If the environment variable is not set, $HOME/.config/Yubico/u2f_keys is used.
nouserok
Set to enable authentication attempts to succeed even if the user trying to authenticate is not found inside authfile or if authfile is missing/malformed.
openasuser
Setuid to the authenticating user when opening the authfile. Useful when the user’s home is stored on an NFS volume mounted with the root_squash option (which maps root to nobody which will not be able to read the file).
alwaysok
Set to enable all authentication attempts to succeed (aka presentation mode).
max_devices=n_devices
Maximum number of devices allowed per user (default is 24). Devices specified in the authentication file that exceed this value will be ignored.
interactive
Set to prompt a message and wait before testing the presence of a U2F device. Recommended if your device doesn’t have tactile trigger.
[prompt=your prompt here]
Set individual prompt message for interactive mode. Watch the square brackets around this parameter to get spaces correctly recognized by PAM.
manual
Set to drop to a manual console where challenges are printed on screen and response read from standard input. Useful for debugging and SSH sessions without U2F-support from the SSH client/server. If enabled, interactive mode becomes redundant and has no effect.
cue
Set to prompt a message to remind to touch the device.
PIV
To do
Reference:
- https://developers.yubico.com/PGP/Importing_keys.html
- https://www.yubico.com/support/knowledge-base/categories/articles/reset-applet-yubikey/#resetapplet
- https://wiki.archlinux.org/index.php/Yubikey
- https://www.esev.com/blog/post/2015-01-pgp-ssh-key-on-yubikey-neo/
- https://github.com/drduh/YubiKey-Guide
- https://gist.github.com/ageis/14adc308087859e199912b4c79c4aaa4
本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可。转载请注明来源: https://snowfrs.com/yubikey 欢迎对文中引用进行考证,欢迎指出任何不准确和模糊之处。