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

FIDO2, WebAuthn и CTAP: как связаны стандарты passkey

Это не альтернативы. WebAuthn — API от W3C, который вызывают сайты, CTAP — протокол FIDO между устройством и ключом безопасности или телефоном, а FIDO2 — общее название для обоих.

FIDO2 или WebAuthn — вопрос не о выборе, а об уровнях. WebAuthn (W3C Web Authentication) определяет JavaScript API, который вызывают доверяющие стороны (relying parties, RP), и форматы подписанных данных, которые они проверяют. CTAP (FIDO Client to Authenticator Protocol) определяет, как браузер или операционная система обменивается данными с внешним аутентификатором через USB, NFC, Bluetooth или гибридный транспорт с QR-кодом. FIDO2 — общее название для связки WebAuthn и CTAP 2, которое FIDO Alliance ввёл в 2018 году. Ключи доступа (passkeys) — это учётные данные FIDO, созданные по этим стандартам.

Коротко о главном

WebAuthnCTAPFIDO2
Что этоAPI браузера и форматы данныхПротокол обмена с внешними аутентификаторамиОбщее название для обоих
Кто выпускаетW3CFIDO AllianceFIDO Alliance
Текущая версияLevel 3, рекомендация W3C от 25 авг. 2026 г.CTAP 2.3, предлагаемый стандарт от 26 февр. 2026 г. (2.2: июль 2025 г.)WebAuthn Level 3 с CTAP 2.3
Прежние версииLevel 1 (март 2019 г.), Level 2 (апрель 2021 г.)CTAP 2.0, 2.1 (2021 г.); CTAP1 — это U2FПредшественник — U2F (2014 г.)
Кто реализуетБраузеры и платформы (API), доверяющие стороны (проверки на сервере)Браузеры, операционные системы, ключи безопасности, телефоныВсе участники цепочки
СвязываетВеб-страницу и браузер; сервер и данные аутентификатораКлиентскую платформу и аутентификаторДоверяющую сторону с аутентификатором
КодированиеОбъекты JavaScript, clientDataJSON, объекты аттестации в CBORКоманды CTAP2 в каноническом CBOR, например authenticatorGetAssertion (0x02)Оба варианта
ТранспортыНе применяетсяUSB HID, NFC, Bluetooth Low Energy, гибридный (QR-код, проверка близости, туннель)Оба варианта
Основные точки входаnavigator.credentials.create() и .get()authenticatorMakeCredential (0x01), authenticatorGetInfo (0x04)Не применяется

Кто что делает при входе

  1. Сайт вызывает navigator.credentials.get(), передавая challenge и свой идентификатор доверяющей стороны (RP ID). Это WebAuthn.
  2. Браузер собирает clientDataJSON (тип, challenge, источник, флаг crossOrigin), хеширует его и просит аутентификатор поставить подпись. Для встроенного платформенного аутентификатора (Touch ID, Windows Hello, Android) запрос идёт через API операционной системы, а для ключа безопасности или телефона — по CTAP, в виде команды authenticatorGetAssertion в CBOR.
  3. Аутентификатор проверяет присутствие пользователя и, если это запрошено, выполняет проверку пользователя, а затем подписывает свои данные аутентификатора, к которым приписан хеш клиентских данных. Ответ возвращается по CTAP или через API платформы.
  4. Браузер возвращает эти байты странице, а сервер проверяет их так, как предписывает § 7.2 спецификации WebAuthn.

Скрипт ниже разбирает официальный тестовый вектор WebAuthn Level 3 из § 16.2, в котором есть регистрация и аутентификация для учётных данных ES256, и проверяет подпись (Node.js 26, cbor-x 1.6.0). authData и подпись созданы аутентификатором, а clientDataJSON — браузером.

Разбор и проверка тестового вектора WebAuthn Level 3 из § 16.2
import { createHash, createPublicKey, verify } from "node:crypto";
import { decode } from "cbor-x";

// WebAuthn Level 3 §16.2 test vector: ES256 credential, "none" attestation
const hex = (s) => Buffer.from(s, "hex");
const attestationObject = hex("a363666d74646e6f6e656761747453746d74a068617574684461746158a4bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b559000000008446ccb9ab1db374750b2367ff6f3a1f0020f91f391db4c9b2fde0ea70189cba3fb63f579ba6122b33ad94ff3ec330084be4a5010203262001215820afefa16f97ca9b2d23eb86ccb64098d20db90856062eb249c33a9b672f26df61225820930a56b87a2fca66334b03458abf879717c12cc68ed73290af2e2664796b9220");
const authenticatorData = hex("bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b51900000000");
const clientDataJSON = hex("7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224f63446e55685158756c5455506f334a5558543049393770767a7a59425039745a63685879617630314167222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73657d");
const signature = hex("3046022100f50a4e2e4409249c4a853ba361282f09841df4dd4547a13a87780218deffcd380221008480ac0f0b93538174f575bf11a1dd5d78c6e486013f937295ea13653e331e87");

const sha256 = (b) => createHash("sha256").update(b).digest();
const flagNames = { 0x01: "UP", 0x04: "UV", 0x08: "BE", 0x10: "BS", 0x40: "AT", 0x80: "ED" };
const flags = (b) => Object.entries(flagNames).filter(([bit]) => b & bit).map(([, n]) => n).join(" ");

// Registration: authenticator data produced by the authenticator (CTAP authenticatorMakeCredential)
const { fmt, authData } = decode(attestationObject);
const credIdLen = authData.readUInt16BE(53);
const coseKey = decode(authData.subarray(55 + credIdLen));
console.log("attestation format:", fmt);
console.log("rpIdHash == SHA-256('example.org'):", authData.subarray(0, 32).equals(sha256("example.org")));
console.log("flags:", flags(authData[32]), "| signCount:", authData.readUInt32BE(33));
console.log("AAGUID:", authData.subarray(37, 53).toString("hex"), "| credential ID bytes:", credIdLen);
console.log("COSE key: kty", coseKey[1], "alg", coseKey[3], "crv", coseKey[-1]);

