不透明的、可互操作的通行密钥记录
摘要
本文提出了一种基于PHC字符串的不透明、可互操作的通行密钥记录格式,并描述了一个用于处理这些记录的Go API,旨在简化服务器端通行密钥存储和库之间的互操作性。
<p><a href="https://lobste.rs/s/boyu9x/opaque_interoperable_passkey_records">评论</a></p>
查看缓存全文
缓存时间: 2026/07/20 23:30
# 不透明、可互通的通行密钥记录(以及一个 Go API)
来源:https://words.filippo.io/passkey-record/
通行密钥是当前信息安全领域最重要的发展,因为它是应对钓鱼攻击压倒性有效性的唯一原则性解决方案,就像内存安全是应对内存损坏攻击的唯一原则性解决方案一样。不幸的是,在服务器端实现它们可能比使用密码哈希更复杂。部分复杂性不可避免,因为通行密钥需要与浏览器交互才能获得其抗钓鱼属性。然而,另一部分复杂性可以通过定义可互通的通行密钥记录编码来更有效地抽象化。
WebAuthn 规范[定义](https://www.w3.org/TR/webauthn-3/#reg-ceremony-create-credential-record)了一个*凭据记录*作为抽象概念,包含多个组件,如 `type`、`id`、`publicKey`、`backupState`、`transports` 和其他标志。Google [推荐](https://developers.google.com/identity/passkeys/developer-guides/server-registration#store_the_public_key)一个以 Credential ID 为主键的数据库表,包含 `public_key`、`backed_up` 和 `transports` 列。Adam Langley 出色的 [WebAuthn 之旅](https://www.imperialviolet.org/tourofwebauthn/tourofwebauthn.html#the-server-side)同样推荐一个 `cred_id` 主键,以及单独的 `public_key_spki` 和 `backed_up` 列。所有指导都建议使用库来处理 WebAuthn 认证,但这仍然使应用程序面临潜在的非互通数据库模式。将整个认证流程和数据库交互委托给一个库或框架也可能不切实际。
可互通、定义明确的通行密钥记录,使应用程序可以将它们作为不透明字符串处理(就像密码哈希一样),可以成为中间层抽象。c2sp.org/passkey-record (https://github.com/C2SP/C2SP/blob/push-oxkkyrwpqxot/passkey-record.md) 是一个规范提案,它借用了[密码哈希竞赛(PHC)字符串](https://c2sp.org/phc-strings)的语法,并重用现有的认证器数据编码来完成大部分工作。一个记录看起来像这样:
```
$webauthn$v=1$transports=hybrid+internal$<authenticator-data-base64url>
```
有效负载是认证器数据,即凭据记录大多数字段的 CTAP2 CBOR 编码,这已由 WebAuthn [规定](https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data),并包含在 `AuthenticatorAttestationResponse` (https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAttestationResponse) 的 JSON 编码中(即使不使用 attestation,它也是 `navigator.credentials.create()` (https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create) 的返回类型)。传输方式(transports)是唯一缺失的字段,它们作为 PHC 参数存储。
这样,应用程序只负责跟踪哪些通行密钥记录与用户账户关联,这是一项 Web 开发者熟悉的任务,因为它与实现密码认证没有太大区别(只是每个账户有多个通行密钥)。这些不透明字符串可以传递给库来验证登录断言(或生成带有适当 `excludedCredentials` 的注册请求)。通过一个定义明确的互通存储格式,希望将来能够切换通行密钥库(甚至后端语言)而保留凭据数据库。
## 你可能想存储的其他字段
除了通行密钥记录,应用程序可能仍希望存储元数据字段,如用户选择的昵称、创建时间和最后使用时间戳,以便提供美观的通行密钥管理 UI。这些都不需要 WebAuthn 库的特殊处理。唯一的例外是备份状态标志。通行密钥向服务器报告它们是否已备份(例如,备份到 iCloud 钥匙串或 Google 账户),服务器可以利用这个信号建议从账户中移除密码。这个标志在每次登录时都可能改变,而通行密钥记录是不可变的,因此它必须单独存储并在每次登录时更新。我认为对于普通网站来说,这种逻辑被高估了,因为它们无论如何都会继续支持通过电子邮件重置密码。
## 一个潜在的 crypto/passkey API
基于这些通行密钥记录,我起草了一个潜在的 `crypto/passkey` 无状态 Go 包 API (https://godoc-play.exe.xyz/chibklhpf6nn)。
**注册流程**是:
1. 使用已登录(或已识别)的用户详情和任何现有的通行密钥记录调用 `RelyingParty.NewRegistration` (https://godoc-play.exe.xyz/chibklhpf6nn#RelyingParty.NewRegistration)。
2. 将返回的 JSON 传递给 `parseCreationOptionsFromJSON()`,然后再传递给 `navigator.credentials.create()`。
3. 将返回的 JSON 编码的 PublicKeyCredential 传递给 `RelyingParty.Register` (https://godoc-play.exe.xyz/chibklhpf6nn#RelyingParty.Register)。
4. 将返回的通行密钥记录存储在数据库中。
**登录流程**是:
1. 在生成登录页面时调用 `RelyingParty.NewLogin` (https://godoc-play.exe.xyz/chibklhpf6nn#RelyingParty.NewLogin)。
2. 将返回的请求存储在具有短 TTL 的键值缓存中,使用 `RequestID(request)` (https://godoc-play.exe.xyz/chibklhpf6nn#RequestID),并将返回的 JSON 传递给 `parseRequestOptionsFromJSON()`,然后再传递给 `navigator.credentials.get()`。
3. 将返回的 JSON 编码的 PublicKeyCredential 传递给 `Inspect` (https://godoc-play.exe.xyz/chibklhpf6nn#Inspect),并使用返回的 requestID 从键值缓存中检索请求,并使用返回的 userID 从数据库中检索通行密钥记录。
4. 将 JSON PublicKeyCredential、请求和通行密钥记录传递给 `RelyingParty.Login` (https://godoc-play.exe.xyz/chibklhpf6nn#RelyingParty.Login)。
**应用程序负责**:
- 为每个用户关联一个不透明、永久、保护隐私的用户 ID;
- 存储与用户关联的通行密钥记录;
- 缓存由 `RelyingParty.NewLogin` 产生的请求挑战。
该库提供可以直接传递给 `parseCreationOptionsFromJSON()` (https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/parseCreationOptionsFromJSON_static) 和 `parseRequestOptionsFromJSON()` (https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/parseRequestOptionsFromJSON_static) 的 JSON 值,并接受通过对 `PublicKeyCredential` (https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/toJSON) 调用 `JSON.stringify()` 返回的 JSON 值。
这是为可发现凭据流程(即通行密钥,认证器在此流程中存储并向服务器提供用户 ID)优化的,但 `RelyingParty.NewLoginForUser` (https://godoc-play.exe.xyz/chibklhpf6nn#RelyingParty.NewLoginForUser) 方法也可用于第二因素流程或重新认证提示。该 API 同时适用于模态和条件 UI(自动填充)流程。
有几个辅助函数可以从通行密钥记录中提取信息(`AAGUID` (https://godoc-play.exe.xyz/chibklhpf6nn#AAGUID), `BackedUp` (https://godoc-play.exe.xyz/chibklhpf6nn#BackedUp)),以及从 JSON 编码的 PublicKeyCredential 中提取信息(`ResponseBackedUp` (https://godoc-play.exe.xyz/chibklhpf6nn#ResponseBackedUp))。
目前还没有实现;我希望在可能为 Go 1.28 提出提案之前,获得关于[通行密钥记录格式](https://github.com/C2SP/C2SP/blob/push-oxkkyrwpqxot/passkey-record.md)和 [Go API](https://godoc-play.exe.xyz/chibklhpf6nn) 的反馈。
## 关于重复的 Credential ID
这种存储模型无法做到的一件事是确保不同账户不共享具有相同 Credential ID 的通行密钥,而规范说你应该这样做。进行此检查的原因是防止攻击:攻击者通过自己的账户故意注入一个冲突的 Credential ID,使得通过 ID 查找凭据时,你找到错误的公钥或用户 ID。如果你一开始就没有 Credential ID 索引,这种攻击根本不可能发生!索引仅用于缓解因索引存在而引入的攻击。登录尝试会携带用户 ID,如果你用它来查找用户的通行密钥记录以验证登录,那么其他用户是否有具有相同 Credential ID 的通行密钥并不重要,就像两个用户共享一个密码也不重要一样。不要让攻击者决定你的主键,你就不会有主键冲突攻击。
有关更多 Go API 预览,请在 Bluesky 上关注我:@filippo.abyssdomain.expert (https://bsky.app/profile/filippo.abyssdomain.expert) 或在 Mastodon 上关注我:@[email protected] (https://abyssdomain.expert/@filippo)。
## 图片
来自今年 [CENTOPASSI](https://www.centopassi.net/)(一项 GPS 追踪摩托车比赛,需要精心规划,包含 100 个坐标,在三天半内行驶 1700 公里二级公路)的更多内容。这是从阿奎拉省卡斯特尔德尔蒙特(Castel del Monte, AQ)看到的一瞥,从荒芜且仍积雪的坎波因佩拉托雷(Campo Imperatore)爬下来后。从高处俯瞰一座中世纪小镇,树木环绕。远处是树林、丘陵,然后是一座带有雪纹的山峰。天空灰暗多云。
我的工作得到了 [Geomys](https://geomys.org/) 的支持,这是一个由专业 Go 维护者组成的组织,由 [Ava Labs](https://www.avalabs.org/)、[Teleport](https://goteleport.com/)、[Datadog](https://www.datadoghq.com/)、[Tailscale](https://tailscale.com/) 和 [Sentry](https://sentry.io/) 资助。通过我们的保留合同,他们确保我们开源维护工作的可持续性和可靠性,并直接获得我和其他 Geomys 维护者的专业知识。(了解更多请参阅 [Geomys 公告](https://words.filippo.io/geomys)。)
下面是一些来自赞助商的寄语!
**Teleport** — 过去五年,攻击和入侵已经从传统的恶意软件和安全漏洞转向通过社会工程、凭证窃取或钓鱼来识别和破坏有效用户账户和凭证。[Teleport Identity](https://goteleport.com/platform/identity/?utm=filippo) 旨在通过访问监控消除薄弱访问模式,通过访问请求最小化攻击面,并通过强制性访问审查清除未使用的权限。
**Ava Labs** — 我们 [Ava Labs](https://www.avalabs.org/),[AvalancheGo](https://github.com/ava-labs/avalanchego)(与 [Avalanche 网络](https://www.avax.network/) 交互时最广泛使用的客户端)的维护者,相信开源加密协议的可持续维护和开发对于区块链技术的广泛采用至关重要。我们很自豪通过持续赞助 Filippo 和他的团队来支持这项必要且有影响力的工作。
相似文章
Show HN: 使用密钥驱动加密的匿名年龄验证
ONE 是一个以隐私为中心的身份基础设施,它使用密钥驱动加密进行匿名年龄验证和人类证明,允许用户控制他们的数据,而无需暴露个人可识别信息。
Show HN:由 Ory 开发的用 Go 编写的开源 API Key 服务器
Ory Talos 是一个用 Go 编写的开源 API Key 服务器,用于大规模地签发、验证和管理 API 密钥,具有低延迟验证能力,并支持 JWT 和 macaroon 令牌。
匿名凭证:图解入门(第二部分)
图解入门系列的第二部分,介绍 Privacy Pass 和 Google 年龄验证提案等真实世界的匿名凭证系统,重点讲解如何防止凭证克隆,并在不牺牲用户隐私的前提下实现富有表现力的证明。
密码糟糕透了。通行密钥能取代它们吗?
讨论通行密钥作为一种更安全的认证方法取代密码的潜力。
传递通行密钥:无密码认证的新攻击面
这项Unit 42研究披露了三种针对无密码认证的新攻击,利用了谷歌的同步通行密钥生态系统,展示了恶意软件如何接管受通行密钥保护的账户、绕过用户验证并提取私钥。