TOTP: Esos Códigos de 6 Dígitos No Son Magia
Cada 30 segundos, tu app autenticador genera un nuevo código de 6 dígitos. Sin conexión a internet. Sin comunicación con servidor. ¿Cómo sabe el servidor qué código estás viendo? Porque ambos lados están ejecutando el mismo algoritmo con el mismo secreto.
El Algoritmo
TOTP (Time-Based One-Time Password, RFC 6238) funciona en 4 pasos:
- Obtener el tiempo actual como timestamp Unix
- Dividir por el time step (30 segundos por defecto) → esto da un valor de contador
- HMAC-SHA1 del contador con el secreto compartido → produce un hash de 20 bytes
- Truncar el hash a un número de 6 dígitos
// Pseudocódigo
time_step = floor(unix_timestamp / 30)
hmac = HMAC-SHA1(secret, time_step)
offset = hmac[19] & 0x0f
code = (hmac[offset..offset+3] & 0x7fffffff) % 1000000
// Resultado: código de 6 dígitos como "482901"Por Qué Funciona Sin Internet
Durante el setup, el servidor genera un secreto aleatorio y lo comparte con tu autenticador (vía QR code). Ambos lados ahora tienen:
- El mismo secreto (típicamente 20 bytes, codificado en Base32)
- El mismo algoritmo (HMAC-SHA1)
- El mismo time step (30 segundos)
- Relojes sincronizados (dentro de tolerancia razonable)
El servidor no necesita contactar tu celular. Solo ejecuta el mismo cálculo con el mismo secreto y verifica si los códigos coinciden.
El QR Code
Cuando escaneas un QR code TOTP, contiene una URI como:
otpauth://totp/MyApp:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=MyApp&algorithm=SHA1&digits=6&period=30| Parámetro | Valor | Significado |
|---|---|---|
secret | JBSWY3DPEHPK3PXP | Secreto compartido codificado en Base32 |
issuer | MyApp | Nombre del servicio mostrado en el autenticador |
algorithm | SHA1 | Algoritmo HMAC (SHA1/SHA256/SHA512) |
digits | 6 | Longitud del código (6 u 8) |
period | 30 | Time step en segundos |
Implementación en JavaScript
// Verificación TOTP en Node.js
import { createHmac } from 'crypto';
function generateTOTP(secret: Buffer, timeStep = 30): string {
const counter = Math.floor(Date.now() / 1000 / timeStep);
// Convertir contador a buffer big-endian de 8 bytes
const counterBuffer = Buffer.alloc(8);
counterBuffer.writeBigInt64BE(BigInt(counter));
// HMAC-SHA1
const hmac = createHmac('sha1', secret).update(counterBuffer).digest();
// Truncamiento dinámico
const offset = hmac[hmac.length - 1] & 0x0f;
const code = (
((hmac[offset] & 0x7f) << 24) |
((hmac[offset + 1] & 0xff) << 16) |
((hmac[offset + 2] & 0xff) << 8) |
(hmac[offset + 3] & 0xff)
) % 1000000;
return code.toString().padStart(6, '0');
}
// Verificar con ventana ±1 para drift de reloj
function verifyTOTP(secret: Buffer, token: string): boolean {
for (let i = -1; i <= 1; i++) {
const adjustedTime = Date.now() + i * 30000;
// ... generar código en tiempo ajustado
if (generatedCode === token) return true;
}
return false;
}Implementación Python
import hmac
import hashlib
import struct
import time
import base64
def generate_totp(secret_b32: str, time_step: int = 30) -> str:
secret = base64.b32decode(secret_b32)
counter = int(time.time()) // time_step
counter_bytes = struct.pack('>Q', counter)
hmac_hash = hmac.new(secret, counter_bytes, hashlib.sha1).digest()
offset = hmac_hash[-1] & 0x0f
code = (
struct.unpack('>I', hmac_hash[offset:offset + 4])[0] & 0x7fffffff
) % 1000000
return f"{code:06d}"
# Ejemplo
print(generate_totp('JBSWY3DPEHPK3PXP'))
# "482901" (cambia cada 30 segundos)Consideraciones de Seguridad
- Almacena secretos cifrados. El secreto TOTP en tu base de datos es equivalente a una contraseña. Cífralo en reposo.
- Proporciona códigos de respaldo. Los usuarios pierden celulares. Genera 8-10 códigos de uso único durante el setup.
- Permite drift de reloj. Acepta códigos del time step anterior y siguiente (±30 segundos).
- Rate limit en la verificación. Sin rate limiting, un atacante tiene 1/1.000.000 de probabilidad por intento — brute-forceable a alta velocidad.
- No uses SMS. TOTP (app autenticador) es más seguro que 2FA basado en SMS, que es vulnerable a SIM swapping.
TOTP vs HOTP
| Feature | TOTP (Basado en Tiempo) | HOTP (Basado en Contador) |
|---|---|---|
| Contador | Tiempo actual / 30 | Entero incrementado |
| Expiración | Códigos expiran después de 30s | Códigos nunca expiran |
| Problemas de sync | Drift de reloj | Desync de contador |
| Seguridad | Mejor (códigos expiran) | Menos seguro (replay posible) |
| Usado por | Google Authenticator, Authy | YubiKey (modo HOTP), algunos tokens físicos |
Genera códigos TOTP: Generador TOTP — ingresa una clave secreta y ve el código en vivo con temporizador de cuenta regresiva.