Ir al contenido
2FAAuth.Work
Idioma: Español
10 min de lectura Por 2faauth.work Política editorial

Cómo funciona TOTP: las matemáticas detrás de tu código de 6 dígitos

Una explicación en lenguaje sencillo de cómo se generan las contraseñas de un solo uso basadas en el tiempo, desde el secreto compartido hasta los seis dígitos que escribes.

Qué es realmente un código TOTP

Abre tu aplicación de autenticación y verás un número de 6 dígitos que cuenta atrás sus 30 segundos de vida. Ese número es un código TOTP: una contraseña de un solo uso basada en el tiempo, estandarizada en RFC 6238 como una extensión del esquema anterior basado en HMAC, HOTP.

La idea clave: el código no es una contraseña. Es la prueba de que posees un secreto compartido — un valor instalado en tu dispositivo cuando configuraste 2FA por primera vez, normalmente al escanear un código QR. Como el código se deriva de ese secreto más la hora actual, cambia cada 30 segundos y no sirve de nada para quien lo vea después de que lo hayas tecleado. Si alguien espía tu pantalla, solo obtiene un número que caduca en segundos.

Nada viaja para que esto funcione. Ni SMS, ni conexión al servidor de inicio de sesión, ni red alguna. Tu aplicación y el servidor realizan el mismo cálculo de forma independiente, con sus propios relojes. Mismas entradas, mismas matemáticas, mismo código — ese es todo el truco. Por eso el TOTP funciona en modo avión: el único requisito es que la hora de tu dispositivo sea aproximadamente correcta, con un margen de tolerancia que suele permitir uno o dos pasos de 30 segundos de desfase.

El secreto compartido: donde todo comienza

Cuando activas la autenticación de dos factores en una cuenta, el servidor genera una clave secreta aleatoria — normalmente 20 bytes de aleatoriedad criptográfica. Esta única cadena de caracteres es la raíz de todo lo que viene después.

El servidor te la entrega una sola vez, normalmente como un código QR. Lo que parece un patrón de píxeles es en realidad solo texto codificado: el secreto en sí, escrito en Base32 (letras A–Z y dígitos 2–7), más el nombre de la cuenta y el emisor. Tu aplicación de autenticación lo escanea, lo decodifica y almacena el secreto localmente. Si el QR falla, casi todos los servicios ofrecen la clave en texto plano para introducirla a mano; escribe los caracteres exactamente como aparecen, sin espacios.

Desde ese momento existen dos copias del mismo secreto: una en el servidor, otra en tu aplicación. Ninguna se vuelve a enviar por la red. Cada código futuro se deriva de este valor compartido — por eso protegerlo importa.

Una captura de pantalla de ese código QR es una copia permanente de tu clave. Cualquiera que la vea puede generar códigos válidos para siempre. Escanéalo y luego bórralo. Si lo guardaste en la papelera de fotos o en la nube, elimínalo también allí. Y si sospechas una fuga, regenera el 2FA desde la configuración de la cuenta: el secreto viejo deja de valer.

El Paso de Tiempo: Un Contador Móvil

TOTP nunca usa el reloj directamente. En cambio, tanto tu aplicación como el servidor toman la marca de tiempo Unix actual (segundos desde 1970) y la dividen entre 30, descartando el resto:

counter = floor(unix_time / 30)

El resultado es un número entero simple que avanza en uno cada 30 segundos. Al momento de escribir esto, ese contador es un número enorme — pero a la aplicación solo le importa que ambas partes calculen el mismo valor. Mismo contador más mismo secreto significa mismo código.

Con un ejemplo se ve claro: si han pasado exactamente 1.234.567.890 segundos desde 1970, el contador es floor(1234567890 / 30), es decir 41.152.263. A los 30 segundos siguientes, 41.152.264. La marca de tiempo cambia cada segundo, pero el contador solo salta una vez por paso; por eso el código es estable durante medio minuto y no parpadea constantemente.

Por Qué la Desincronización del Reloj Rompe los Códigos

