前言

几年前,我开始接触硬件安全密钥,当时使用的 Canokey 它实现了 Yubikey 的绝大部分功能,在 Canokey 签名和认证应用指南 中记录了我的配置过程(该指南同样适用于 Yubikey)。

然而,时隔数年,当我尝试在 Windows 系统上重新配置此流程时,却遭遇了诸多令人困惑的地方。难以排查系统内建的 Win32-OpenSSH 与通过 Scoop 安装的 GnuPG (而非 Gpg4Win )之间,对于 ssh-agentgpg-agent,如何实现管道和代理转发的复杂机制,它们对我来说就像是个「黑盒子」。

confused.webp

经过查阅 很多 一些资料,我重新完成了这个配置过程,本文仍可能存在错误之处,但其核心价值在于记录,确保下次重装系统后我可以快速完成设置和使用。

本文着力于 Win32-OpenSSH 原生 SSH 客户端 的配置,而非 PuTTY、Cygwin、MSYS2 或 WSL2 中的实现。使用 PuTTY 进行代理转发的方案不够简洁高效,我更喜欢简单、原生的实现和处理。此外, win-gpg-agent 项目的维护者已于 2022 年 9 月宣布停止维护,安全考量可能是其中一个原因。

仔细阅读 GnuPG 清单 T3883 和 GitHub #827 上关于此问题的追踪,可以发现跨系统代理配置困难的主要原因可能在于 Windows 的命名管道 (Named Pipes) 与 *nix 系统的套接字 (Socket) 处理机制存在本质区别,最新的 Win32-OpenSSH 是否已经实现了这个原生命名管道的处理呢?我无从了解,但是现在最起码它正常工作了!

pinentry-basic.webp

本文中,我们的处理流程大致如下:

+----------------+
|    YubiKey     |
|  OpenPGP Keys  |
+-------+--------+
        |
        | (GPG Keys)
        v
+------------------------+
|      gpg-agent         |
| (GPG Agent & SSH Proxy)|
+---+--------+--------+--+
    ^        |        ^
    |        |        | (SSH Auth Request)
    |        |        +---------------------+
    |        |                              |
    | (GPG Sign Request)                    v
+---+-----------------------+        +------+-------------------------+
|   GnuPG / Git (Commit -S) |        | Win32-OpenSSH / Git (Push/SSH) |
+---------------------------+        +--------------------------------+

             (PIN/Touch Authentication)
             <------------------------>

环境

先决条件

我们假定 YubiKey 上的 OpenPGP 应用中已生成并写入了 GPG 密钥。同时默认你此前在其他计算机上的相关配置能够正常工作。在新安装的 Windows 系统上,你已通过 Scoop 完成了 gnupggitsudo 的安装。

scoop install git gnupg
scoop install sudo -g

系统信息

systeminfo | findstr /B /C:"OS 名称" /C:"OS 版本"
OS 名称:            Microsoft Windows 11 IoT 企业版 LTSC
OS 版本:            10.0.26100 N/A Build 26100

GnuPG 信息

GnuPG 的版本信息

gpg --version
gpg (GnuPG) 2.5.16
libgcrypt 1.11.2
Copyright (C) 2025 g10 Code GmbH
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: C:\Scoop\apps\gnupg\current\home
Supported algorithms:
Pubkey: RSA, Kyber, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

配置文件路径,我的在 C 盘的 Scoop 目录下

gpgconf --list-dirs
sysconfdir:C%3a\Scoop\apps\gnupg\current\etc\gnupg
bindir:C%3a\Scoop\apps\gnupg\current\bin
libexecdir:C%3a\Scoop\apps\gnupg\current\bin
libdir:C%3a\Scoop\apps\gnupg\current\lib\gnupg
datadir:C%3a\Scoop\apps\gnupg\current\share\gnupg
localedir:C%3a\Scoop\apps\gnupg\current\share\locale
socketdir:C%3a\Scoop\apps\gnupg\current\gnupg
dirmngr-socket:C%3a\Scoop\apps\gnupg\current\gnupg\S.dirmngr
keyboxd-socket:C%3a\Scoop\apps\gnupg\current\gnupg\S.keyboxd
agent-ssh-socket:C%3a\Scoop\apps\gnupg\current\gnupg\S.gpg-agent.ssh
agent-extra-socket:C%3a\Scoop\apps\gnupg\current\gnupg\S.gpg-agent.extra
agent-browser-socket:C%3a\Scoop\apps\gnupg\current\gnupg\S.gpg-agent.browser
agent-socket:C%3a\Scoop\apps\gnupg\current\gnupg\S.gpg-agent
homedir:C%3a\Scoop\apps\gnupg\current\home

OpenSSH 信息

使用的是 Windows 默认的 Win32-OpenSSH 客户端

ssh -V
OpenSSH_for_Windows_9.5p2, LibreSSL 3.8.2

配置流程

禁用 SSH Agent 服务

现在的 Win32-OpenSSH 已经有 ssh-agent 服务了,但是它会干扰 gpg-agent 的处理进程,确保它已停止运行

sudo Get-Service ssh-agent

Status   Name               DisplayName
------   ----               -----------
Stopped  ssh-agent          OpenSSH Authentication Agent

