用一句话解锁 Windows 锁屏:正常说一句 25 秒的话,210 秒后自动进桌面 —— 而锁屏界面一个像素都不改。
English summary: VoiceUnlock is a Windows lock-screen unlocker driven by a speaker-verification (voiceprint) model, not by speech recognition. A resident agent listens only while the workstation is locked; when the voiceprint matches it mints a short-lived unlock ticket, and a Credential Provider inside LogonUI submits the logon credential. The lock screen itself is left completely untouched — no tile, no status text, no theming: with no valid ticket the provider enumerates zero credentials, so Windows renders its own, stock UI.
⚠ 先读免责声明:
DISCLAIMER.md
- 没有后门:不联网、不上传音频、无遥测、无自动更新;
DISCLAIMER.md§1 给了你自己核验的命令。- 但凭据有被解出的风险:它必须替你保管登录密码(DPAPI 用户作用域加密)。 防得住"拿到磁盘文件的人"和"别的用户",防不住已能以你身份运行代码的人、本机管理员, 也防不住离线爆破 —— DPAPI 主密钥由你的 Windows 口令派生,口令越弱越危险。
- 声纹可以被冒用:没有活体检测;在真实麦克风旁播录音、或用语音克隆,仍可能骗过判定。 请把它当"便捷因素",不要当唯一防线。
本软件按"现状"提供、不提供任何担保,使用风险自负;只用于你自己拥有或已获授权的设备。
- ⚠ 免责声明与安全边界
- 它解决什么问题
- 它长什么样(用户视角)
- 前置条件
- 安装
- 日常使用
- 排障
- 紧急关闭与卸载
- 工作原理(完整链路)
- 从源码构建
- 验证与证据
- 隐私与安全
- 已知限制
- 许可与第三方组件
- 目录结构
笔记本 / 台式机在工位或家里,锁了屏要敲一长串密码,很烦;但换成"人脸"又常常要求专用摄像头。 这个项目把声纹当便捷因素:你说一句话就能进桌面,密码框一直都在、随时可用。
两个设计底线,贯穿全部实现:
- 锁屏界面一点不改。 不画磁贴、不加状态文字、不动外观 —— 用户看到的永远是 Windows 自己的登录界面。
- 绝不把用户锁在门外。 自动提交凭据之前,一定先用 Windows 自己验证一次;不对就一次都不提交。
Win+L 锁屏
↓
对着麦克风正常说一句 2~5 秒(不用喊)
↓
2~10 秒后自动回到桌面
- 锁屏上没有任何我们的元素,也不会有提示音 —— 所以请稍等几秒,别连按回车。
- 想手输密码:随时输,
Ctrl+Alt+Del也一直可用。 - 只在"锁屏"这一种场景工作:开机/注销后那个还没有用户会话的登录界面,本项目不参与 (原因见已知限制:那要求把凭据降级成机器级保护,本项目选择不降级)。
| 项 | 要求 |
|---|---|
| 系统 | Windows 10 x64(19041+,本项目在 19045 上实测)。Windows 11 可用但未签名凭据提供者可能不被加载,见已知限制 |
| 账户 | 必须有密码。空密码账户没有可提交的凭据,声纹解锁没有意义 |
| 麦克风 | 任意可用录音设备;虚拟混音总线默认不参与解锁(见隐私与安全) |
| 权限 | 安装需要管理员(要注册锁屏凭据提供者、写 HKLM) |
| 网络 | 完全不需要。程序不联网、不上传任何音频 |
-
从 Releases 下载
VoiceUnlock-Setup-<版本>.exe。 -
在目标机器上运行(安装界面中英文自动)。
-
勾选项:
勾选项 默认 说明 登录时自动启动声纹监听 勾 注册计划任务 + 最高权限,见原理 §3 安装完成后立即设置登录凭据 勾 弹一个窗口要你输 Windows 登录密码 安装完成后立即登记声纹 勾 需要麦克风,按屏幕提示念一句 -
装完立刻核对(30 秒):
& "C:\Program Files\VoiceUnlock\VoiceUnlock.exe" paths
重点看这几行:
credential = ...credential.bin credential = OK (decryptable in this session; user=<你> password length=<n>) profiles = 1 以上 autostart = scheduled task VoiceUnlockAgent (highest privileges) --- CP registration state --- => both keys present - it should appear on the lock screen非中文 Windows 上这些标签是英文(程序按系统语言自动切换输出,避免 437 代码页下中文变乱码)。 想强制:
set VOICEUNLOCK_LANG=zh(或en)。
| 想做的事 | 怎么做 |
|---|---|
| 登记声纹 | 开始菜单「登记声纹」,或 VoiceUnlock.exe enroll(照着屏幕上的句子念) |
| 登记多支麦克风 | VoiceUnlock.exe enroll --all;只登记一支:--device <序号> |
| 查看状态 | 开始菜单「查看状态(排障)」,或 VoiceUnlock.exe paths / agent status |
| 换密码之后 | 必须重新设置凭据:VoiceUnlock.exe setup-password(DPAPI 绑定当前会话) |
声纹登记有几个要点(都踩过坑,写在代码注释里):
- 屏幕会给出要念的句子和倒计时 —— 内容不影响识别(声纹与文本无关),给句子只是为了让你知道"现在该说话、说多久"。
- 登记前会做质检:先判"有没有人在说话"(活跃比例),静音、噪声、几乎没人声都会拒绝建档并说明原因。噪声也能过的那种判据会写出害人的阈值。
- 登记完会按解锁时的判定路径复核一遍,必要时下调阈值。
- 不登记也能用:任何一次成功解锁之后,用到的设备会被自动建档。
第一条命令永远是:
& "C:\Program Files\VoiceUnlock\VoiceUnlock.exe" paths日志(每次判定、分数、阈值、耗时都在里面):
%LOCALAPPDATA%\VoiceUnlock\logs\agent.log
常见症状 → 往哪看:
| 症状 | 看什么 |
|---|---|
| 说了一句话但完全没反应 | agent.log 里有没有 [listen] 电平触发。没有 ⇒ 麦克风没被采到,或 agent 没在锁屏状态听 |
| 声纹通过但锁屏不动 | 有没有 [agent] 常驻判定(...):通过 之后的票据记录;再看 CP 是否注册(cp-status) |
日志里几百行 拒绝对端(映像='?'),收到请求 一条没有 |
agent 不是以最高权限运行 —— 见原理 §3 |
凭据可读性 = ✗ 本会话解不开 |
会话建立后改过密码。在当前登录会话里重跑一次 setup-password |
| 锁屏上出现"未登记自启"之类提示 | 旧版本的 paths 只看 HKCU Run;新版显示的是计划任务 |
| 场景 | 做法 |
|---|---|
| 已经进了桌面、想立刻关掉 | 双击桌面「★ 紧急关闭声纹解锁」(会弹一次 UAC),或管理员运行 VoiceUnlock.exe panic —— 它做三件事:注销凭据提供者、停常驻监听、移除自启 |
| 正常卸载 | 控制面板 → 程序和功能。卸载会先注销凭据提供者再删文件,并清掉凭据与声纹数据 |
| 连桌面都进不去 | WinRE → 命令提示符 → 删掉两处注册项(具体命令随包 使用说明.txt 里有整段可复制) |
卸载顺序不是随便定的:必须先注销凭据提供者,再删 DLL。反了就会变成"注册表里指着一个不存在的 DLL", 那是会把锁屏弄坏的形态。
┌──────────────────────── 用户会话(登录之后) ────────────────────────┐
│ VoiceUnlockAgent.exe(计划任务启动,最高权限) │
│ ├─ 只在【确认锁屏】时才开麦(session.is_locked(),按输入桌面判读) │
│ ├─ 电平触发:每设备自适应门限 = max(该设备近期中位数×6, 绝对下限) │
│ ├─ 录一段 → 声纹 embedding → 与该设备档案比 cos → 过阈值 │
│ └─ 通过 ⇒ 备一张 40 秒有效的"解锁票据"(单次使用) │
│ ↕ 命名管道 \\.\pipe\VoiceUnlock │
│ SDDL 收紧 + 对端映像校验(只认 logonui.exe)│
└──────────────────────────────────────────────────────────────────────┘
↕
┌────────────────── 安全桌面:LogonUI.exe(SYSTEM) ───────────────────┐
│ 我们的 Credential Provider(DLL,随安装包注册) │
│ ├─ 每 200ms ping 问票据 │
│ ├─ ticket=false ⇒ 【枚举 0 个凭据】→ 界面上完全没有我们 │
│ └─ ticket=true ⇒ CredentialsChanged(经 GIT 封送,跨线程回调 STA)│
│ → 凭据出现 → pbAutoLogonWithDefault │
│ → LogonUI 自动提交 → 解锁 │
└──────────────────────────────────────────────────────────────────────┘
凭据提供者的枚举结果直接决定登录界面渲染什么。所以只要在"没有有效票据"时返回 0 个凭据, LogonUI 就没有任何东西可画 —— 我们不需要去隐藏、去适配主题、去猜缩放,Windows 自己的界面就是它自己的样子。
反过来"先画上去再想办法藏"是做不到的:CP 一旦枚举出凭据,LogonUI 就会给它分配磁贴/选中框, 那是系统画的,改不了外观。
这条设计顺带解决了与任何主题登录界面共存的问题:我们不在锁屏上画东西,就没有"适配"这回事。
| 路径 | 适用机器 | CP 做什么 | 实测 |
|---|---|---|---|
| 提交凭据 | 有密码(主场景) | 提交 DPAPI 解出的 Windows 密码 | 已实测自动解锁 |
| 注入回车 | 空密码账户(兼容) | 在安全桌面 SendInput 注入回车 = Windows 自己的"按回车即登录"路径,不提交任何凭据,不会触发账户锁定 |
未在真机实测 |
这是实测出来的产品缺陷(另一台机器上排障得到),不是设计洁癖:
- 管道的对端校验要读客户端进程的映像名,而锁屏那边的客户端是
LogonUI.exe(SYSTEM 身份)。 - 普通权限的 agent 调
OpenProcess会被系统拒绝(err=5)→ 映像名取不到 → 判peer-not-allowed→ 所有锁屏请求都被回绝。 - 症状极具误导性:
agent.log里几百行拒绝对端(映像='?'),而收到请求一条都没有; 用户看到的只是"声纹不灵"。
所以自启是计划任务 VoiceUnlockAgent,RunLevel=HighestAvailable(不弹 UAC),
并且带 IgnoreNew(防两个实例抢管道)、ExecutionTimeLimit=PT0S(默认 72 小时会掐掉常驻进程)、
电池上不停(笔记本拔电源就停服等于功能消失)、失败重启 3 次。
- 每设备一份档案 + 每设备一个阈值:不同麦克风的频响差别很大,共用一个阈值必然误判。
- 本设备命中:直接比阈值,最快(快判 ~3 秒)。
- 跨设备兜底:新插入/未登记的设备走"多段全部通过"的严格路径。
- 自动登记:任何一次成功解锁之后,用到的设备被自动建档,下次它就是"一等公民"。
- 同一物理源的重复计票去重:否则一个混音总线会冒充多个"独立证据"。
- 可信 / 不可信白名单:混音总线、回环、虚拟设备默认不参与解锁(本机最大的安全风险点就在这里)。
推理侧不依赖 torch/funasr:CAM++ 说话人模型导出为 ONNX,只用 onnxruntime + kaldi_native_fbank 跑,
冻结版与源码版的向量一致性有 parity 自检(cos = 1.00000000 才算过)。
自动提交密码之前,一定先用 Windows 自己(LogonUser)真登一次:
- 登录成功 → 才允许提交;
- 登录失败(比如用户改过密码)→ 一次都不提交,并把原因写清楚到日志。
理由很实在:本机账户策略是"10 次密码错锁 10 分钟"。如果程序闷头提交旧密码,
用户会被自己的程序锁在门外。代价是每 30 秒最多一次 LogonUser(几毫秒)。
另外:常驻程序不在、凭据读不出来、票据过期 —— 任何一种情况下 CP 都是"什么凭据都不枚举", 手输密码不受任何影响。
需要:Windows 10/11 x64、Python 3.12、Inno Setup 6、MSVC(编译凭据提供者 DLL)。
git clone <this repo> ; cd voiceunlock
python -m venv venv
venv\Scripts\pip install -r requirements.txt # numpy / onnxruntime / kaldi-native-fbank / soundcard
# 1) 取声纹模型(不随仓库分发)
# a) 从上游下载 CAM++ 权重仓库(iic/speech_campplus_sv_zh-cn_16k-common),
# 把整个目录放到 models\campplus_sv\(里面应能看到 campplus_cn_common.bin)
# b) 用一个【带 torch + funasr 的解释器】导出 ONNX(不是本项目的 venv ——
# export_onnx.py 要 import torch/funasr,只做一次性离线转换):
# <带torch的解释器>\python.exe tools\export_onnx.py
# 产物:models\campplus.onnx(约 27 MB)
# 路径可用环境变量覆盖:VOICEUNLOCK_SV_DIR / VOICEUNLOCK_MODELS
# 2) 编译锁屏凭据提供者(需要 VS 的 VC.Tools 组件 + CMake)
pwsh -File provider\build.ps1
# 产物:provider\build\Release\VoiceCredentialProvider.dll
# 3) 一键打包(冻结 + 自检 + 出安装包)
pwsh -File packaging\build_installer.ps1
# 产物:dist\VoiceUnlock-Setup-<版本>.exe 与免安装形态 dist\VoiceUnlock\打包脚本内置三道硬门槛,任何一道不过就中止:
- 生成源码侧 parity 参考向量;
- PyInstaller 冻结(控制台 exe + 无窗口 agent exe,共享一份
_internal); - 冻结版自检:
parity cos必须1.00000000,且判定逻辑自测通过。
出门前再跑两条自检:
venv\Scripts\python.exe tools\i18n_audit.py --all # 输出语言层五项审计
venv\Scripts\python.exe tools\scan_package_sensitive.py # 包里有没有本机痕迹
tools\scan_package_sensitive.py值得单独说:PyInstaller 的 PYZ 是压缩的, 直接按字节 grep exe 什么都查不到(连源码里的字面量都不在可见字节里), 所以它会把模块解出来、反序列化、遍历字符串常量;另外源码里的#注释不进包, 而 docstring 会进包。这两个坑都真踩过。
这个项目是"证据优先"的:每个结论都对应一条可复跑的命令或日志。
| 项 | 判据 |
|---|---|
| 推理一致性 | 冻结版 parity --ref logs\parity_ref.npy → cos = 1.00000000 |
| 运行时内核 | VoiceUnlock.exe parity / python -m src.selftest → SELFTEST OK |
| 判定逻辑 | VoiceUnlock.exe selftest-decision → DECISION SELFTEST OK(覆盖本设备/跨设备/自动登记三层) |
| 装机 | 安静安装退出码 0;paths 里 CP 两处键 present;计划任务 RunLevel=HighestAvailable |
| 锁屏界面 | 抓图对比:登录界面只有系统自己的账户名 + 密码框 |
| 对端校验修复 | 计划任务启动的 agent + 锁屏 30 秒:收到请求 87 次 / 拒绝对端 0 次(修复前是"几百次拒绝、0 次请求") |
| 卸载干净 | 两个 CP 注册键、计划任务、旧的 HKCU 自启项、用户数据全部清除 |
| 输出语言 | 中文系统逐行零回归;英文系统全 ASCII(437 代码页不乱码)—— tools\i18n_audit.py --all 五项断言 |
完整的风险清单、可自行核验的"无后门"证据、以及责任限制,都在
DISCLAIMER.md。 下面是摘要。
- 不联网、不上传音频。 没有任何网络代码;所有处理在本机。
- 只做声纹,不做语音识别。 你说什么内容不影响结果,也不会有转写。
- 凭据用 Windows 自带的 DPAPI(用户作用域)加密存本机,只有你自己的登录会话能解开; 不会以明文出现在任何文件或日志里。 ⚠ 但这个保护不防已能以你身份运行代码的人、本机管理员/SYSTEM, 也不防离线爆破(主密钥由你的 Windows 口令派生 → 口令越弱越危险)。
- 声纹可以被冒用:没有活体检测;录音回放/语音克隆可能骗过判定。它只是"便捷因素"。
- 声纹是生物特征,泄露不可撤销 —— 别把
profiles.json上传或分享。 - 虚拟设备默认不信任:混音总线/回环设备不参与解锁(防止自建音色 + 混音总线自我欺骗)。
- 常驻 agent 以最高权限运行(原因见上)—— 这是为了让管道的对端校验可用;它只做"听声纹 → 备票据"这一件事。
- 仓库里不包含任何用户数据(凭据、声纹档案、日志)也不包含第三方主题登录界面的文件。
- 只支持"锁屏",不支持"开机/注销后的登录界面"。 这是有意的边界,不是漏做: 锁屏时用户会话还在,常驻 agent 能在该会话里听声纹、并用 DPAPI 用户作用域解开凭据; 而开机/注销后的登录界面还没有任何用户会话 —— 要支持它,就得让一个 SYSTEM 常驻进程 在"无用户会话"的上下文里解密凭据,凭据必须从"只有你的登录会话能解开"降级为 "管理员/SYSTEM 能解开"(还不确定那个上下文能否采到麦克风)。本项目选择不降级: 登录界面就交给 Windows 原样处理(我们的凭据提供者那时拿不到票据,枚举 0 个凭据, 界面上看不到我们)。
- 凭据提供者未做代码签名:Windows 11 对未签名 CP 的加载策略更严,可能不加载; Win10 实测正常。给其他机器用之前请先在目标机器上验一遍锁屏。
- "注入回车"路径未在真机实测(只在有密码的机器上验过提交凭据那条路)。
- 声学验证过的机器有限:作者本机(有真实麦克风)已验证真实语音自动解锁; 虚拟机里可以用"主机侧假麦克风"(往虚拟音频线里灌 wav)做端到端,但那只验证链路,不验证真实声学。
- 登记/解锁需要安静环境;嘈杂环境下会走更保守的判定路径(更慢,但不容易误放行)。
- 仅 Windows:锁屏凭据提供者是 Windows 专有机制。
- 本项目代码:见仓库根
LICENSE。 - 随包分发的声纹模型
campplus.onnx是上游 CAM++ 说话人声纹权重 (iic/speech_campplus_sv_zh-cn_16k-common,版权归阿里巴巴 / DAMO 达摩院,Apache License 2.0) 的格式转换件:由本项目用torch.onnx.export从原始 PyTorch 权重转换而来, 权重数值未做任何改动,没有再训练、微调、剪枝或量化。 为满足 Apache-2.0 §4 的义务,安装包里随附LICENSE-Apache-2.0.txt与THIRD-PARTY-NOTICES.txt; 上游模型仓库自身不发 LICENSE/NOTICE,那份许可正文由本项目补入。 §6 不授予商标权:本项目与阿里巴巴集团无隶属或背书关系。 - 不包含 Shittim Logon(蔚蓝档案主题登录界面)的任何文件 —— 它的授权禁止再分发与二次打包。 本项目的凭据提供者在无票据时枚举 0 个凭据、锁屏上不画任何东西,因此与任何主题登录界面 互不影响、也不需要适配;想用主题界面就自行安装它自己的发行版。
- 仓库不分发上游模型权重:
models\*.onnx在.gitignore里,按上面的步骤自行导出/获取。
src/ 运行时(独立推理内核 + 判定 + 管道 + 凭据 + 安装动作 + 输出语言层)
sv.py CAM++ ONNX 推理(fbank → TDNN → seg_pooling → 192 维 embedding)
verify.py 判定:快判/退回、挑窗、阈值、三层覆盖
agent.py 常驻 agent:锁屏才开麦、电平触发、备票据
win32pipe.py 命名管道服务端(SDDL + 对端映像校验 + 日志限流)
session.py 锁屏判读(以输入桌面名为准)
dpapi.py 凭据的 DPAPI 用户作用域加解密
enroll.py 声纹登记(含质检与判定路径复核)
install.py 凭据提供者注册 / 计划任务自启 / 紧急关闭
i18n.py + i18n_catalog.py 输出语言自动切换(中文系统纯 no-op)
provider/ 锁屏凭据提供者(C++ / COM,在 LogonUI 内加载)
packaging/ PyInstaller spec、Inno Setup 脚本、一键打包、给用户的使用说明
tools/ 构建 / 验证 / 自动化工具
export_onnx.py 把上游 CAM++ 权重导出成 ONNX
i18n_audit.py 输出语言层五项审计
scan_package_sensitive.py 交付前隐私扫描(含"解 PYZ 查字符串常量")
feed_audio.py 往主机虚拟音频线灌 wav(给虚拟机当假麦克风)
声纹解锁-施工方案.md 设计记录:每个决定的理由 + 全部失败路径与反例(最值得读的一份)
全流程说明.md 打包 → 装机 → 凭据 → 登记 → 使用 → 验证 → 应急,逐步带判据
HANDOFF.md 开发/交接记录:已完成与未完成、踩过的坑清单(30+ 条)
设计文档里记了大量失败路径与反例(哪个判据看似合理但会误判、哪个"修复"其实引入了新 bug)。 如果你要改判定阈值、加设备类型、动锁屏交互,先读一遍,能省几轮。