Todo el esquema depende del acuerdo. Si el reloj de tu teléfono está desviado 40 segundos, calculas el contador N mientras el servidor ya está en N+1 — códigos diferentes, error de "código inválido", aunque lo hayas escrito perfectamente. Es un error de sincronización, no de tecleo: reintentar con el mismo código fallará igual hasta que corrijas la hora.

Los servidores saben que los relojes se desincronizan, así que la mayoría acepta el código del paso de tiempo anterior o siguiente como ventana de gracia. Más allá de eso, resincroniza el reloj de tu dispositivo (configúralo en automático). Un desvío grande también puede aparecer tras volar entre husos horarios si el teléfono no ajusta la hora automáticamente.

HMAC: Mezclando el Secreto y el Contador

En el corazón de cada código TOTP hay una única función: HMAC-SHA1. HMAC significa Hash-based Message Authentication Code (Código de Autenticación de Mensajes Basado en Hash), y su trabajo aquí es simple — combinar dos entradas en una sola salida impredecible.

Las dos entradas:

  • El secreto compartido — la clave, conocida solo por tu teléfono y el servidor
  • El contador de tiempo — el número que cambia cada 30 segundos

Piensa en HMAC como un hash con clave. Un hash normal mezcla los datos de la misma manera cada vez. HMAC los mezcla de forma distinta según la clave. Alimentarlo con el mismo secreto y el contador 48291 siempre produce el mismo resultado de 20 bytes. Cambia el contador en uno, y la salida se convierte en algo que parece completamente unrelated.

En detalle, HMAC funciona en dos pasos: primero aplica el hash a la clave (combinada con una constante interna) para obtener una clave derivada, y luego calcula el hash del contador usando esa clave derivada. Este doble hashing evita ataques que explotarían las debilidades de un hash simple.

Crucialmente, el proceso solo fluye en una dirección. Ver millones de salidas no le dice a un atacante nada útil sobre el secreto — esa es la propiedad que hace seguro todo el esquema.

Truncamiento: De 20 Bytes a 6 Dígitos

HMAC-SHA-1 produce 20 bytes — mucho más de lo que una pantalla puede mostrar razonablemente. TOTP lo comprime mediante truncamiento dinámico:

  1. Toma el último nibble (los 4 bits más bajos) de la salida HMAC. Esto da un número del 0 al 15.
  2. Úsalo como desplazamiento: toma los 4 bytes que comienzan en esa posición de la salida. Con un desplazamiento de 15, esos 4 bytes llegan justo hasta el final de los 20 — por eso el máximo es 15 y no 16.
  3. Enmascara el bit superior de esos 4 bytes, dejando un entero de 31 bits. Este paso existe porque el bit más alto podría interpretarse como signo en lenguajes que usan enteros con signo; al ponerlo a cero, el resultado siempre es positivo y todas las implementaciones coinciden.

Ese entero sigue siendo demasiado grande para mostrar, así que se reduce módulo 1.000.000. El resto — cualquier valor de 0 a 999.999 — se convierte en el código, rellenado con ceros hasta 6 dígitos. Un resto de 4.207 se muestra como 042207. Omitir el relleno sería un error clásico: un código de 5 dígitos fallaría la validación aunque el cálculo fuera correcto.

Se llama dinámico porque el desplazamiento proviene de los propios datos: cada código muestrea una porción diferente de la salida HMAC.

Algunos servicios usan códigos de 7 u 8 dígitos, reduciendo módulo 10.000.000 o 100.000.000 en su lugar — misma matemática, espacio de códigos mayor, más difícil de adivinar.

Por qué el código es seguro de teclear

Un código de 6 dígitos ofrece un millón de valores posibles — modesto sobre el papel. Lo que lo hace funcionar es que solo tienes unos pocos intentos. Los servidores aplican rate limiting a los intentos de inicio de sesión y rechazan los fallos repetidos, así que un atacante no puede probar combinaciones en bucle. El rate limiting, no la longitud del código, es la verdadera defensa. Por eso conviene comprobar el código antes de enviarlo: tres o cuatro fallos seguidos pueden bloquear temporalmente tu propia cuenta, un error frecuente al teclear deprisa o confundir el dígito 0 con la letra O en apps que mezclan tipografías.

