TOTP: Aqueles Códigos de 6 Dígitos Não São Mágica
A cada 30 segundos, seu app autenticador gera um novo código de 6 dígitos. Sem conexão com internet. Sem comunicação com servidor. Como o servidor sabe qual código você está vendo? Porque ambos os lados estão rodando o mesmo algoritmo com o mesmo segredo.
O Algoritmo
TOTP (Time-Based One-Time Password, RFC 6238) funciona em 4 passos:
- Pegar o tempo atual como timestamp Unix
- Dividir pelo time step (30 segundos por padrão) → isso dá um valor de contador
- HMAC-SHA1 do contador com o segredo compartilhado → produz um hash de 20 bytes
- Truncar o hash para um 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 Que Funciona Sem Internet
Durante o setup, o servidor gera um segredo aleatório e compartilha com seu autenticador (via QR code). Ambos os lados agora têm:
- O mesmo segredo (tipicamente 20 bytes, codificado em Base32)
- O mesmo algoritmo (HMAC-SHA1)
- O mesmo time step (30 segundos)
- Relógios sincronizados (dentro de tolerância razoável)
O servidor não precisa contatar seu celular. Ele apenas roda o mesmo cálculo com o mesmo segredo e verifica se os códigos batem.
O QR Code
Quando você escaneia um QR code TOTP, ele contém uma URI como:
otpauth://totp/MyApp:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=MyApp&algorithm=SHA1&digits=6&period=30| Parâmetro | Valor | Significado |
|---|---|---|
secret | JBSWY3DPEHPK3PXP | Segredo compartilhado codificado em Base32 |
issuer | MyApp | Nome do serviço mostrado no autenticador |
algorithm | SHA1 | Algoritmo HMAC (SHA1/SHA256/SHA512) |
digits | 6 | Tamanho do código (6 ou 8) |
period | 30 | Time step em segundos |
Implementação em JavaScript
// Verificação TOTP em Node.js
import { createHmac } from 'crypto';
function generateTOTP(secret: Buffer, timeStep = 30): string {
const counter = Math.floor(Date.now() / 1000 / timeStep);
// Converter contador para 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();
// Truncamento 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 com janela ±1 para drift de relógio
function verifyTOTP(secret: Buffer, token: string): boolean {
for (let i = -1; i <= 1; i++) {
const adjustedTime = Date.now() + i * 30000;
// ... gerar código no tempo ajustado
if (generatedCode === token) return true;
}
return false;
}Implementação 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}"
# Exemplo
print(generate_totp('JBSWY3DPEHPK3PXP'))
# "482901" (muda a cada 30 segundos)Considerações de Segurança
- Armazene segredos criptografados. O segredo TOTP no seu banco de dados é equivalente a uma senha. Criptografe em repouso.
- Forneça códigos de backup. Usuários perdem celulares. Gere 8-10 códigos de uso único durante o setup.
- Permita drift de relógio. Aceite códigos do time step anterior e próximo (±30 segundos).
- Rate limit na verificação. Sem rate limiting, um atacante tem 1/1.000.000 de chance por tentativa — brute-forceable em alta velocidade.
- Não use SMS. TOTP (app autenticador) é mais seguro que 2FA baseado em SMS, que é vulnerável a SIM swapping.
TOTP vs HOTP
| Feature | TOTP (Baseado em Tempo) | HOTP (Baseado em Contador) |
|---|---|---|
| Contador | Tempo atual / 30 | Inteiro incrementado |
| Expiração | Códigos expiram após 30s | Códigos nunca expiram |
| Problemas de sync | Drift de relógio | Desync de contador |
| Segurança | Melhor (códigos expiram) | Menos seguro (replay possível) |
| Usado por | Google Authenticator, Authy | YubiKey (modo HOTP), alguns tokens físicos |
Gere códigos TOTP: Gerador TOTP — insira uma chave secreta e veja o código ao vivo com timer de contagem regressiva.