3. WebAuthn核心概念

WebAuthn核心概念 学习卡片

要真正把WebAuthn用进支付强认证与风控体系,必须先厘清它的六个核心概念:公钥凭证、挑战-响应、RP ID、用户句柄、认证器,以及客户端与依赖方的角色分工。这六个概念不是孤立术语,而是一条从注册到认证、从设备到服务端的完整信任链。理解它们之间的约束关系,才能避免“接入了WebAuthn却依然被钓鱼”的典型失败。

一、核心原理:一次注册,长期持有私钥

WebAuthn的本质是基于非对称密钥的凭证模型。注册阶段,认证器在本地生成一对密钥,私钥永不离开认证器,公钥则交给依赖方(Relying Party,RP)保存。此后每次认证,RP发出随机挑战,认证器用私钥签名,RP用保存的公钥验签。整个过程没有共享密钥在网络中传输,因此天然抗钓鱼、抗重放。

这里的关键是公钥凭证(Public Key Credential)。它不是一个简单的公钥字符串,而是一个包含凭证ID、公钥、签名计数器、AAGUID、传输方式等字段的结构体。RP保存的是“凭证ID + 公钥 + 用户绑定关系”,认证时通过凭证ID定位公钥。凭证ID本身不是秘密,可以公开传输,真正敏感的是认证器内部的私钥。

公钥凭证 = 凭证ID(索引)+ 公钥(验签)+ 签名计数器(防克隆)+ 用户/RP绑定(作用域)。私钥永远不离开认证器。

二、挑战-响应:防重放的核心机制

挑战-响应(Challenge-Response)是WebAuthn防重放攻击的根基。RP在每次认证请求中生成一个高熵随机数作为challenge,认证器将其纳入签名数据。攻击者即使截获了上一次的签名,也无法用于下一次,因为challenge已变化。

签名数据并非只签challenge,而是签一个由多字段拼接后哈希的认证数据(authenticatorData)与客户端数据JSON(clientDataJSON)的组合。clientDataJSON中包含type、challenge、origin等,RP必须逐项校验:challenge是否匹配、origin是否属于自己、type是否为webauthn.get或webauthn.create。

// 服务端生成挑战(示例)
const challenge = crypto.randomBytes(32); // 32字节高熵随机数
session.challenge = challenge.toString('base64url');

// 认证器返回的clientDataJSON(解码后)
{
  "type": "webauthn.get",
  "challenge": "k9X...base64url...",
  "origin": "https://pay.example.com",
  "crossOrigin": false
}
常见误区:把challenge写成固定值或时间戳。固定值等于放弃防重放;时间戳可预测,熵不足。必须使用密码学安全随机数,且一次性使用、服务端保存并与本次会话绑定。

三、RP ID、用户句柄与作用域约束

RP ID是WebAuthn的作用域标识,通常是一个有效域名(如example.com)。它决定了凭证的适用范围:在pay.example.com注册的凭证,默认可用于同域及其子域,但不能用于evil.com。浏览器会强制校验origin与RP ID的一致性,这是WebAuthn抗钓鱼的关键——钓鱼站点即使诱导用户操作,也无法让认证器为错误RP ID签名。

用户句柄(user.id)是RP为用户分配的、不可猜测的字节序列,用于在认证器中标识用户。它不是用户名,也不是邮箱。用户名可以变、可以重复展示,但user.id必须稳定且唯一。认证器保存的是user.id与凭证的映射,RP在注册时提供,认证时通过allowCredentials回传凭证ID来定位。

概念作用域/归属是否秘密典型错误
RP ID依赖方域名否用IP或端口,导致浏览器拒绝
user.id依赖方分配否(但需不可猜测)直接用邮箱或自增ID
challenge每次会话是(一次性)固定值或复用
凭证ID认证器生成否当作秘密存储
私钥认证器内部是试图导出到服务端

四、认证器、客户端与依赖方的角色分工

WebAuthn定义了三个角色,理解它们的分工是排障的基础:

  • 认证器(Authenticator):真正持有私钥并执行签名的实体。可以是平台认证器(Touch ID、Windows Hello、Android生物识别),也可以是漫游认证器(YubiKey等安全密钥)。它负责用户验证(UV)和用户存在性检测(UP)。
  • 客户端(Client):通常是浏览器或操作系统WebAuthn API。它负责与认证器通信、组装clientDataJSON、校验origin与RP ID,并把结果返回给RP。客户端不持有私钥,但它是信任链的“守门人”。
  • 依赖方(Relying Party,RP):即支付系统服务端。它生成challenge、保存公钥凭证、验签、校验计数器与标志位,并最终决定是否放行交易。

在支付风控场景中,RP还需要关注认证器返回的标志位:UP(用户存在)、UV(用户验证)、BE(备份可用)、BS(备份状态)。例如,大额支付通常要求UV=true,即用户通过了生物识别或PIN;若仅UP=true(如按下按键),则可能只适合低风险场景。风控引擎可据此动态调整策略。

排障口诀:浏览器报错先看origin与RP ID是否匹配;验签失败先看challenge是否一致;凭证找不到先看allowCredentials与user.id绑定是否正确。

五、案例与复盘:一次“验签通过却被盗刷”的复盘

某支付团队接入WebAuthn后,验签逻辑正确,但仍发生盗刷。复盘发现:服务端在注册时把user.id直接设为用户邮箱,且认证时未校验签名计数器。攻击者通过撞库拿到邮箱后,在另一台设备上重新注册,由于user.id相同,RP错误地将新凭证与老用户绑定,导致攻击者用自己的认证器完成了“合法”签名。修复方案是:user.id改为不可猜测的随机UUID,并在认证时校验计数器单调递增,同时绑定凭证ID与用户的一对多关系。

这个案例说明:WebAuthn的密码学强度很高,但身份绑定与状态校验仍由RP负责。公钥凭证、挑战、RP ID、用户句柄、认证器、客户端与RP角色,任何一个环节的语义被误用,都会把强认证降级为“形式上的强认证”。在支付风控实战中,建议把上述六要素做成一张校验清单,纳入每次认证请求的必检项,并结合交易金额、设备指纹、地理位置做动态策略,才能真正发挥WebAuthn的抗钓鱼与抗重放价值。