← 文章 / 编程开发
Hacker News 1小时前 · 2026-11-11 04:00:40 · 8 阅读

我删除了所有SSH密钥:基于YubiKey、TPM与Secure Enclave的硬件密钥管理

自 ed25519-sk 密钥支持 YubiKey 以来,我几乎第一时间就开始用了。不过由于一些特定的使用场景(说实话也因为 YubiKey 挺贵的),我手上一直留着几把传统的磁盘密钥,迟早要处理掉。

这篇文章记录了我如何利用各种安全隔离硬件(YubiKey、TPM、Apple Secure Enclave)来存放和加密 SSH 密钥,从而实现磁盘上不留任何未加密密钥的目标。

首先——设置 passphrase 不就够了吗?

大概率是够的。我并不是说这篇文章介绍的方法是使用 SSH 密钥的唯一正确方式。设置 passphrase 后,密钥会用 bcrypt 和 AES 加密,即使磁盘上的密钥文件泄露,也能保持受保护状态。

而且只要 ssh-agent 正常工作,每个会话只需输入一次 passphrase,之后解密后的密钥会保存在内存中。

所以对大多数威胁模型来说,这种“传统”方法应该没问题。但我还是想转向硬件密钥,主要是图个安心和满足感。

关于硬件驻留密钥

我说的“硬件驻留”密钥,是指使用安全隔离硬件或专用设备(如 YubiKeys)来存储或保护密钥材料的密钥。

理论上这样做的好处是:使用密钥必须要有生成该 SSH 密钥的那台硬件。用这种方式生成的密钥,没有任何机制可以让它被泄露或窃取。即使磁盘上存在某些密钥材料,没有对应硬件也无济于事。

目标

我的目标是:

  • 完全改用硬件驻留密钥
  • 尽量用原生方式实现,能不用额外软件就不用
  • 在各种真实场景中测试,比如在 LXC 中运行,或以非交互方式挂载 SSHFS 服务器

硬件

我打算尝试三种不同的硬件方案,应该能满足我的全部需求:

  • 方案一:YubiKey
  • 方案二:TPM
  • 方案三:Apple Secure Enclave

方案一:YubiKey

使用 YubiKey(第 5 代及更新型号)时,可直接将 SSH 密钥注册到设备中,命令使用 ed25519-sk(其中 -sk 代表“Security Key”)。除 OpenSSH 本身外,无需安装其他软件。

要在 YubiKey 中存储新的 SSH 密钥,插入设备并运行以下命令:

ssh-keygen -t ed25519-sk -O resident -O application="ssh:yournamehere" -f ~/.ssh/id_ed25519-sk

命令中设置的 application 标签是任意的,你可以将其设为任何容易记忆的字符串,以便在同一个 YubiKey 上创建多个密钥时进行区分。

如果已安装 ykman,即可查看设备中已注册的凭据列表:

$ ykman fido credentials list
Enter your PIN: 
Credential ID  RP ID             Username  Display name
0f41fb61...    ssh:yournamehere  openssh   openssh

如果 ykman 提示尚未在 YubiKey 上设置 FIDO PIN,可通过 ykman fido access change-pin 完成设置。

需要注意的是,在 YubiKey 中注册或使用 SSH 密钥并不需要 ykman,但它能简化设备管理操作。

密钥创建完成后,可从磁盘获取公钥:

$ cat ~/.ssh/id_ed25519-sk.pub 
sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIA9B+2GGCkQvBzpa4WobQhfC9YU5Su3pKXBmVLSXEqfRAAAAEHNzaDp5b3VybmFtZWhlcmU= alice@hostname

该公钥可像普通 SSH 公钥一样使用——可添加到主机的 authorized_keys 文件,通过 ssh-copy-id 复制,或粘贴到依赖 SSH 的服务(如代码托管平台)中。

磁盘上还会出现 ~/.ssh/id_ed25519-sk 文件(无 .pub 后缀)。尽管该文件看似私钥,实则并不包含密钥材料。它只是一个句柄,用于在硬件设备上定位相应密钥。

为配置该密钥用于身份验证,可像普通密钥一样通过 ssh-add 注册,或直接在 ~/.ssh/config 中指定:

Host blog.infected.systems
    IdentityFile ~/.ssh/id_ed25519-sk

