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.comno puede usarse enevil-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:
- El servidor genera un challenge criptográfico aleatorio y envía al cliente un
PublicKeyCredentialCreationOptionscon los parámetros del relying party (RP), restricciones de autenticador y la lista de credenciales existentes. - El navegador invoca
navigator.credentials.create()con las opciones recibidas. - El autenticador (dispositivo TEE, Secure Enclave o llave de hardware) genera un nuevo par de claves único para ese dominio.
- El autenticador opcionalmente emite un attestation statement que certifica el origen y modelo del dispositivo.
- 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:
- El servidor envía un
PublicKeyCredentialRequestOptionscon el challenge y la lista de credentialIds registradas para ese usuario. - El navegador invoca
navigator.credentials.get(). - El autenticador verifica la presencia del usuario (biometría, PIN), localiza la clave privada correspondiente al
credentialIdy al dominio, y firma digitalmente el challenge. - 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
authenticatorDatacontenga el flagUP(User Present) yUV(User Verified). - Que la
signaturesea válida con la clave pública almacenada. - Que el
challengecoincida con el enviado y no haya expirado. - Que el
originenclientDataJSONcoincida 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:
- El
rpIdcoincide exactamente con el dominio de la passkey. - El usuario verifica su identidad biométrica o con PIN.
- 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
signalAPI 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
- Qué es Zero Trust (Confianza Cero) — Arquitectura Zero Trust con identidad como nuevo perímetro
- Active Directory y Seguridad Kerberos — Autenticación empresarial y migración de Kerberos a passkeys
- Guía de Phishing e Ingeniería Social — Passkeys eliminan el vector de phishing por diseño
- Deepfakes: Nueva Ingeniería Social — Amenazas de identidad que complementan la protección de passkeys