// Authentication: verify the assertion with the registered public key
const publicKey = createPublicKey({ key: { kty: "EC", crv: "P-256",
  x: Buffer.from(coseKey[-2]).toString("base64url"), y: Buffer.from(coseKey[-3]).toString("base64url") }, format: "jwk" });
console.log("\nclientDataJSON (built by the browser):", clientDataJSON.toString());
console.log("assertion flags:", flags(authenticatorData[32]));
const ok = verify("sha256", Buffer.concat([authenticatorData, sha256(clientDataJSON)]), publicKey, signature);
console.log("signature over authenticatorData || SHA-256(clientDataJSON):", ok ? "valid" : "invalid");
Результат
attestation format: none
rpIdHash == SHA-256('example.org'): true
flags: UP BE BS AT | signCount: 0
AAGUID: 8446ccb9ab1db374750b2367ff6f3a1f | credential ID bytes: 32
COSE key: kty 2 alg -7 crv 1

clientDataJSON (built by the browser): {"type":"webauthn.get","challenge":"OcDnUhQXulTUPo3JUXT0I97pvzzYBP9tZchXyav01Ag","origin":"https://example.org","crossOrigin":false}
assertion flags: UP BE BS
signature over authenticatorData || SHA-256(clientDataJSON): valid

Флаги показывают присутствие пользователя (UP), учётные данные, для которых резервное копирование допускается и уже выполнено (BE, BS: синхронизируемый ключ доступа), и сведения об аттестованных учётных данных (AT) при регистрации. Ключ COSE (тип ключа 2 = EC2, алгоритм −7 = ES256, кривая 1 = P-256) — формат, общий для обеих спецификаций; инспектор WebAuthn декодирует те же структуры из настоящего ответа браузера.

От U2F к ключам доступа

Стандарт FIDO U2F (2014) дал ключам безопасности простой протокол второго фактора, который теперь называется CTAP1, и API для браузера. В 2018–2019 годах на смену этому API пришёл WebAuthn (Chrome 67, Firefox 60, Safari 13), а CTAP 2 добавил PIN-коды, встроенную проверку пользователя и обнаруживаемые учётные данные, так что ключ смог заменить пароль, а не только дополнять его. Из Chrome старый JavaScript API для U2F удалили в версии 104 (2022 год); учётные данные, зарегистрированные через него, по-прежнему работают в WebAuthn благодаря расширению appid.

Когда Apple, Google и Microsoft в 2022 году объявили о синхронизируемых ключах доступа, нового протокола для этого не понадобилось: ключ доступа — это обнаруживаемые учётные данные с установленными флагами резервного копирования, создаваемые и используемые через тот же API WebAuthn. CTAP 2.2 (июль 2025 года) стандартизировал политики сложности PIN-кода и гибридный транспорт, который позволяет телефону после сканирования QR-кода работать аутентификатором для компьютера поблизости; CTAP 2.3 (февраль 2026 года) — очередная редакция с небольшими изменениями: например, в ней аутентификаторы могут сообщать, какие команды конфигурации они поддерживают.

Аттестация и сертификация

При регистрации аутентификатор может приложить заявление об аттестации, подписанное ключом, общим для целой партии устройств, и AAGUID, который указывает модель. WebAuthn определяет форматы этих заявлений (packed, tpm, android-key, apple, fido-u2f, none), а FIDO Alliance сертифицирует аутентификаторы и публикует их метаданные, включая корневые сертификаты аттестации и уровни сертификации, в службе метаданных FIDO (FIDO Metadata Service). Большинству сайтов для массовой аудитории не следует запрашивать аттестацию, а принимать следует любой аутентификатор. Компании, которые обязаны допускать только сертифицированные аппаратные ключи, запрашивают аттестацию direct или enterprise и сверяют её с метаданными; как это работает, показывают проверка аттестации YubiKey и поиск по AAGUID.

Что реализовать

  • Сайт или веб-приложение: WebAuthn в браузере и серверная библиотека, которая реализует шаги проверки из § 7.
  • Нативное приложение для iOS или Android: API ключей доступа платформы (AuthenticationServices, Credential Manager), которые возвращают данные в формате WebAuthn, привязанные к вашему веб-домену через associated domains или Digital Asset Links.
  • Компания, которая допускает только аппаратные ключи: WebAuthn с аттестацией, которую сверяют с записями службы метаданных FIDO.
  • Производитель ключей безопасности, телефонов или ОС: CTAP для внешних аутентификаторов и сертификация FIDO для продукта.
  • Закупки: запрашивайте уровень сертификации FIDO, поддерживаемую версию CTAP и транспорты, а также возможности WebAuthn, на которые вы опираетесь, а не просто «поддержку FIDO2».

Заметки о миграции

  1. Замена регистраций U2F: при входе продолжайте передавать старый App ID через расширение appid, а при следующем входе пользователя регистрируйте для него новые учётные данные WebAuthn.
  2. Переход на ключи доступа: запрашивайте residentKey: "required" и userVerification: "preferred" или "required" и сохраняйте флаги резервного копирования.
  3. Переход на возможности Level 3 выполняйте постепенно, по одной: условное создание, getClientCapabilities(), сигнальные методы и связанные источники (related origins) — все они корректно деградируют там, где их не поддерживают.
  4. Тестирование: до запуска проверьте в песочнице ключей доступа платформенные аутентификаторы, ключ USB или NFC и гибридный сценарий с телефоном.

Источники

Все сравнения