设置完成后,即可像使用普通密钥一样通过 SSH 连接目标主机。执行 SSH 命令时,系统会要求触碰 YubiKey 以确认存在。

无人值守使用 YubiKey

使用上述默认设置创建的 ed25519-sk 密钥时,每次使用密钥都需要物理触摸 YubiKey 来确认你在场。

保持这个默认行为通常是个好主意,但如果你有特定场景需要无人值守地使用密钥,可以按如下方式生成一个 no-touch-required 密钥:

ssh-keygen -t ed25519-sk -O resident -O no-touch-required -O application="ssh:nameyourkey" -f ~/.ssh/id_ed25519-sk

使用 no-touch-required 密钥时,虽然恶意攻击者即使拿到宿主机 root 权限也无法导出密钥本身,但只要有访问权限就能直接使用密钥,无需任何在场检测或人工交互。所以在这样做之前,请务必确认它符合你的风险承受能力。

要用 no-touch-required 密钥对远程服务器进行无人值守认证,需要在目标主机的 authorized_keys 文件中对应条目前加上 no-touch-required,否则 OpenSSH 的默认安全设置会拒绝该密钥。

例如:

$ cat ~/.ssh/authorized_keys 
no-touch-required sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIA9B+2GGCkQvBzpa4WobQhfC9YU5Su3pKXBmVLSXEqfRAAAAEHNzaDp5b3VybmFtZWhlcmU= alice@hostname

方法二:TPM

YubiKey 很好用,但价格不便宜。如果能用主机上已有的 TPM 来保护密钥材料,那就更方便了。事实证明,这是可行的!

不过这需要额外安装软件,好在它是一个非常精简的 Golang 包——ssh-tpm-agent。可以直接用 Go 从 GitHub 安装,Ubuntu 已原生打包,我也正在为 Fedora 做打包。

要创建一个带 PIN 的 TPM 密钥,运行:

ssh-tpm-keygen -N "your-pin"

生成过程会询问密钥的存放位置。之后就和其他方式一样,我们会得到一个带 .pub 文件的密钥对,可以用在 authorized_keys 中,或按需复制到别处。

要使用基于 TPM 的密钥,需要调用由 ssh-tpm-agent 包提供的、支持 TPM 的 SSH agent。如果安装的是原生 Golang 包而非发行版集成的版本,可以按以下方式启用 agent 的 systemd socket 单元:

ssh-tpm-agent --install-user-units && systemctl --user enable --now ssh-tpm-agent.socket

为了确保 shell 会话中运行的 SSH 进程能找到 agent 的 socket,请将以下内容添加到 .bashrc 或你的 shell 等价配置文件中:

export SSH_AUTH_SOCK="$(ssh-tpm-agent --print-socket)"

同样地,我们现在可以像使用传统磁盘密钥一样,用这个密钥向服务器进行身份验证。

无人值守的 TPM 使用

如果你希望该密钥在无人值守模式下可用,或供不使用完整 shell 的服务(如 systemd mount 单元)使用,可以创建一个无需 PIN 的密钥(在 ssh-tpm-keygen 命令中传入 -N ""),并在 ~/.ssh/config 中直接指定 socket 路径,而不是依赖 SSH_AUTH_SOCK(但这要求 socket 地址是可预测的,即你的 UID 是稳定的):

Host blog.infected.systems
    IdentityAgent /run/user/1000/ssh-tpm-agent.sock
    IdentityFile ~/.ssh/id_ecdsa

请注意,ssh-tpm-agent 并不检查记录系统启动状态(如固件版本等)的 TPM 平台配置寄存器(PCR)值。因此,硬件、固件或操作系统的变更不会导致系统拒绝解封密钥(这与 BitLocker 等产品不同)。因此,在决定使用免 PIN 密钥之前,请确保这符合你的风险模型。

关于 TPM 密钥的注意事项

你会注意到,此时保存在磁盘上的私钥文件使用了 .tpm 扩展名。这里涉及 TPM 方法的一个重要注意事项——与 YubiKey 方案不同,私钥材料实际上确实存储在磁盘上。这是因为 TPM 设备本身不支持注册 SSH 密钥。其工作原理是:私钥材料以加密形式存储在磁盘上,而 TPM 用于解密它。

这仍然是一种绑定设备的凭据——即使密钥文件泄露或被窃取,没有 TPM 设备也无法使用。但与 -sk 这类驻留设备(device-resident)的凭据相比,二者还是有一个值得注意的区别。

