用一对钥匙替掉密码
passkey(通行密钥)不是「更安全的密码」,而是彻底换了模型——密码是两端都存的共享秘密(会被钓走、撞库、泄库),passkey 是一对非对称密钥:私钥被锁在设备的 authenticator 里永不离开,服务器只存公钥;登录靠对服务端发来的
challenge 做一次签名来证明「我手里有私钥」。底层是 FIDO2 = WebAuthn(浏览器 JS API navigator.credentials)+ CTAP2(浏览器↔认证器协议)。
本页每节都直接调浏览器原生 PublicKeyCredential / navigator.credentials——能力检测是当场跑出来的真实结果,「真机尝试」会触发本机 Touch ID / Windows Hello(localhost 是 secure context,WebAuthn 可用)。四节:两种模型的对照 →
注册一把新钥匙 → 三个正交的旋钮 → 用钥匙登录。
1 · 共享秘密模型与非对称密钥模型
密码的根本问题不在「不够长」,而在它是共享秘密(shared secret):同一个值用户与服务器两端都要存。于是它能被钓鱼页面骗走、能在 A 站泄露后拿去对 B 站撞库、服务器一旦被脱库就整批泄露。passkey 换了一套数学:非对称密钥对——私钥锁在设备里永不外传,服务器只拿到公钥,就算泄库也没有可冒用的秘密。
1.1 · 共享秘密 vs 非对称密钥
抗钓鱼是「天生」的,不靠用户警惕。 每个 passkey 在创建时就绑定了 rp.id(一个域名)。浏览器只允许 rp.id 是当前 origin 可注册后缀的凭证参与——所以 login.example.com 用得了 example.com 的凭证,而钓鱼站
examp1e.com 的 JS 根本调不出它(WebAuthn Level 3 另有 Related Origin Requests 这一受控例外,让同一 RP ID 服务于若干注册域;Chrome 151 的 getClientCapabilities() 实测已报 relatedOrigins: true,核对于
2026-08)。密码做不到这点:再像的假站,用户仍可能手动输入密码。
1.2 · FIDO / WebAuthn / CTAP 是什么关系
这几个词常被混用,实为一摞分层标准,由 FIDO Alliance(Fast IDentity Online)与 W3C 共同制定。「passkey」是这套技术面向用户的品牌名:
| 层 | 名字 | 管什么 |
|---|---|---|
| 伞 | FIDO2 | 总称 = WebAuthn + CTAP2。passkey 是 FIDO2 可发现凭证 (discoverable credential) 的用户向叫法,按是否跨设备同步分 synced 与 device-bound 两种——安全钥上放的通常是后者。 |
| 网页 ↔ 浏览器 | WebAuthn | W3C 的浏览器 JS API:navigator.credentials.create() / .get() 与 PublicKeyCredential。本系列讲的正是它。 |
| 浏览器 ↔ 认证器 | CTAP2 | Client to Authenticator Protocol:浏览器怎么和外接安全钥 / 手机(USB·NFC·BLE)对话。网页层看不到,但「扫码用手机登录电脑」靠它。 |
1.3 · 当前设备现在支持吗(当场检测)
下面四项都是真的调浏览器 API 跑出来的。WebAuthn 要求 secure context(HTTPS 或 localhost);isUserVerifyingPlatformAuthenticatorAvailable() 报告本机有没有内建认证器(Touch ID / Windows Hello);isConditionalMediationAvailable()
决定能不能做表单自动填充式登录。
1.4 · 跨平台支持矩阵
passkey 已是各大平台默认能力,差异主要在「能不能跨设备同步」。数据概览自 passkeys.dev(FIDO Alliance 维护):
| 平台 | 创建 passkey | 云端同步 | 当 cross-device 认证器(扫码) |
|---|---|---|---|
| Apple(iOS 16+ / macOS 13+) | ✓ | iCloud Keychain | ✓ |
| Android(9+ / Play services) | ✓ | Google Password Manager | ✓ |
| Windows(10/11 · Hello) | ✓ | 11 起逐步同步 | ✓ |
| Chrome / Edge / Safari / Firefox | ✓ | 借底层 OS / 密码管理器 | ✓(各家进度并不同步,以下方实测徽标与 passkeys.dev 为准,核对于 2026-08) |
| 硬件安全钥(YubiKey 等) | ✓ | ✗(不同步,绑在钥上) | 本身就是 cross-platform |
1.5 · 在 Chrome 里管理已有 passkey
想看 / 删本机存了哪些 passkey,Chrome 的入口层级较深(得先用 passkey 登录过一次,管理页才会出现):
地址栏直接输入 chrome://settings/passkeys 也能到。手机上则在系统设置里:iOS =「密码」、Android =「Google 密码管理工具」。删掉本机 passkey 不会注销服务器侧的公钥——那是两份独立的东西,服务器侧要去各站「安全设置」里单独移除。反过来,服务端可以用 Signal API
主动通知认证器清理已失效的凭证(signalUnknownCredential / signalAllAcceptedCredentials / signalCurrentUserDetails,Chrome 151 实测均可用,核对于 2026-08)。
2 · 注册一把新钥匙
注册(规范里叫 registration ceremony;attestation 指认证器对「这把密钥出自何种认证器」的声明,是仪式的产物之一,也可以取 none)做的事:让 authenticator 当场生成一对新密钥,把公钥连同一个新 credential id
交回服务器存档,私钥留在设备里。入口是 navigator.credentials.create({ publicKey })——关键全在 publicKey 这个 PublicKeyCredentialCreationOptions 里。
2.1 · 一次注册握手
2.2 · 拼一份 creation options(改开关看 JSON)
下面几个开关实时改写 publicKey。把鼠标移到某个字段说明上,JSON 里对应那行会点亮:
三个旋钮 authenticatorSelection 这节先用默认值。 它里面的 authenticatorAttachment / residentKey / userVerification 是三件互相独立的事,单独成一节讲——见 三个正交的旋钮。
2.3 · 真机尝试:在这台机器上真注册一把
下方按钮会用上述选项真实调用 create()——本机有 Touch ID / Windows Hello 时会弹出生物识别。rp.id 取当前域名,challenge 用 crypto.getRandomValues 现场生成。成功后凭证 id 会被存进 localStorage,用钥匙登录一节能直接拿去登录。
为什么 challenge / user.id 是 BufferSource 而不是字符串? WebAuthn 选项里凡是「二进制数据」(challenge、id)都用 ArrayBuffer / Uint8Array。服务端通常发 base64url 字符串,前端要先 decode 成
BufferSource 再塞进选项——PublicKeyCredential.parseCreationOptionsFromJSON() 能代劳这层转换(与 parseRequestOptionsFromJSON()、PublicKeyCredential.prototype.toJSON() 同批加入,Chrome 151 实测三者齐备,核对于 2026-08)。
3 · 三个最容易混淆的旋钮
注册时 authenticatorSelection 里有三个字段,初学者几乎都会把它们当成「一件事的不同说法」。它们互相独立、各自调节——回答的是三个不同问题:认证器在哪、凭证存哪、要不要验人。三个旋钮可任意组合,每种组合对应一种登录体验。
3.1 · authenticatorAttachment——认证器在哪
控制让用户用「什么位置」的认证器。注意它不决定验不验人,也不决定凭证存哪:
| 取值 | 含义 | 例子 |
|---|---|---|
platform |
绑在本机的内建认证器,不可拔。 | Touch ID / Face ID / Windows Hello / Android 指纹 |
cross-platform |
可移动、能接入其他设备的认证器。 | USB / NFC 安全钥(YubiKey)、扫码借用的另一台手机 |
| (不填) | 两者都允许,把选择权交给用户。 | 大多数面向公众的网站不填 |
3.2 · residentKey / discoverable credential——凭证存哪
决定凭证是不是 discoverable credential(可发现凭证,旧称 resident key)——即把凭证本体存进 authenticator、并且能在不被告知 id 的情况下被列举出来。它直接决定登录能不能免输用户名:
| 取值 | 凭证存在哪 | 对登录的影响 |
|---|---|---|
required |
存进 authenticator(占一个槽位)。 | 可发现 → 登录时 allowCredentials 可留空,authenticator 自己列出账号 → usernameless。passkey 的典型选择。 |
preferred |
能存就存(看 authenticator)。 | 尽量做成可发现,设备不支持则退回不可发现。 |
discouraged |
不存本体,只在服务器存「加密包」(server-side credential)。 | 不可发现 → 登录前服务器必须按用户名查出 credential id,放进 allowCredentials 递给浏览器。 |
「可发现」到底发现了什么? discoverable credential 在 authenticator 里连带存了 rp.id 与 user 信息,所以浏览器能向它查询「本站存了哪些账号」并列出供用户选择——这就是为什么能不先报用户名就登录。代价是它占用 authenticator
的存储槽(硬件密钥的槽位有限)。
3.3 · userVerification——要不要验人(安全等级 LoA / AAL)
控制 authenticator 要不要额外证明「现在按的人就是设备主人」(生物识别 / PIN)。它是安全等级的旋钮——决定这次认证算「单因子(只证明握有设备)」还是「双因子(握有设备 + 通过验证)」,和凭证存哪完全无关:
| 取值 | 要求 | 得到的保证 |
|---|---|---|
required |
必须验人(指纹 / 面容 / PIN),不通过就失败。 | 双因子:掌握设备 + 验证本人。passkey 场景的常用取值。 |
preferred |
能验就验,不能验也放行。 | 视设备而定(默认值)。 |
discouraged |
RP 不要求验人,只需 user presence。 | 是否真的验人由认证器决定——苹果平台认证器的生物识别关不掉,此档下 authenticatorData 的 UV 位照样置 1,要读该位才算数。 |
正交性一句话:「验不验人(userVerification)」属于安全等级 / AAL;「凭证存不存得进设备(residentKey)」属于存储与可发现性;「认证器在本机还是外接(authenticatorAttachment)」属于物理位置。三件事可以任意搭配——上面三个旋钮就是用于验证这一点。
3.4 · 几种典型组合
| 场景 | attachment | residentKey | userVerification |
|---|---|---|---|
| 现代 passkey(本机免密登录) | platform |
required |
required |
| 面向公众、不限设备 | (不填) | preferred |
preferred |
| 老式 U2F 二次因子(密码之外再加一把安全钥) | cross-platform |
discouraged |
discouraged |
| 高保证(企业 / 金融) | cross-platform |
required |
required |
4 · 用钥匙登录
登录(规范里叫 authentication ceremony;assertion 指它产出的那个 AuthenticatorAssertionResponse)做的事:服务器发一个新 challenge,authenticator
用已有私钥给它签个名,服务器用注册时存的公钥验签。验的字节串是 authenticatorData 与 SHA-256(clientDataJSON) 的拼接,服务端还要逐项核对 clientDataJSON 里的 challenge 与 origin、authenticatorData
里的 rpIdHash 与 UP / UV 标志位,并按策略处理 signCount 的回退。入口是 navigator.credentials.get({ publicKey })——全程没有任何秘密被发送,只发一个签名。
4.1 · 一次登录握手
4.2 · 拼一份 request options
各字段的说明与 JSON 中对应的行一一对照:
allowCredentials 留空 = usernameless 登录的关键。 留空时浏览器向 authenticator 查询本站已存的 discoverable credential,直接列给用户挑——用户不用先输用户名。填了列表则是「先确定账号、再验证持有」的流程(凭证可以是 non-discoverable 的 server-side credential)。
旧设备的限制: 部分老 authenticator 不支持空 allowCredentials,直接抛出 Use of an empty 'allowCredentials' list is not supported on this device。想兼容这类设备就得回落到「先收用户名 → 服务器查出 id → 填进 allowCredentials」的传统流程,或用 isConditionalMediationAvailable() 等检测做能力分支。
4.3 · 真机尝试:用注册一节创建的钥匙登录
本节的登录演示依赖注册一节先建出一把凭证(凭证 id 存进 localStorage);随后的登录会触发本机生物识别,真实调用 get() 并验出一个 signature。allowCredentials 选「空」则改走 discoverable 流程。
4.4 · Conditional UI:把 passkey 接进输入框自动填充
体验最顺畅的登录不需要点任何按钮:页面一加载就以 mediation:'conditional' 在后台挂一个 get(),配合输入框上的 autocomplete="username webauthn",用户点输入框时系统下拉里直接列出 passkey,选了就登录。它不会主动弹模态框,没有可用凭证时悄无声息:
4.5 · 跨设备:hybrid(扫码用手机登录电脑)
电脑上没存 passkey 时,可借手机里的那把:浏览器弹一个二维码,手机扫码后通过蓝牙就近确认身份——这就是 hybrid transport(旧称 caBLE),底层走 CTAP2。代码侧只要在 get() 里给 hints: ['hybrid'] 把它提到优先,或在
allowCredentials 的条目里标 transports: ['hybrid']。私钥始终不离开手机,电脑只拿到一次签名。
相关链接
-
Web Authentication: An API for accessing Public Key Credentials
W3C
WebAuthn Level 3 规范:
PublicKeyCredential接口、create / get 的全部选项(authenticatorSelection、attestation、hints…)与算法的权威定义。 -
MDN · Web Authentication API
developer.mozilla.org
WebAuthn 总览:注册 / 登录两条流程、discoverable credential、autofill UI 的概念图与
navigator.credentials.create()/.get()的整体用法。 -
MDN · PublicKeyCredentialCreationOptions
developer.mozilla.org
对应「注册」「三个旋钮」两节:
rp/user/challenge/pubKeyCredParams/authenticatorSelection/attestation/excludeCredentials逐字段说明与兼容性。 - Can I use · Web Authentication API caniuse.com WebAuthn 的实时浏览器支持矩阵(逐版本细分),与本系列「当场检测」「跨平台支持」两节对照。
-
Create a passkey for passwordless logins
web.dev
对应「注册」一节:服务端发选项 → 前端
create()→ 回传公钥的完整握手,含excludeCredentials防重复注册的用法。 -
Sign in with a passkey through form autofill
web.dev
对应「登录」一节:Conditional UI / form autofill ——
mediation:'conditional'配合autocomplete="username webauthn"把 passkey 接进原生输入框建议。 -
Passkey form autofill (Conditional UI)
developer.chrome.com
Chrome 侧的 Conditional UI 落地细节与
isConditionalMediationAvailable()检测。 -
Discoverable credentials deep dive
web.dev / FIDO
对应「三个旋钮」一节:discoverable credential(resident key)到底存了什么、为何能 usernameless,以及
residentKey三档取值。 - Passkey device support passkeys.dev 各 OS / 浏览器 / authenticator 的 passkey 支持矩阵(本系列「支持」表的数据来源);站点 passkeys.dev 由 FIDO Alliance 维护。
- Passkeys demo Google Google 官方可交互 demo:真实注册与登录的完整流程,可与本系列的「真机尝试」对照。
- Passwords suck. Can passkeys replace them? kerkour.com 对应「密码的缺陷」一节:从工程视角讲密码的系统性缺陷,以及 passkey 在抗钓鱼 / 抗泄库上的根本不同与现实阻力。