跳到主要内容
ecdsa.com
Python

py_webauthn

Duo Labs 开发的 py_webauthn,Python 中作为参考实现的 WebAuthn 依赖方库:断言签名只接受 DER,计数器检查严格,但除非你明确要求,否则不强制用户验证。
许可证
BSD-3-Clause
检查的版本
3.0.1 ·
源代码
github.com/duo-labs/py_webauthn
文档
duo-labs.github.io/py_webauthn/

兼容性概览

WebAuthn

py_webauthn(PyPI 上的 webauthn 包)基于 pyca/cryptography 生成注册和身份验证选项,并验证响应,支持所有证明格式。检查中向它输入的软件认证器注册数据和断言,与 SimpleWebAuthn、go-webauthn 和 WebAuthn4J 所用的完全相同,正因如此,它们之间的差异才清晰可见。

它的默认策略是这四个库中最宽松的。generate_registration_options() 完全不发送 authenticatorSelection 成员,于是浏览器退回到默认值:用户验证为 preferred,驻留密钥为 discouraged——即不可发现的通行密钥;而除非传入 require_user_verification=True,否则 verify_registration_response() 和 verify_authentication_response() 都接受了未设置 UV 标志的响应。因此,基于默认设置构建的登录流程,只需轻触安全密钥、无需输入 PIN 就能通过。应像示例那样,在生成选项和验证响应两端都显式设置策略。

断言签名必须是 ASN.1 DER:64 字节的原始 r‖s 签名报错 Could not verify authentication signature,这条消息并未提到编码;DER ⇄ 原始格式转换器可以显示测试客户端是否生成了错误的形式。high-S 的 DER 签名验证通过。默认提供 Ed25519(-8)、ES256(-7)和 RS256(-257);与 SimpleWebAuthn 14 不同,没有 ML-DSA 条目。

计数器检查很严格:5 之后出现 3 会报错 Response sign count of 3 was not greater than current count of 5,5 之后出现 0 也一样;而 0 之后仍为 0——同步通行密钥报告的正是这种情况——验证通过。每次登录成功后,都要保存返回的 new_sign_count。

用 WebAuthn 查看器解码浏览器的原始输出,并在通行密钥沙盒中放心试验。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/py-webauthn/run.sh 于2026年10月20日在容器镜像 python:3.14.8-slim-trixie 中运行了 py_webauthn 3.0.1;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • 默认 COSE 算法

    -8-7-257

    generate_registration_options() 列出的 COSE 算法为 -8(Ed25519)、-7(ES256)和 -257(RS256)。

    检查记录
    fact webauthn-default-algorithms = -8, -7, -257
  • 默认证明方式

    none

    证明传递方式默认为 none。

    检查记录
    fact webauthn-attestation = none
  • 默认用户验证

    preferred

    默认不发送 authenticatorSelection,因此浏览器采用 WebAuthn 的默认值 preferred。

    检查记录
    fact webauthn-user-verification = preferred
    evidence webauthn-user-verification: authenticatorSelection in the generated options: absent
  • 默认驻留密钥

    discouraged

    没有 authenticatorSelection 时,适用 WebAuthn 的默认值 discouraged,因此不会要求认证器创建可发现凭证。

    检查记录
    fact webauthn-resident-key = discouraged
  • 默认强制用户验证

    no

    只设置了 UP 标志的注册和断言都验证通过;传入 require_user_verification=True 后,断言报错 User verification is required but user was not verified during authentication。

    检查记录
    fact webauthn-uv-enforced-by-default = no
    evidence webauthn-uv-enforced-by-default: registration with the UV flag clear: verified
  • 断言签名编码

    der

    带 DER 签名的正确 ES256 断言验证通过。

    检查记录
    fact webauthn-assertion-encoding = der
  • 原始 r‖s 断言签名

    rejected

    64 字节的原始 r‖s 签名报错 InvalidAuthenticationResponse: Could not verify authentication signature。

    检查记录
    fact webauthn-raw-signature = rejected
  • high-S 断言签名

    accepted

    把 s 替换为 n − s 的 DER 签名验证通过。

    检查记录
    fact webauthn-high-s = accepted
  • 签名计数器回退

    rejected

    已存储值为 5 时,计数器 3 和 0 都报错 Response sign count of … was not greater than current count of 5;已存储值为 0 时,计数器 0 验证通过。

    检查记录
    fact webauthn-counter-regression = rejected
  • Ed25519 凭证

    supported

    Ed25519(COSE -8)凭证的注册和身份验证均成功。

    检查记录
    fact webauthn-ed25519 = supported
    evidence webauthn-ed25519: verified

用法

生成要求用户验证和可发现凭证的注册与身份验证选项,而不使用库的默认值。
import json

from webauthn import generate_authentication_options, generate_registration_options, options_to_json
from webauthn.helpers.structs import AuthenticatorSelectionCriteria, ResidentKeyRequirement, UserVerificationRequirement

# The defaults send no authenticatorSelection at all, and verification does not
# require UV. State the policy in the options, and pass
# require_user_verification=True to verify_registration_response() and
# verify_authentication_response() so the server enforces the same policy.
registration = generate_registration_options(
    rp_id="example.com",
    rp_name="Example",
    user_name="user@example.com",
    authenticator_selection=AuthenticatorSelectionCriteria(
        resident_key=ResidentKeyRequirement.REQUIRED,
        user_verification=UserVerificationRequirement.REQUIRED,
    ),
)
authentication = generate_authentication_options(rp_id="example.com", user_verification=UserVerificationRequirement.REQUIRED)

selection = json.loads(options_to_json(registration))["authenticatorSelection"]
print("uv:", selection["userVerification"], json.loads(options_to_json(authentication))["userVerification"])
print("resident key:", selection["residentKey"])

代码片段测试工具会在容器镜像 python:3.14.8-slim-trixie 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
pip install --quiet --root-user-action=ignore webauthn==3.0.1
命令
python usage.py
输出必须匹配(正则表达式)
uv: required required\nresident key: required

库