1466 字
7 分钟
用手机指纹批准 sudo —— 自建 PAM 认证工具 FingerKey

在Agent请求sudo的时候,我之前一直让他使用pkexec,但是这样每天都要重复几十次的操作。密码冗长、容易输错,在 sudo -S 脚本里处理密码管道也需要格外谨慎。我一直希望有一个像手机解锁一样流畅的方案:手机在手边,指纹一按,命令即放行。

于是我写了 FingerKey:一个 PAM 认证模块 + 本地 daemon + Android app。安装之后,sudopkexec、SSH 登录,乃至任何经过 PAM 的认证,都会把请求推送到手机,用 BiometricPrompt 弹出指纹验证,批准则放行,拒绝则回退到密码。整个过程不依赖任何云服务。

7emotions
/
fingerkey
Waiting for api.github.com...
00K
0K
0K
Waiting...

动机:为什么要再写一个认证工具#

Linux 桌面上想免密码提权,现有的方案我都不太满意:

  • 硬件密钥(YubiKey 一类)需要额外购买、随身携带,遗失后恢复成本高;
  • fprintd 依赖笔记本内置的指纹传感器,桌面台式机大多不具备,驱动兼容性也参差不齐;
  • 云端方案 意味着认证请求要离开内网、经过厂商服务器,这是我难以接受的。

手机几乎是人人都有、随身携带的设备,指纹识别技术也已经相当成熟。它和电脑通常处于同一个局域网。把”批准”这个动作放到手机上,等于免费得到了一台”第二因子”设备,无需购买任何新硬件。

工作原理:一条请求的旅程#

FingerKey 由三个部分组成:

组件语言作用
fingerkeydGo本地 daemon,持有待批准会话、签发 nonce、校验签名
pam_fingerkey.soCPAM 认证模块,创建会话并等待手机决策
FingerKey appFlutter扫码配对、钉住证书指纹、弹出指纹验证

三个组件之间的连接关系:

PAM

unix socket

unix socket

TLS / mDNS

pkexec / sudo

pam_fingerkey.so

fingerkeyd daemon

fingerkey CLI

FingerKey app

一次 sudo 的完整流程如下:

手机 appdaemon (fingerkeyd)PAM 模块手机 appdaemon (fingerkeyd)PAM 模块32 字节随机 nonce + user/service/tty创建会话 (root-only unix socket)推送 pending (TLS 4443)BiometricPrompt 指纹验证decision (Ed25519 签名)校验签名decision-result成功 / 失败

PAM 配置中,auth sufficient pam_fingerkey.so 这一行放在 @include common-auth 之前sufficient 的语义是:批准即跳过后续认证;其他任何结果(拒绝、超时、手机不可达)都会自动回退到正常的密码流程。因此 FingerKey 永远不会把系统锁死。

安全设计:凭什么信任手机#

这是整个项目里我投入最多精力的部分。核心原则是:不信任网络,只信任密码学

证书指纹 pinning,而不是 CA。 daemon 持有一张自签名的 ECDSA P-256 证书,手机在配对时记录这张证书的 SHA-256 指纹(小写十六进制)。此后每次连接,手机都会拒绝任何指纹不符的服务器。没有 CA 可以被攻破,也没有公网 DNS 可以被劫持——局域网内不持有私钥的机器无法冒充 daemon。

决策使用 Ed25519 签名,且不可重放。 手机签名的字节序列是:

"phone-fprint-auth/v1" || 0x00 || action || 0x00
  || u8len(user) || user || u8len(service) || service || u8len(tty) || tty
  || nonce

nonce 是一次性的 32 字节随机数,会话 60 秒后过期,且用户/服务/终端上下文都被绑定在签名中。因此即使攻击者截获了某个批准报文,也无法用它重放给另一个请求。

密码永不传输。 认证的三个环节——PAM 模块、daemon、手机——都不会接触你的密码。daemon 上只保存公钥,手机私钥留在手机本地。即便 daemon 被攻破,也无法获得任何能签出”批准”的凭据。

默认快速回退。 如果没有已配对的手机、或手机未连接,daemon 直接返回 503,PAM 立即回退到密码提示,而不是等待 60 秒。这条设计从一开始就确定了:宁可回退到密码,也不把系统锁死。

几个有意思的取舍#

  • 不依赖任何云。 电脑和手机通过局域网 TCP/TLS 直连,服务发现使用 mDNS(_phonefprint._tcp),由 daemon 内置,无需 Avahi 之类的额外依赖。整个系统没有出网流量。
  • 用 PAM 而非 hook sudo。 PAM 是 Linux 认证的统一入口,一个模块即可覆盖 sudopkexecsuloginsshd、桌面登录管理器等所有 PAM 服务,无需为每个工具单独打补丁。
  • daemon 运行在非 root 用户下。 它以 phonefprint 系统用户运行(无 home、nologin),unix socket 为 root-only 的 0700,只有 root 或 setuid-root 的 PAM 模块能够创建会话。

现状与用法#

项目已经开源(MIT),首个版本 v0.1.0 已发布。CI 使用 GitHub Actions:推送一个 v* 的 tag,即自动编译 Linux 二进制和 Android APK,并发布到 Release 页面。

安装非常直接:

# 从源码装(Ubuntu/Debian)
sudo ./scripts/install.sh

# 打印配对二维码
sudo fingerkey pair-qr my-phone

# 手机装 app-release.apk,扫码配对,然后随便跑个 sudo 试试
sudo true

配对之后,每次提权手机都会弹出指纹验证框,显示请求来自哪个用户、哪个服务、哪个终端。批准则放行,拒绝或超时则回退到密码。

结语#

这个项目的意义在于,它把三个截然不同的技术栈结合在了一起:C 编写的 PAM 模块、Go 编写的并发 daemon、Flutter 编写的安卓 app,中间通过一条自定义的 TLS 帧协议衔接。如果它对你有用,欢迎 star、试用、提交 issue;如果你对某个设计细节感兴趣,仓库里的 README 和 threat model 有详细的说明,代码也配备了完整的单元测试和一套无需真机的端到端测试脚本。

用手机指纹批准 sudo —— 自建 PAM 认证工具 FingerKey
https://lorenzofeng.top/posts/fingerkey/
作者
Lorenzo Feng
发布于
2026-09-11
许可协议
CC BY-NC-SA 4.0