Pular para o conteúdo
ecdsa.com

FIDO2 vs WebAuthn vs CTAP: os padrões das chaves de acesso

Não são alternativas. O WebAuthn é a API do W3C que os sites chamam, o CTAP é o protocolo da FIDO entre o dispositivo e uma chave de segurança ou um celular, e FIDO2 é o nome dos dois juntos.

FIDO2 vs WebAuthn é uma questão de camadas, não de escolha. O WebAuthn (Web Authentication, do W3C) define a API JavaScript que as partes confiáveis (relying parties, RPs) chamam e os formatos de dados assinados que elas verificam. O CTAP (Client to Authenticator Protocol, da FIDO) define como um navegador ou sistema operacional se comunica com um autenticador externo por USB, NFC, Bluetooth ou pelo transporte híbrido com QR code. FIDO2 é o nome abrangente que a FIDO Alliance criou em 2018 para o WebAuthn mais o CTAP 2. As chaves de acesso (passkeys) são credenciais FIDO criadas por meio desses padrões.

Visão geral

WebAuthnCTAPFIDO2
O que éAPI do navegador e formatos de dadosProtocolo de comunicação com autenticadores externos (roaming)Termo abrangente para os dois
Publicado porW3CFIDO AllianceFIDO Alliance
Versão atualLevel 3, Recomendação do W3C de 25 de agosto de 2026CTAP 2.3, padrão proposto (Proposed Standard) de 26 de fevereiro de 2026 (2.2: julho de 2025)WebAuthn Level 3 com CTAP 2.3
Versões anterioresLevel 1 (março de 2019), Level 2 (abril de 2021)CTAP 2.0, 2.1 (2021); o CTAP1 é o U2FU2F (2014) é o antecessor
Quem implementaNavegadores e plataformas (API), partes confiáveis (verificações no servidor)Navegadores, sistemas operacionais, chaves de segurança, celularesTodos na cadeia
EntrePágina web e navegador; servidor e dados do autenticadorPlataforma cliente e autenticadorDa parte confiável ao autenticador
CodificaçãoObjetos JavaScript, clientDataJSON, objetos de atestado em CBORComandos em CBOR canônico do CTAP2, como authenticatorGetAssertion (0x02)Ambos
TransportesNão se aplicaUSB HID, NFC, Bluetooth Low Energy, híbrido (QR code, verificação de proximidade, túnel)Ambos
Principais pontos de entradanavigator.credentials.create() e .get()authenticatorMakeCredential (0x01), authenticatorGetInfo (0x04)Não se aplica

Quem faz o quê durante um login

  1. O site chama navigator.credentials.get() com um desafio e o próprio ID da parte confiável (RP ID). Isso é WebAuthn.
  2. O navegador monta o clientDataJSON (tipo, desafio, origem, flag de origem cruzada), calcula o hash dele e pede a um autenticador que assine. Com um autenticador de plataforma embutido (Touch ID, Windows Hello, Android), isso passa pelas APIs do sistema operacional; com uma chave de segurança ou um celular, segue pelo CTAP como um comando authenticatorGetAssertion em CBOR.
  3. O autenticador confere a presença do usuário e, se solicitado, faz a verificação do usuário; depois assina os próprios dados do autenticador concatenados com o hash dos dados do cliente. A resposta volta pelo CTAP ou pela API da plataforma.
  4. O navegador devolve os bytes à página, e o servidor os verifica conforme especificado no WebAuthn §7.2.

O script abaixo analisa o vetor de teste oficial §16.2 do WebAuthn Level 3, que contém um registro e uma autenticação de uma credencial ES256, e verifica a assinatura (Node.js 26, cbor-x 1.6.0). O authData e a assinatura foram produzidos pelo autenticador; o clientDataJSON, pelo navegador.

Analisar e verificar o vetor de teste §16.2 do WebAuthn Level 3
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");
Saída
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

No registro, as flags mostram a presença do usuário (UP), uma credencial elegível para backup e com backup feito (BE, BS: uma chave de acesso sincronizada) e dados atestados da credencial (AT). A chave COSE (tipo de chave 2 = EC2, algoritmo −7 = ES256, curva 1 = P-256) é o formato que as duas especificações compartilham; o inspetor de WebAuthn decodifica as mesmas estruturas a partir de uma resposta real do navegador.

Do U2F às chaves de acesso

O FIDO U2F (2014) deu às chaves de segurança um protocolo simples de segundo fator, hoje chamado CTAP1, e uma API de navegador. O WebAuthn substituiu essa API entre 2018 e 2019 (Chrome 67, Firefox 60, Safari 13), e o CTAP 2 acrescentou PINs, verificação do usuário embutida e credenciais detectáveis, de modo que uma chave passou a poder substituir a senha, e não apenas complementá-la. O Chrome removeu a antiga API JavaScript do U2F na versão 104 (2022); as credenciais registradas por meio dela continuam funcionando no WebAuthn graças à extensão appid.

O anúncio das chaves de acesso sincronizadas pela Apple, pelo Google e pela Microsoft, em 2022, não exigiu um protocolo novo: uma chave de acesso é uma credencial detectável com as flags de backup ativadas, criada e usada pela mesma API WebAuthn. O CTAP 2.2 (julho de 2025) padronizou o transporte híbrido, que permite a um celular atuar como autenticador de um computador próximo depois de escanear um QR code, junto com políticas de complexidade de PIN; o CTAP 2.3 (fevereiro de 2026) é uma revisão incremental que, por exemplo, permite aos autenticadores informar quais comandos de configuração eles aceitam.

Atestado e certificação

No registro, um autenticador pode incluir uma declaração de atestado assinada por uma chave compartilhada por um lote de dispositivos, além de um AAGUID que identifica o modelo. O WebAuthn define os formatos de declaração (packed, tpm, android-key, apple, fido-u2f, none); a FIDO Alliance certifica autenticadores e publica os metadados deles, incluindo raízes de atestado e níveis de certificação, no serviço de metadados da FIDO (FIDO Metadata Service). Na maioria dos sites voltados ao consumidor, convém não solicitar atestado e aceitar qualquer autenticador; empresas que precisam permitir apenas chaves de hardware certificadas solicitam atestado direct ou enterprise e o conferem com os metadados, como ilustram o verificador de atestado da YubiKey e a consulta de AAGUID.

O que implementar

  • Site ou aplicativo web: WebAuthn no navegador e uma biblioteca de servidor que implemente as etapas de verificação (§7).
  • App nativo para iOS ou Android: as APIs de chaves de acesso da plataforma (AuthenticationServices, Credential Manager), que retornam dados no formato WebAuthn vinculados ao seu domínio web por meio de domínios associados (associated domains) ou Digital Asset Links.
  • Empresa que permite apenas chaves de hardware: WebAuthn com atestado, conferido com as entradas do serviço de metadados da FIDO.
  • Fabricante de chaves de segurança, celulares ou sistemas operacionais: CTAP para autenticadores externos, certificação FIDO para o produto.
  • Compras e licitações: peça o nível de certificação FIDO, a versão do CTAP e os transportes aceitos, além dos recursos do WebAuthn de que você depende, em vez de apenas “suporte a FIDO2”.

Notas de migração

  1. Substituição de registros U2F: continue enviando o antigo App ID pela extensão appid no login e registre uma nova credencial WebAuthn no próximo login de cada usuário.
  2. Adoção de chaves de acesso: solicite residentKey: "required" e userVerification: "preferred" ou "required", e armazene as flags de backup.
  3. Migração para o Level 3, recurso por recurso: a criação condicional, getClientCapabilities(), os métodos de sinalização (signal methods) e as origens relacionadas degradam graciosamente quando não há suporte.
  4. Testes: antes do lançamento, experimente no ambiente de testes de chaves de acesso autenticadores de plataforma, uma chave USB ou NFC e o fluxo híbrido a partir de um celular.

Referências

Todas as comparações