Перейти к содержимому
ecdsa.com

WebAuthn / Passkey Inspector — инспектор WebAuthn и ключей доступа

Вставьте то, что вернул navigator.credentials.create() или .get(), — весь JSON или одно поле base64url. Каждая часть декодируется и объясняется: clientDataJSON, флаги данных аутентификатора бит за битом, AAGUID, идентификатор учётных данных, открытый ключ COSE и заявление об аттестации. При входе подпись ECDSA P-256 проверяется прямо здесь.

100 % локально — всё работает в браузере, ни на один сервер ничего не отправляется

Ответ navigator.credentials.create() — новый ключ доступа.

Результат декодирования

Пока ничего не вставлено

всё декодируется локально

Выполните регистрацию или вход в консоли браузера и скопируйте результат. В Chrome и Safari credential.toJSON() возвращает именно тот JSON, который ожидает это поле; в старом коде нужны значения base64url из credential.response. Или нажмите Загрузить пример, чтобы увидеть настоящий ответ ключа доступа с верной подписью.

How it works

A passkey is an ECDSA key pair — almost always on the P-256 curve — created by your phone, laptop or security key. The private half never leaves that device. Registration hands your server the public half; every later login is a signature made with the private half over data your server chose. There is no shared secret to steal, and the browser refuses to sign for a site the credential was not created on, which is what makes passkeys phishing-resistant.

The browser returns two or three base64url blobs. clientDataJSON is plain JSON describing what the browser saw: the ceremony type, your challenge, and the real origin. authenticatorData is a packed binary record: 32 bytes of SHA-256(RP ID), one flags byte, a 4-byte counter, and — during registration — the AAGUID, the credential ID and the new public key as a CBOR-encoded COSE_Key. During registration that record is wrapped in an attestationObject, a CBOR map that adds the authenticator's optional statement about its own make and model.

Verifying a login is one line of maths: verify(signature, SHA-256(authenticatorData ‖ SHA-256(clientDataJSON)), publicKey). Note what is not in there — the challenge and the origin are only covered because they sit inside clientDataJSON, so a valid signature is necessary but never sufficient: your server still has to read those fields and compare them with what it expects. WebAuthn signatures are ASN.1 DER, not the raw r‖s format JWTs use, which is a common source of "valid signature that will not verify".

The same page checks the attestation statement of a registration in every format the WebAuthn registry defines — packed, tpm, android-key, android-safetynet, fido-u2f, apple, none and compound — and assertion signatures with every COSE algorithm a credential may carry: ES256, ES384, ES512, EdDSA, RS256, PS256 and their relatives. A valid attestation proves that whoever holds the attestation key signed this registration; whether that key belongs to hardware you trust is a separate decision, made by chaining its certificates to a root you accept — normally one the FIDO Metadata Service publishes, which the AAGUID Lookup reads for you.

This page does all of it in your browser: the CBOR decoder and the WebAuthn parsers are written from scratch, ECDSA and EdDSA signatures are checked with the audited @noble/curves library, and RSA signatures with a few dozen lines of RFC 8017 arithmetic. Nothing you paste is uploaded, logged or stored — which matters, because a real response identifies a real credential of a real user.