否则请停止其服务并禁止自动运行

sudo Stop-Service ssh-agent
sudo Set-Service -StartupType Disabled ssh-agent

识别智能卡

这一步并非强制要求,但根据 YubiKey-Guide 中的 说明 ,现在 Windows 中可能存在一些虚拟智能卡(例如 Windows Hello 相关的读卡器)。有可能干扰 到 scdaemon 的工作,导致它无法正确读取 YubiKey 智能卡设备。

查看设备信息:

Get-PnpDevice -Class SoftwareDevice | Where-Object {$_.FriendlyName -like "*YubiKey*"} | Select-Object -ExpandProperty FriendlyName

会输出 Yubikey 完整的设备名称,比如:

Yubico YubiKey OTP+FIDO+CCID 0

C:\Scoop\apps\gnupg\current\home 下新建一个 scdaemon.conf 写入以下内容:

reader-port Yubico YubiKey OTP+FIDO+CCID 0

插入 Yubikey,查看智能卡状态:

# reload gpg config
gpgconf --launch gpg-agent

# check gpg card
gpg --card-status

导入公钥

从服务器或者本地文件导入我们的 PGP 公钥,我在公钥服务器导入:

curl -fsSL https://pgp.dejavu.moe/ | gpg --import -

信任自己的公钥:

gpg --expert --edit-key [your email/keyid/fingerprint]

gpg> trust

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y

配置 Git 签名

查看用于签名子密钥的 [KeyID of signature subkey]

gpg --list-keys --keyid-format LONG
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
\Scoop\apps\gnupg\current\home\pubring.kbx
--------------------------------------------
pub   ed25519/[Master KeyID] 2021-05-31 [C]
      [Master Key Fingerprint]
uid                 [ultimate] Dejavu Moe <admin@dejavu.moe>
sub   ed25519/[KeyID of signature subkey]      2022-09-26 [S] [expires: 2035-09-23]
sub   cv25519/[KeyID of encryption subkey]     2021-07-18 [E] [expires: 2035-07-15]
sub   ed25519/[KeyID of authentication subkey] 2022-01-09 [A] [expires: 2035-01-06]

添加 Git 设置:

git config --global user.name "YourName"
git config --global user.email "[email protected]"
git config --global user.signingkey "[KeyID of signature subkey]"
git config --global commit.gpgsign true
git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"

配置 GPG 程序路径:

# find gpg.exe
which gpg.exe
C:\Scoop\apps\gnupg\current\bin\gpg.exe

# then
git config --global gpg.program "C:/Scoop/apps/gnupg/current/bin/gpg.exe"

验证 GPG 签名

新建一个测试文本文件

echo "This is a test message to be signed." > test_sign.txt

尝试签名

gpg --local-user [KeyID/Email] --detach-sign test_sign.txt

验证签名

gpg --verify test_sign.txt.sig test_sign.txt
gpg: Signature made 10/23/25 12:31:54
gpg:                using EDDSA key 2E21425AC287228857E027D5223E2D2495F24232
gpg:                issuer "[email protected]"
gpg: Good signature from "Dejavu Moe <[email protected]>" [ultimate]

配置 SSH 代理

查看用于身份认证子密钥的 keygrip,进入 C:\Scoop\apps\gnupg\current\home 目录,将 [keygrip for authentication subkey] 的内容写入 sshcontrol 文件:

# list all keygrip
gpg -K --with-keygrip
ssb>  ed25519 2022-01-09 [A] [expires: 2035-01-06]
      Keygrip = [keygrip for authentication subkey]

同样在这个目录下写入 gpg-agent.conf 文件:

# enable SSH agent
enable-ssh-support
# enable optimized support for Win32-OpenSSH client
enable-win32-openssh-support
# set the default cache time for PIN codes (10 minutes)
default-cache-ttl 600
# set the maximum cache time for PIN codes (2 hours)
max-cache-ttl 7200
# pinentry dir
pinentry-program C:/Scoop/apps/gnupg/current/bin/pinentry-basic.exe

重新启动相关进程:

gpg-connect-agent killagent /bye
gpg-connect-agent /bye
gpgconf --launch gpg-agent

尝试导出 SSH 公钥:

ssh-add -L

应该会得到正确的 SSH 公钥格式,将其添加到 GitHub 或 VPS 上的信任 SSH 公钥列表里,比如:

ssh-ed25519 AAAA.../... cardno:12_345_987

验证 GPG SSH

测试下 GitHub SSH 访问

ssh -T git@github.com

应该没问题了

Hi DejavuMoe! debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
You've successfully authenticated, but GitHub does not provide shell access.

最后

还是想吐槽下这个 pinentry-basic.exe,每次输入 PIN 解锁 Yubikey 时,都要用鼠标去输入框点一下,它无法自动获取输入焦点,暂时只能做到在 GPG 签名时可以直接在终端里输入 PIN。

pinentry-tty-sign.webp

嗯,现在一切就已经完成了。然而,目前对于 SSH 认证的 PIN 输入问题仍未找到满意的解决方案,类似 pinentry-tty 的思路在当前环境下(指 Windows 原生环境)尚未成功实现,算是一个小痛点吧。

参考资料: