Saltar al contenido principal
Passkeys y FIDO2: El Fin de las Contraseñas y la Autenticación sin Contraseña

Passkeys y FIDO2: El Fin de las Contraseñas y la Autenticación sin Contraseña

Todo sobre passkeys y el estándar FIDO2: cómo funcionan técnicamente, por qué son más seguras que las contraseñas, cómo implementarlas en tu organización y el estado actual de la adopción global.

11 minCyberFlows Team
[Espacio publicitario — AdSense]

Passkeys y FIDO2: El Fin de las Contraseñas

El problema que ningún parche ha podido resolver

En 2025, la base de datos de Have I Been Pwned superó los 15.000 millones de credenciales comprometidas. Cada semana aparecen nuevos volcados masivos en foros underground: millones de combinaciones usuario-contraseña expuestas, listas para ser explotadas en credential stuffing, stuffing lateral y ataques de fuerza bruta automatizados.

Las contraseñas son un mecanismo de autenticación heredado de los años 60, diseñado en una era en la que los ordenadores compartían habitaciones, no bolsillos. En 2026, seguimos intentando que 8.000 millones de personas memoricen, roten y gestionen decenas de credenciales únicas. El resultado es predecible: reutilización masiva, contraseñas débiles, post-it en monitores y robo de credenciales como servicio (CaaS) operando con rentabilidad.

NIST, OWASP y la FTC llevan años recomendando autenticación multifactor. Pero incluso MFA tiene puntos ciegos: ataques SIM swap contra SMS OTP, prompt bombing contra aplicaciones de autenticación y session hijacking contra tokens. La autenticación basada en conocimiento (contraseñas) y los factores derivados de posesión (tokens, apps) comparten una vulnerabilidad fundamental: pueden ser interceptados en tránsito o suplantados mediante ingeniería social.

FIDO2 y las passkeys eliminan esta ecuación por completo. No es una mejora incremental. Es un cambio de paradigma.


¿Qué son las Passkeys?

Una passkey es un par de claves criptográficas asimétricas vinculado a un dominio específico, almacenado en un dispositivo de hardware o software, que se utiliza en lugar de una contraseña para autenticar al usuario.

A diferencia de una contraseña, una passkey:

  • Nunca se transmite a través de la red. La autenticación se realiza mediante firma digital con la clave privada.
  • Es específica del sitio web. Una passkey generada para example.com no puede usarse en evil-example.com, lo que la hace inmune por diseño al phishing.
  • Se genera criptográficamente. No depende de la capacidad del usuario para crear o memorizar entropy.
  • Puede ser sincronizada entre dispositivos del mismo ecosistema (Apple Keychain, Google Password Manager, 1Password) o almacenada en hardware dedicado (YubiKey, Titan Key).

El estándar que define el funcionamiento técnico de las passkeys es FIDO2, que se compone de dos especificaciones principales: CTAP2 (Client to Authenticator Protocol) y WebAuthn (Web Authentication API), esta última ratificada por el W3C.


Cómo funciona FIDO2/WebAuthn técnicamente

El protocolo WebAuthn sigue un modelo challenge-response basado en criptografía de curva elíptica (ECDSA sobre P-256 o EdDSA). Veamos el flujo completo:

1. Registro (Attestation)

Cuando un usuario registra una passkey en un servicio, ocurre lo siguiente:

  1. El servidor genera un challenge criptográfico aleatorio y envía al cliente un PublicKeyCredentialCreationOptions con los parámetros del relying party (RP), restricciones de autenticador y la lista de credenciales existentes.
  2. El navegador invoca navigator.credentials.create() con las opciones recibidas.
  3. El autenticador (dispositivo TEE, Secure Enclave o llave de hardware) genera un nuevo par de claves único para ese dominio.
  4. El autenticador opcionalmente emite un attestation statement que certifica el origen y modelo del dispositivo.
  5. La clave pública se envía al servidor y se almacena. La clave privada nunca sale del autenticador.
// Registro: servidor envía opciones al cliente
const credential = await navigator.credentials.create({
  publicKey: {
    challenge: serverGeneratedChallenge, // Uint8Array de 32 bytes
    rp: {
      name: "CyberFlows",
      id: "cyberflows.uk"
    },
    user: {
      id: new Uint8Array(user.idBytes),
      name: "analista@cyberflows.uk",
      displayName: "Analista de Seguridad"
    },
    pubKeyCredParams: [
      { alg: -7, type: "public-key" },   // ES256 (ECDSA P-256)
      { alg: -257, type: "public-key" }  // RS256 (RSA)
    ],
    authenticatorSelection: {
      residentKey: "required",
      userVerification: "required"
    },
    timeout: 60000,
    attestation: "direct"
  }
});

// credential.rawId contiene la credentialId
// credential.response con attestationObject y clientDataJSON

2. Autenticación (Assertion)

En cada inicio de sesión posterior:

  1. El servidor envía un PublicKeyCredentialRequestOptions con el challenge y la lista de credentialIds registradas para ese usuario.
  2. El navegador invoca navigator.credentials.get().
  3. El autenticador verifica la presencia del usuario (biometría, PIN), localiza la clave privada correspondiente al credentialId y al dominio, y firma digitalmente el challenge.
  4. El servidor verifica la firma usando la clave pública almacenada.
// Autenticación: cliente firma el challenge
const assertion = await navigator.credentials.get({
  publicKey: {
    challenge: serverGeneratedChallenge,
    rpId: "cyberflows.uk",
    allowCredentials: registeredCredentialIds,
    userVerification: "required",
    timeout: 60000
  }
});

// assertion.response.signature contiene la firma sobre
// clientDataJSON.hash || authenticatorData

3. Verificación server-side

El servidor debe validar:

  • Que el authenticatorData contenga el flag UP (User Present) y UV (User Verified).
  • Que la signature sea válida con la clave pública almacenada.
  • Que el challenge coincida con el enviado y no haya expirado.
  • Que el origin en clientDataJSON coincida con el dominio esperado.

Estas validaciones se ejecutan en la lógica del servidor. La clave privada nunca se expone, el challenge es de un solo uso y la autenticación está vinculada criptográficamente al dominio.


Passkeys sincronizadas vs. dedicadas (hardware)

Existen dos categorías principales de passkeys:

Passkeys sincronizadas (Synced)

Almacenadas en el Trusted Execution Environment (TEE) del dispositivo y sincronizadas entre dispositivos del mismo ecosistema vía cifrado E2E en la nube. Ejemplos: iCloud Keychain (Apple), Google Password Manager, 1Password.

Ventajas: experiencia de usuario fluida, recuperación ante pérdida de dispositivo, adopción masiva sin hardware adicional.

Riesgos: dependen de la seguridad del ecosistema del proveedor, attack surface ampliado en la nube, posible vínculo forense entre cuentas a través del sync group.

Passkeys dedicadas (Device-bound / Hardware)

Almacenadas en un chip de seguridad físico que no exporta la clave privada. Ejemplos: YubiKey 5C NFC, Google Titan Security Key.

Ventajas: aislamiento físico absoluto de la clave privada, resistencia a malware y ataques remotos, cumplimiento de altos niveles de assurance (NIST AAL3).

Riesgos: pérdida o daño físico implica pérdida de acceso sin backup, coste por unidad, logística de distribución.

La recomendación práctica para organizaciones en 2026 es adoptar una estrategia híbrida: passkeys sincronizadas para empleados generales (baja fricción, alta adopción) y passkeys de hardware dedicadas para cuentas privilegiadas, administradores y entornos de producción.


¿Por qué las passkeys son inmunes al phishing?

La resistencia al phishing no es una feature opcional. Es una propiedad matemática del protocolo.

Una passkey está vinculada criptográficamente a un rpId (origin específico). Cuando un atacante clona una página de login y la aloja en cyberfl0ws.uk (note el cero), el autenticador rechazará firmar porque el dominio no coincide con el almacenado en la passkey.

A diferencia de un código OTP o un TOTP, que es válido sin importar quién lo solicita, una passkey solo se activa cuando:

  1. El rpId coincide exactamente con el dominio de la passkey.
  2. El usuario verifica su identidad biométrica o con PIN.
  3. El challenge proviene de una sesión legítima con contexto HTTPS válido.

Un atacante no puede extraer la clave privada del autenticador. No puede reenviar la passkey a otro dominio. No puede interceptar la firma y reutilizarla en otra sesión (binding al challenge). El ataque de phishing, tal como se conoce hoy, no tiene vector de explotación contra passkeys.


Casos de uso: empresas, consumo y desarrollo

Empresas y transformación digital

Las organizaciones están desplegando passkeys para eliminar la superficie de ataque de credenciales. Casos documentados:

  • Cloudflare: migró a autenticación FIDO2 con llaves de hardware en 2021, eliminando TOTP y SMS como factores secundarios. En 2024, extidió passkeys sincronizadas para acceso de empleados.
  • GitHub: soporte completo para passkeys desde 2023, con adopción creciente entre contribuidores open-source.
  • Dashlane, 1Password, Bitwarden: gestores de contraseñas que ahora actúan como passkey providers, sincronizando claves entre dispositivos con cifrado E2E.

Consumo y experiencia de usuario

Apple, Google y Microsoft han integrado passkeys a nivel de sistema operativo. Los usuarios de iPhone, Android y Windows 11 ya pueden registrar passkeys que se sincronizan automáticamente entre sus dispositivos. La experiencia de login se reduce a un gesto biométrico (Touch ID, Face ID, huella dangular): sin teclado, sin memoria, sin fricción.

Desarrollo de software

Las APIs de WebAuthn están disponibles en todos los navegadores modernos. Los frameworks más populares (Spring Boot, Node.js/Express, Django, Laravel) cuentan con bibliotecas server-side bien mantenidas. La barrera de entrada para implementar passkeys es menor que nunca.


Cómo implementar passkeys en tu aplicación

La implementación se divide en dos partes: generación de opciones en el servidor y manejo del flujo en el cliente. Aquí mostramos un ejemplo completo usando JavaScript server-side y la API WebAuthn nativa del navegador.

Servidor: registro y verificación

import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse
} from "@simplewebauthn/server";

// Registro: generar opciones
app.post("/api/passkey/register/options", async (req, res) => {
  const user = await getUserById(req.session.userId);

  const options = await generateRegistrationOptions({
    rpName: "CyberFlows",
    rpID: "cyberflows.uk",
    userID: user.id,
    userName: user.email,
    attestationType: "none",
    authenticatorSelection: {
      residentKey: "preferred",
      userVerification: "preferred"
    },
    excludeCredentials: user.passkeys.map(pk => ({
      id: pk.credentialID,
      type: "public-key",
      transports: pk.transports
    }))
  });

  await setCurrentChallengeForUser(user.id, options.challenge);
  res.json(options);
});

// Registro: verificar respuesta
app.post("/api/passkey/register/verify", async (req, res) => {
  const user = await getUserById(req.session.userId);
  const expectedChallenge = await getCurrentChallengeForUser(user.id);

  const verification = await verifyRegistrationResponse({
    response: req.body,
    expectedChallenge,
    expectedOrigin: "https://cyberflows.uk",
    expectedRPID: "cyberflows.uk"
  });

  if (verification.verified && verification.registrationInfo) {
    const { credentialPublicKey, credentialID, counter } =
      verification.registrationInfo;

    await savePasskeyToUser(user.id, {
      credentialID,
      credentialPublicKey,
      counter,
      transports: req.body.response.transports
    });

    res.json({ verified: true });
  } else {
    res.status(400).json({ verified: false });
  }
});

// Autenticación: generar opciones
app.post("/api/passkey/auth/options", async (req, res) => {
  const options = await generateAuthenticationOptions({
    rpID: "cyberflows.uk",
    userVerification: "preferred"
  });

  await setCurrentChallengeForUser(null, options.challenge);
  res.json(options);
});

// Autenticación: verificar firma
app.post("/api/passkey/auth/verify", async (req, res) => {
  const expectedChallenge = await getCurrentChallengeForUser(null);
  const passkey = await findPasskeyById(req.body.id);

  const verification = await verifyAuthenticationResponse({
    response: req.body,
    expectedChallenge,
    expectedOrigin: "https://cyberflows.uk",
    expectedRPID: "cyberflows.uk",
    credential: {
      id: passkey.credentialID,
      publicKey: passkey.credentialPublicKey,
      counter: passkey.counter
    }
  });

  if (verification.verified) {
    await updatePasskeyCounter(passkey.id, verification.authenticationInfo.newCounter);
    req.session.authenticated = true;
    res.json({ verified: true });
  } else {
    res.status(401).json({ verified: false });
  }
});

Cliente: flujo de registro y login

// Registro desde el navegador
async function registerPasskey() {
  const optionsRes = await fetch("/api/passkey/register/options", {
    method: "POST"
  });
  const options = await optionsRes.json();

  const credential = await navigator.credentials.create({
    publicKey: options
  });

  const verifyRes = await fetch("/api/passkey/register/verify", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(credential)
  });

  const result = await verifyRes.json();
  if (result.verified) {
    window.location.href = "/dashboard";
  }
}

// Login con passkey
async function loginWithPasskey() {
  const optionsRes = await fetch("/api/passkey/auth/options", {
    method: "POST"
  });
  const options = await optionsRes.json();

  const assertion = await navigator.credentials.get({
    publicKey: options
  });

  const verifyRes = await fetch("/api/passkey/auth/verify", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(assertion)
  });

  const result = await verifyRes.json();
  if (result.verified) {
    window.location.href = "/dashboard";
  }
}

Dependencias del servidor

npm install @simplewebauthn/server @simplewebauthn/browser

Estado de adopción en 2026

El ecosistema de passkeys ha alcanzado un punto de inflexión en 2026:

  • Apple soporta passkeys de forma nativa en iOS 17+, macOS 14+ y Vision Pro. iCloud Keychain sincroniza passkeys entre todos los dispositivos del usuario con cifrado E2E. Safari, Chrome y Firefox soportan WebAuthn Level 3.
  • Google implementó passkeys como método de inicio de sesión predeterminado en Android y Chrome. Google Password Manager sincroniza passkeys entre dispositivos Android, Windows, macOS y ChromeOS. Los desarrolladores disponen de la Passkey Developer Guide con bibliotecas oficiales.
  • Microsoft integró passkeys en Windows 11 y Microsoft Edge, con soporte para Hello biométrico y sincronización vía Microsoft Authenticator. Enterprise customers pueden desplegar passkeys a través de Microsoft Entra ID.
  • Navegadores: Chrome 117+, Safari 17+, Firefox 128+, Edge 117+ soportan la API completa de WebAuthn Level 3, incluyendo signal API para el management de credenciales.
  • Adopción empresarial: según la FIDO Alliance y sus informes de 2026, más del 68% de las organizaciones están desplegando o planeando desplegar passkeys para empleados, con aproximadamente 5.000 millones de passkeys creadas globalmente. La reducción media en incidentes de credential compromise supera el 90% en organizaciones que completaron la migración.

Conclusión

Las passkeys no son una promesa futura. Son una realidad operativa en 2026, respaldada por los tres ecosistemas de software dominantes, por un estándar W3C ratificado y por una alianza industrial (FIDO) que incluye a prácticamente todas las empresas tecnológicas relevantes.

Para los profesionales de ciberseguridad, la recomendación es clara: evaluar la migración a passkeys en los próximos 12-18 meses. Comenzar por cuentas privilegiadas, extender a empleados y finalmente a usuarios. Las organizaciones que lo hagan no solo reducirán su superficie de ataque: eliminarán una categoría completa de vulnerabilidades que ha dominado los informes de incidentes durante tres décadas.

La contraseña no va a desaparecer de la noche a la mañana. Pero su relevancia como mecanismo de autenticación primario está llegando a su fin. Y eso es algo que la seguridad de la información celebra.

Artículos Relacionados

Hardware RecomendadoLlave FIDO2

Yubico - YubiKey 5C NFC

La llave de hardware FIDO2 más compatible del mercado. Soporta passkeys, FIDO2/WebAuthn y TOTP. Resistente a phishing, malware y ataques MITM.

Hardware RecomendadoFIDO2 / U2F

Google Titan Security Key

Llave de seguridad de Google con soporte FIDO2 y U2F. Chip de seguridad custom con protección contra ataques físicos. Buen precio para despliegues masivos.

Hardware RecomendadoGestor de Passkeys

Authenticators App - 1Password

1Password es uno de los primeros gestores de contraseñas en soportar passkeys sincronizadas. Almacena passkeys en la nube con cifrado E2E y sincroniza entre dispositivos.

Aviso legal: Este contenido es estrictamente educativo. CyberFlows no apoya actividades ilegales. Úsalo solo en entornos autorizados.

[Espacio publicitario — AdSense]

Newsletter de Ciberseguridad

Resumen semanal con los mejores artículos, CVEs críticos y tendencias del mercado. Sin spam.

🔒 Tu email no se compartirá. Puedes darte de baja en cualquier momento.