方法三:Apple Secure Enclave

从 macOS Sequoia 开始,macOS 内置的 Smart Card Auth 系统已经能够原生支持将 SSH 密钥作为身份凭据。

我们可以这样生成一个密钥:

sc_auth create-ctk-identity -l "ssh-labelyourkey" -k p-256-ne -t bio

上面各参数的含义:

  • -l 给密钥打标签。这里建议起一个独特且易识别的名字,因为之后需要在磁盘上据此找到对应的密钥句柄文件。
  • -k p-256-ne 创建一个不可导出(-ne)的密钥。这一点很重要,能确保密钥真正驻留在安全隔区中。
  • -t bio 要求使用 TouchID 才能调用该密钥。

接着可以列出这个密钥:

% sc_auth list-ctk-identities

Key Type Public Key Hash                          Prot Label            Common Name      Email Address Valid To          Valid 
p-256-ne B15235464F87EC7C8DEE89BFA06399FFD6678E99 bio  ssh-labelyourkey ssh-labelyourkey               04/11/2027, 09:29 YES

要把一个句柄导出到磁盘,以便 OpenSSH 使用安全隔区中的密钥,可以执行:

(cd ~/.ssh && ssh-keygen -w /usr/lib/ssh-keychain.dylib -K -N "")

导出过程中如果提示输入 pin,直接留空即可,这里用不到它。执行后会在 ~/.ssh 目录下生成一个公钥文件和一个私钥句柄文件。

注意,上面的命令会导出所有已录入 Secure Enclave 的 SSH 密钥的句柄(如果需要,可以重复上述步骤创建多个密钥)。我没找到只导出单个密钥句柄的办法,尽管 Apple 的文档说可以通过 KEYCHAIN_CERTIFICATES 实现。最稳妥的做法是创建密钥时用 -l 参数取一个清晰的标签名,这样密钥句柄导出到磁盘后就很容易找到。

拿到公钥和句柄文件后,后续流程就和往常一样——把公钥复制到远程主机的 authorized_keys 文件里,或者添加到你偏好的任何服务中。

要使用 Secure Enclave 中的 SSH 密钥,需要在 Shell 中设置 SSH_SK_PROVIDER。可以将其加入 ~/.zshrc:

export SSH_SK_PROVIDER=/usr/lib/ssh-keychain.dylib

重启 Shell 后,密钥即可像其他密钥一样正常工作。不过你可能需要手动指定密钥路径,因为在我所处的情况下,macOS 的 SSH agent 未能自动发现该密钥:

Host blog.infected.systems
    IdentityFile ~/.ssh/id_ecdsa_sk_rk_ssh-labelyourkey

让 Mac 以“合盖模式”运行

如果你让 Mac 以苹果所称的“合盖模式”(即笔记本电脑合上盖子)运行,初始的 create-ctk-identity 命令会失败并抛出如下错误:

Error: Error Domain=com.apple.LocalAuthentication Code=-4 "Touch ID is not available in closed clamshell mode." UserInfo={BiometryType=1, Subcode=10, NSDebugDescription=Touch ID is not available in closed clamshell mode., NSLocalizedDescription=Authentication canceled.}

遗憾的是,使用 -t bio 创建的密钥在生成或使用时必须调用完整的 Touch ID 功能——这与解锁 Safari Keychain 或使用 Apple Pay 不同,后者在合盖模式下可以回退到用户密码验证。

也有选项使用 -t none 创建完全禁用存在性确认的密钥。这与我之前在讨论无人值守使用 YubiKey 时提到的风险相同,你需要自行判断这是否符合你的风控模型。

如果像我一样,几乎全程让机器处于合盖模式,这里有几个仍能保留存在性检测的替代方案:

最后的话

综合运用这三种方法,我终于把所有环境里的本地密钥都淘汰掉了。

希望这篇文章里的信息对大家有帮助!这些内容网上其实都能找到,我只是为了自己方便整理到了一处。

关于硬件密钥,主要要记住的一点是:密钥确实存在于硬件里。硬件故障、丢失或更换都可能导致密钥无法使用。所以最好至少准备多个密钥。别忘了,authorized_keys 里想放多少个密钥都可以,做好冗余才是上策。

原始来源: Hacker News

评论 (0)