C# WebAuthn凭证文件 C#如何处理存储生物识别信息的公钥凭证文件

1次阅读

webauthn公钥凭证不包含生物识别信息,仅含加密签名等元数据;attestationobject为cbor编码,须用cbor库解析;authdata中嵌入公钥,需正确提取并验证;credentialid和publickey必须以二进制形式存储。

C# WebAuthn凭证文件 C#如何处理存储生物识别信息的公钥凭证文件

WebAuthn 公钥凭证不是“生物识别信息”本身

很多人误以为 AuthenticatorAttestationResponseAuthenticatorAssertionResponse 文件里存着指纹/人脸图像,其实完全不是。它只包含加密签名、挑战响应、公钥、认证器标识等元数据——生物特征始终留在设备 Secure Enclave / TPM 里,从不离开。C# 程序处理的只是这些密码学结构,不是生物模板。

所以你不需要也**不应该**去“解密生物信息”,而是要正确解析、验证、序列化和持久化标准 WebAuthn 二进制结构(如 CBOR 编码的 attestationObjectauthenticatorData)。

C# 解析 attestationObject 必须用 CBOR 库,不能用 json

浏览器传来的 attestationObject 是 CBOR 编码的二进制 blob,不是 JSON 字符串。直接用 JsonSerializer.Deserialize 会报错或静默损坏数据——这是最常踩的坑。

  • System.Text.Json 解析必然失败,CBOR 和 JSON 语义不兼容(比如字节串、标签、未定义长度数组)
  • 推荐用 microsoft.Cswpf.Cbor(轻量、.NET 6+ 原生支持)或 Newtonsoft.Json + Newtonsoft.Json.Cbor
  • 关键字段如 authData 是字节数组,必须原样保留;fmt 字段决定验证逻辑分支("fido-u2f""packed""android-key" 等)

示例:用 CborReader 提取 authData

var cbor = CborReader.Read(bytes); // bytes = request.Response.AttestationObject var authDataBytes = cbor["authData"].AsByteArray(); // 不要 ToString() 或 Encoding.UTF8.GetString()

验证 signature 时,publicKey 必须从 authData 解析,不能从 x5c 直接取

尤其在 "packed" 格式下,证书链(x5c)可选,而公钥一定嵌在 authData 的扩展字段(attestedCredentialData)里。跳过这步会导致验签失败或接受伪造凭证。

  • authData 前 32 字节是 RP ID hash,接着 1 字节标志位,再 16 字节 AAGUID,然后才是可变长的 attestedCredentialData
  • 其中第 17–20 字节是 credential ID 长度,之后是 credential ID(需原样存储用于后续登录),再之后 1 字节是公钥长度,紧接着才是 DER 编码的 EC public key(SubjectPublicKeyInfo
  • System.Security.Cryptography.ECDsa.Create() 加载该 DER 数据,再用它验证 signatureclientDataHash + authData 的签名

存储 credentialId 和 publicKey 时,别用 Base64Url 就完事

很多教程只说“存 credentialId”,但漏了两个关键点:长度和编码一致性。数据库字段类型、索引策略、比较逻辑都会因此出问题。

  • credentialId 是原始字节数组,长度通常 16–64 字节(不同认证器差异大),**必须存为 VARBINARY / BYTEA / BLOB**,而非 VARCHAR;否则可能被截断或乱码
  • 如果非要转字符串(比如日志或调试),统一用 Base64UrlEncoder.Encode(不是 Convert.ToBase64String),避免 + / = 字符引发路由或 URL 问题
  • publicKey 同理:存 DER 二进制,不要存 PEM;验证时也必须用原始 DER,PEM 头尾会破坏 ASN.1 结构

真正麻烦的是跨平台兼容性:Android SafetyNet 和 Apple Secure Enclave 返回的 fmt 和签名算法完全不同,一套验证逻辑跑不通所有设备——得按 fmt 分支写,且每个分支都要单独测试真实设备。

text=ZqhQzanResources