WSL中如何顺畅地使用GPG和Yubikey这类东西
有时经常需要在WSL2中执行一些GPG操作,比如签名Git提交什么的。如果此时又不幸购买了Yubikey,那么就需要想办法让GPG和插在Windows上的Yubikey对上话。
然而如果你要一旦上网去搜索wsl gpg yubikey,那么你大概就会看到像这样或者那样的文章。
诚然,这些文章写的都很好。我自己以前也是用usbipd把yubikey映射到wsl中。但这样有个问题,你会忘记上一次配置usbipd把yubikey接到linux上了,这很容易导致未预期的错误。
如果你选择去问AI,那么它大概会给你一个奇幻的答案,让你在windows和linux上装一大堆乱七八糟的东西,然后最好的情况是gpg --card-status没有反应。
但是,WSL(Windows Subsystem of Linux)必须得Sub✍️✍️✍️,就像它的名字一样
好了不扯废话了,我来说我现在的解决方案。
我的环境
这是我的fastfetch,即使不完全匹配应该也没大问题
1 | PS C:\Users\stevezmt> uname -a |
我的WSL:
1 | steve@DESKTOP-LLICT9J:~$ uname -a |
我的电脑上安装了Gpg4win,我觉得你们也都应该装一下
我现在的解决方案
就像我文章头图那样:
1 | gpg.exe |
真的,如果想在wsl上下文里面运行任何windows程序的话,后面加个.exe就好了。
你甚至可以直接在 WSL2 终端里输入 notepad.exe 并回车,Windows 记事本就会弹出来
也就是说,如果你想要在wsl里面用gpg签署提交,并且windows的gpg4win也都配置完成了,那你就可以直接:
1 | git config --global gpg.program gpg.exe |
当然类似cls dir这种cmd.exe提供的命令不大跑的起来,不过shutdown.exe是可以的
同理,你甚至可以alias adb="adb.exe",然后wsl和windows调试同一台设备
如果你的工作只是要传递stdin和stdout就可以了的话,用这个就够了。
为何它运作?
我觉得wsl和普通虚拟机相比最大优势不是它快,也不是资源管理器直接管理虚拟磁盘,而是跨操作系统进程操作(Cross-OS Process Interop)
1 |
|
可以看到,它将所有以4d5a开头的文件交给了/init去处理。
那为什么是/init?
/init充当Linux虚拟机与Windows主机间的“跨系统消息总线”。它会检查自己被调用时的“名字”(argv[0]),来知道它现在该干嘛。在这次调用中,它的名字是/init,于是就开始作为binfmt_misc的解释器,尝试启动一个windows程序。
这就是为何wsl内核要定制。
启动新的 Windows 进程/init需要连接到互操作服务器。互操作服务器是特殊的 Linux 进程,充当 Linux 和 Windows 之间的桥梁。它们与 Windows 进程(例如wsl.exe或wslhost.exe)保持安全的通信通道(通过 hvsocket 连接),从而启动 Windows 可执行文件。
在 Linux 系统中,每个会话领导者和每个init实例都有一个关联的互操作服务器,该服务器通过 unix 套接字提供服务/run/WSL。
/init使用$WSL_INTEROP环境变量来确定要连接的服务器。如果未设置该变量,/init会尝试/run/WSL/${pid}_interop使用自身的进程 ID (PID) 连接到服务器。如果连接失败,/init则会尝试连接其父进程的进程 ID,然后继续向上查找,直到找到init 进程。
连接成功后,/init会发送一个LxInitMessageCreateProcess(WSL1)或LxInitMessageCreateProcessUtilityVm(WSL2),然后将该消息转发给关联的 Windows 进程,该进程将启动请求的命令并将其输出转发给/init。^1
之后的操作都在windows上执行了,windows程序根据参数去干活,然后把参数发回去,wsl里面的init拿到退出码和返回后,原样交给fork它的进程,然后就像活都是自己干的一样退出并返回一样的返回值。
所以,一旦涉及跨系统的内存共享或直接内存控制,这套协作机制就无法和 Linux 进程协同了。你也不能把游戏在windows和linux上各装一半,然后试着利用这套机制去运行起来。慢不说,它们在实际运行时就会撞到vm本质上的那层墙。
也不要指望让 Linux 调试器 attach 一个 Windows 进程的内存——何苦呢?
还有什么能传?
不多,除了 stdin/stdout/stderr,/init 这座桥还能传递一些常规的东西,比如:
- 环境变量;很显然,不然你的
gpg.exelinux怎么知道它要处理哪一个elf - 工作目录:Linux 的 /mnt/c/Users/steve 会被转换成 C:\Users\steve 传给 Windows。
- 文件描述符:Linux 进程可以把打开的文件句柄传给 app.exe。比如
git log | gpg.exe --verify,这个管道|是由 Linux 内核的pipefs管理的,/init会把这个管道的一端映射给 Windows 进程。
我以前的解决方案
我以前是“相当于直接把yubikey塞进wsl虚拟机”,这种方式也适合像烧录固件这种最好直接控制usb的程序
Windows 侧
安装 usbipd-win:
在管理员 PowerShell 中执行:
1 | winget install --interactive --exact dorssel.usbipd-win |
WSL2 侧 (以 Ubuntu 为例)
需要安装 USBIP 客户端工具:
1 | sudo apt update |
- 绑定并连接 Yubikey
在 Windows 管理员 PowerShell 中找到 Yubikey:
1 |
|
找到类似 1050:0407 Yubikey 4/5 OTP+U2F+CCID 的行,记下其 BUSID(例如 4-1)。
绑定设备(只需操作一次):
1 | usbipd bind --busid 4-1 |
连接到 WSL:
1 | usbipd attach --wsl --busid 4-1 |
注:连接后,Windows 侧将暂时无法识别该 Yubikey,直到你执行 detach 。
- 在 WSL 中配置 GPG 签名
连接成功后,在 WSL 中执行 lsusb,你应该能看到 Yubikey。
确保智能卡服务运行
1 | sudo apt install scdaemon pcscd |
检查卡片状态:
1 | gpg --card-status |
如果输出了你的 Key 信息(包括 Signature key 的 ID),说明硬件链路已通。
配置 Git 使用该 Key:
获取 Key ID:gpg --list-secret-keys --keyid-format aaaaaaaaaaaaaaaaaaaaa
1 | git config --global user.signingkey 3745872A953198FE |
测试签名 Commit:
1 | git commit -S -m "signed commit via usbipd" |
此时 WSL 内部会调用 scdaemon 访问 USB 硬件。
这么做不仅看运气和环境,而且弹窗也比较麻烦。
但是有时(比如用linux烧固件)只能这么干。
这又是怎么运作的?
这个解释起来稍微简单一点:
诸位肯定都用过usb转发器吧。
它就相当于借助usbipd这个转发器,把你的硬件从windows上拔下来插在usbipd这个延长线上。之后的驱动和操作完全由linux控制。
结语
wsl很可能是microslopMicrosoft这几年为数不多做的还算人事的东西。

