↓ Skip to main content
  1. Posts/

Linux 桌面下的 Passkey 避坑实践:从手机扫码超时到桌面本地直连

·2441 words·5 mins
Note: This article is available in Chinese only. 本文暂无英文版本。 View original

前几天在电脑上登 ChatGPT,登录页面多了个"使用通行密钥(Passkey)“的选项。以前一直用邮箱加密码,看到这个就顺手点了一下,想试试传说中的免密登录。

然后就开始了一段相当折腾的经历。

30 秒超时和"设备不在附近”
#

点了 Passkey 登录之后,ChatGPT 网页弹出一个二维码,提示"使用手机或平板电脑扫描此二维码"。我拿小米 15 的相机扫了一下——结果相机只识别出一段 fido:/... 开头的原始文本,什么反应都没有。

换用 Chrome 扫码,这回倒是有反应了,手机界面跳到一个提示:“正在连接其他设备…"。然后就卡在这个画面上,一动不动。

等了整整 30 秒。

超时之后,手机弹出一句报错:“设备无法连接,请确保设备相互靠近,然后重试。”

我第一反应是代理的问题——手机和电脑走的科学上网节点不一样,IP 不一致,是不是导致两边匹配不上?又检查了一下手机相机权限,确认没问题。换了几次网络配置,重试了好几轮,结果完全一样:卡 30 秒,超时,报"设备不在附近”。

跨设备扫码到底在干什么
#

后来查了一圈资料,才明白这个跨设备 Passkey 认证的底层机制比我以为的复杂得多。

这里面有两层东西在同时工作。

第一层是蓝牙。跨设备 Passkey 认证强依赖蓝牙低功耗(BLE 4.0+)做物理近场验证——扫码之后,手机和电脑之间并不是走 Wi-Fi 或者互联网直连,而是先通过 BLE 做一次物理距离测距。这一步的目的是确认"手机确实在电脑旁边",防止远程钓鱼攻击。

这也解释了一个我之前一直想不通的事:为什么更老的经典机器(比如 ThinkPad X220)完全玩不转跨设备扫码。X220 那个年代的蓝牙硬件是 2.1 版本,根本不支持 BLE,直接被协议一票否决。不是软件不兼容,是硬件物理层就过不去。

第二层是网络中继。BLE 建立近场信任之后,手机还需要通过 Google Play Services 连接 Google 的跨设备中继服务器(caBLE tunnel server),把认证结果转发回电脑端的浏览器。

问题出在这一步。在国内网络环境下,国行手机底层的 Google Play Services 连接 Google 中继服务器极其脆弱。HyperOS 上的 Google 服务框架本身就是半残状态,再叠加网络波动,这条链路随时可能断流。30 秒正好是系统默认的硬超时——不是"快连上了但差一点",而是压根没连通,跑满了整个超时窗口。

整条链路拆开看就是:手机扫码 → BLE 近场握手 → Google Play Services → Google 中继服务器 → 电脑端浏览器。任何一环断了都会失败,而在国内环境下,后半段几乎是必断的。

Passkey 其实不需要手机
#

在这上面浪费了不少时间之后,我退一步重新想了想:为什么我默认觉得 Passkey 就得用手机?

仔细回忆了一下,这个印象大概来自两个方向。一是 Apple 和 Google 早期推广 Passkey 时,宣传材料里全是手机解锁、指纹按一下就登录的画面,给人一种"Passkey = 手机生物识别"的强烈暗示。二是 Linux 桌面确实长期缺少系统级的生物识别基础设施——没有 Windows Hello 那样的统一框架,也没有 macOS 的 Touch ID 集成,所以直觉上觉得桌面端"不具备条件"。

但实际上,Passkey 的本质是一对公私钥,私钥存在本地、由本地验证器保护,跟手机没有绑定关系。所谓"验证器"可以是手机的指纹模块,也可以是桌面端的密码管理器——只要它能安全地存储私钥、在需要时完成本地身份验证(指纹、PIN 码、设备密码,都行),就是一个合格的 Platform Authenticator。