Cada código también es de corta duración. Está ligado a una ventana de 30 segundos y solo se acepta brevemente a su alrededor, así que un código robado caduca antes de poder reutilizarse. Si esperas a que el anillo de progreso de tu app esté casi completo, puede caducar mientras lo tecleas — mejor regenerarlo cuando quede menos de un cuarto de ventana.

Por último, ver un código no revela nada sobre el futuro. El código es la salida de un cálculo HMAC usando tu clave secreta. HMAC es una función unidireccional: dado el resultado, no hay forma de reconstruir la entrada. Un atacante que vislumbre el código de hoy no puede deducir el secreto, ni predecir el código de mañana — porque no conoce el valor de emparejamiento del contador que hay debajo.

Preguntas frecuentes y casos especiales

Por qué fallan los códigos

La causa más común es la desviación del reloj. El reloj de tu teléfono y el del servidor deben mantenerse dentro de la ventana permitida, por lo que un teléfono con la hora incorrecta genera códigos erróneos. Solución: activa la sincronización automática de la hora. Si ya la tienes activa y algo sigue fallando, desactiva y reactiva la sincronización para forzar una corrección; algunos teléfonos muestran la hora "correcta" a simple vista pero difieren por uno o dos minutos, suficiente para romper TOTP.

Un secreto incorrecto también falla en silencio; esto suele ocurrir cuando el código QR se escaneó en la configuración de otra cuenta. El verificador genera códigos válidos pero para el servicio equivocado, y no hay ningún mensaje de error que te avise. Si tienes varias cuentas del mismo servicio, etiqueta cada entrada en tu aplicación con el nombre exacto de la cuenta antes de avanzar.

Por último, los códigos QR pueden caducar: muchos servicios invalidan el código de registro tras un breve período o después de avanzar más allá de la pantalla, por lo que una captura de pantalla antigua no funcionará. Escanea el QR durante la propia configuración, no después.

¿Se puede reutilizar un código?

Sí, técnicamente, pero la mayoría de los servidores rechazan un código que ya fue aceptado dentro del paso de tiempo actual. Esto bloquea a los atacantes que vislumbran un código y lo reutilizan segundos después. Tenlo en cuenta si tú mismo necesitas dos códigos seguidos: espera a que cambie el dígito o usa un código de recuperación.

Si pierdes el dispositivo

El secreto compartido vive solo en tu dispositivo, así que perderlo significa perder el acceso, a menos que hayas guardado los códigos de recuperación al configurar 2FA. Guárdalos sin conexión, en papel o en un lugar físico seguro, no en una nota del propio teléfono. Cada uno suele funcionar una sola vez, así que táchalo al usarlo y cuenta cuántos te quedan.

Poniéndolo Todo Junto: El Flujo Completo

Todo se reduce a un solo pipeline: secreto compartido → contador de tiempo → HMAC → truncamiento → 6 dígitos.

Un ciclo de 30 segundos, de principio a fin:

  • Durante la configuración, escaneas un código QR. Tu app guarda el secreto compartido. Si no puedes escanear, casi todo servicio ofrece el secreto como texto para escribirlo a mano; el resultado es el mismo.
  • A las 12:04:30, la app calcula el paso de tiempo actual (aproximadamente 24.000 si se cuenta desde el Unix epoch en bloques de 30 segundos).
  • HMAC-SHA1 mezcla el secreto con ese contador, produciendo 20 bytes.
  • El truncamiento elige 4 de esos bytes, los convierte en un número y conserva los últimos 6 dígitos: digamos, 482913.
  • Lo escribes. El servidor, que tiene el mismo secreto, ejecuta el cálculo idéntico—permitiendo uno o dos pasos de desfase por diferencias de reloj—y los códigos coinciden. Ese margen existe porque el reloj de tu teléfono y el del servidor nunca serán idénticos al segundo.

Dos hábitos protegen toda la cadena: trata el código QR como una contraseña—nadie más debería verlo, ni una captura de pantalla compartida—y mantén el reloj de tu dispositivo configurado en automático. Un desfase grande es la causa más común de códigos rechazados. Con eso bien hecho, las matemáticas funcionan solas.

Compartir esta guía