现代桌面端 Chrome 内置的 Google 密码管理器就是这样一个验证器。它可以直接在本地生成和保存 Passkey,用一个 6 位 PIN 码做本地解密保护。整个过程不需要手机、不需要蓝牙、不需要中继服务器。Bitwarden 的浏览器扩展也支持同样的功能。

说白了,之前那个跨设备扫码流程是"手机作为验证器,电脑作为请求方"的模式。而我真正需要的是"电脑自己既是请求方也是验证器"的模式——根本不用绕一圈。

在 Manjaro 上的实操
#

搞清楚原理之后,操作本身反而很简单。

Chrome + Google 密码管理器
#

在 ChatGPT 的设置页面找到 Security → Passkeys,点击"创建通行密钥"。这时候 Chrome 会弹出一个对话框,问你要把密钥保存到哪里。选择"Google 密码管理器"(不是"使用其他设备"——那又会回到扫码流程)。

第一次使用时,Chrome 会要求设置一个 6 位数字 PIN 码。这个 PIN 码的作用是在本地加密保护你的私钥——密钥本身存储在 Chrome 的本地 profile 里,PIN 码只在本机解密时使用,不会上传到任何地方。

设置完成后,以后登录 ChatGPT 时选择"使用通行密钥",Chrome 弹出一个小窗口让你输入 6 位 PIN 码,输完直接登入。无痕模式下也能用,因为 PIN 码解锁的是 Chrome profile 级别的密钥存储,不依赖 cookie 或 session。

Bitwarden 浏览器扩展
#

如果你用 Bitwarden 管理密码,它的浏览器扩展从 2024 年开始也支持作为 Passkey 验证器。创建 Passkey 时,Bitwarden 扩展会自动弹出拦截对话框,密钥保存在你的 Bitwarden vault 里。登录时 Bitwarden 自动填充,用你的 vault 主密码或生物识别解锁。

两种方案的核心逻辑一样:把私钥存在本地(或你自己的密码管理器里),用 PIN 码或主密码做本地身份验证,彻底绕开手机、蓝牙和 Google 中继服务器。

写在最后
#

现在的体验和之前完全是两回事。登录 ChatGPT(或者其他支持 Passkey 的服务),点"使用通行密钥",键盘敲 6 位 PIN 码,1 秒进去。不用掏手机,不用等蓝牙,不用祈祷 Google 中继服务器别断。

回头看,我在跨设备扫码上浪费的时间,本质上是被"Passkey 必须用手机"这个错误假设绊住了。在国内网络环境 + Linux 桌面这个组合下,跨设备扫码几乎是一条走不通的路——BLE 近场验证加上 Google 中继服务器,链路太长、太脆弱。与其修这条路,不如直接走桌面本地验证器这条近路。

给同样在 Linux 桌面上折腾 Passkey 的人一个建议:别在跨设备扫码上死磕,直接用 Chrome 密码管理器或者 Bitwarden 在本地创建密钥。整个过程不到两分钟,之后每次登录都是纯键盘操作。

Related

从 RVO 到 Guaranteed Copy Elision:C++ 返回值优化的演进

·4193 words·9 mins
一道我答错的题 # 最近做了一个 Modern C++ 水平测试,碰到这样一道题: 1#include <utility> 2 3struct Widget { 4 Widget() = default; 5 Widget(const Widget&) = delete; 6 Widget(Widget&&) = delete; 7}; 8 9Widget createA() { return Widget{}; } 10 11Widget createB() { 12 Widget w; 13 return w; 14} 15 16Widget createC() { 17 Widget w; 18 return std::move(w); 19} 问:在 C++17 下,哪些函数能通过编译?

从倒排索引到 BPE:高性能系统为什么总在避免扫描全集

·2540 words·6 mins
最近在系统学习 Search 和 Information Retrieval 的知识。在看倒排索引(Inverted Index)的时候,我越看越觉得这个结构非常眼熟。直到我想起前段时间写 CS336 作业里 BPE(Byte-Pair Encoding)分词算法的优化逻辑,我才突然意识到:原来这些看似完全不同领域的系统,底层其实都在共享同一种 pattern。