Você abre o Google Authenticator, copia aqueles seis dígitos e o servidor aceita. Mas o celular não enviou nada — estava offline. A explicação está num algoritmo de 2008 chamado TOTP, no mesmo HMAC-SHA1 que uso semanalmente para assinar webhooks. Segundo o Eurisko.com.br, o ponto-chave é que ambos os lados já combinaram uma chave e usam o relógio como sincronia. O resto é matemática pura.
O que realmente acontece quando você adiciona uma conta
Quando você escaneia o QR Code no setup, três coisas acontecem silenciosamente: o servidor gera um segredo aleatório (geralmente 160 bits, codificado em Base32), esse segredo é embutido no QR Code como uma URI otpauth://totp/..., e o app guarda o segredo localmente. A partir daí, não existe mais canal de comunicação entre celular e servidor. Eles compartilham apenas uma string.
Quando você digita o código, o servidor gera o mesmo código que o app gerou usando o mesmo segredo e o horário atual. Se bater, autenticou. Simples assim.
A matemática por trás do TOTP (RFC 6238)
TOTP é HOTP com contador baseado em tempo. A fórmula é direta:
- T = floor(tempo_atual_unix / time_step) — geralmente 30 segundos
- HOTP = HMAC-SHA1(segredo, T) com truncamento dinâmico
Resultado = HOTP mod 10^6 — daí os seis dígitos
O truncamento dinâmico é o detalhe que confunde muita gente. Você pega o último byte do HMAC como offset, lê quatro bytes a partir daquela posição, força o bit mais significativo para zero (para evitar bugs com inteiros sinalizados) e aplica o módulo. Isso garante que o resultado tenha exatamente 6 dígitos, sem deixar espaço para manipulação maliciosa.
Implementação real em Python
Na minha experiência, ver o código funcionando apaga qualquer dúvida conceitual. Esta é uma implementação limpa de TOTP que roda em produção em vários sistemas que já mantive:
import hmac
import hashlib
import struct
import time
import base64
def generate_totp(secret_b32: str,
timestamp: int = None,
time_step: int = 30,
digits: int = 6) -> str:
"""
Gera um código TOTP compatível com Google Authenticator.
secret_b32: segredo em Base32 (como aparece no QR Code).
timestamp: epoch em segundos. Se None, usa time.time().
"""
# 1. Decodifica o segredo Base32 (Google usa uppercase, sem padding às vezes)
key = base64.b32decode(secret_b32.upper() + "=" * ((8 - len(secret_b32) % 8) % 8))
# 2. Calcula o contador baseado em tempo
if timestamp is None:
timestamp = int(time.time())
counter = timestamp // time_step
# 3. Empacota o contador como 8 bytes big-endian
counter_bytes = struct.pack(">Q", counter)
# 4. HMAC-SHA1
digest = hmac.new(key, counter_bytes, hashlib.sha1).digest()
# 5. Truncamento dinâmico (RFC 4226)
offset = digest[-1] & 0x0F
code = (
((digest[offset] & 0x7F) << 24) |
((digest[offset + 1] & 0xFF) << 16) |
((digest[offset + 2] & 0xFF) << 8) |
(digest[offset + 3] & 0xFF)
)
return str(code % (10 ** digits)).zfill(digits)
# Exemplo de uso com o mesmo segredo que o Google Authenticator usa
if __name__ == "__main__":
secret = "JBSWY3DPEHPK3PXP" # Segredo de teste do padrão RFC
print(f"Código atual: {generate_totp(secret)}")
Se você colocar esse mesmo segredo (JBSWY3DPEHPK3PXP) no Google Authenticator, vai ver que o código bate exatamente. Esse é o teste que faço em qualquer biblioteca que estou validando antes de colocar em produção.
Por que o app gera códigos sem internet?
Nenhuma requisição sai do celular. O app é, essencialmente, um mini-calculador de HMAC com um relógio interno. Ele só precisa saber o segredo e a hora atual. Já vi desenvolvedores perdendo dias tentando "otimizar" o app sincronizando com servidor — não precisa. O segredo nunca sai do dispositivo (em apps bem feitos) e o servidor também não precisa entrar em contato com você.
Existe uma sutileza aqui: se o relógio do seu celular estiver muito fora do horário real, o código não vai validar. Por isso o servidor costuma aceitar uma janela de tolerância — geralmente ±1 time step, ou seja, o código atual, o anterior e o próximo. Isso evita que um pequeno drift de relógio trave o login.
Comparativo honesto: TOTP vs alternativas
Na prática, quando preciso decidir o que recomendar, não olho só para TOTP. Olho para o ecossistema inteiro:
| Método | Offline | Resistente a phishing | UX no celular |
|---|---|---|---|
| TOTP (Google Authenticator) | Sim | Não — código pode ser pescado | Boa |
| Push (Authy, Duo) | Não | Sim — confirmação no app | Ótima |
| FIDO2 / WebAuthn (YubiKey) | Sim | Sim — origem verificada | Excelente |
| SMS | Não | Não — SS7, SIM swap | Variável |
TOTP é o meio-termo clássico: seguro o suficiente para 95% dos casos, fácil de implementar, mas vulnerável a phishing em tempo real (o famoso proxy reverso que pede o código e já o usa antes de você). WebAuthn resolve isso de vez, mas exige hardware ou biometria. Se eu fosse começar um produto novo hoje, faria TOTP como fallback e WebAuthn como método principal.
Erros comuns que vejo em código de produção
Quando reviso PRs com 2FA implementado, aparecem os mesmos deslizes. Anota aí:
- Sem janela de tolerância no servidor. Aceitar só o time step atual e quebrar a UX. Use ±1 como mínimo, ±2 se o seu público tem relógios ruins.
- Reutilização de código. TOTP permite reuso entre janelas. Em sistemas sensíveis, rejeite o mesmo código duas vezes seguidas.
- Rate limit fraco. 6 dígitos = 1 milhão de combinações. Sem limitação agressiva, brute force é viável em segundos. Limite a 3–5 tentativas por janela.
- Segredo mal armazenado. Já vi devs salvando o Base32 em plaintext no banco. Criptografe em repouso. Se vazarem, todos os 2FA dos usuários viram lixo.
- QR Code gerado pelo frontend sem verificação. Renderize a URI
otpauth://no servidor e nunca confie em label/account digitado pelo usuário sem sanitização. - Implementar TOTP do zero em vez de usar libs auditadas. Eu mostrei o código acima para fins didáticos — em produção, use
pyotp(Python) ouotplib(Node). São poucas linhas, mas a quantidade de bugs sutis em TOTP caseiro é impressionante.
O caso sombrio: clock skew em escala
Em 2024, trabalhei num sistema B2B onde clientes em redes corporativas tinham relógios de servidor atrasados em até 5 minutos por causa de políticas de Active Directory mal configuradas. O resultado: 30% dos 2FA falhavam. A "solução" que vi um dev junior propor foi aumentar a janela para ±10. Não faça isso. Cada janela adicional multiplica a superfície de ataque por 3. O certo é:
- Detectar skew via endpoint
/timeno servidor de autenticação - Sincronizar cliente e servidor via NTP quando possível
- Apenas em último caso, expandir a janela — e logar agressivamente
FAQ — perguntas que devs realmente fazem
O Google Authenticator usa SHA-1 mesmo? Não é fraco?
Sim, usa SHA-1 no HMAC. Mas HMAC-SHA1 aqui é seguro — o ataque de colisão do SHA-1 não afeta HMAC porque a chave é desconhecida. Mesmo assim, o algoritmo aceita SHA-256 e SHA-512 (TOTP-SHA256, TOTP-SHA512). Se você controla ambos os lados, prefira SHA-256.
Por que não usam HMAC-SHA256 por padrão?
Compatibilidade histórica. Google Authenticator foi criado em 2010, quando SHA-1 era o padrão. Migrar agora quebraria milhões de usuários. O risco real é baixo, então ninguém paga o custo da mudança.
Como o app recupera código ao trocar de celular?
No caso do Google Authenticator, por padrão: você perde os códigos. Desde 2023 existe a opção de sincronizar com a conta Google, mas isso muda o modelo de ameaça — agora sua conta Google virou single point of failure. É por isso que recomendo manter uma cópia do segredo (criptografada, obviamente) em local seguro.
TOTP dá pra burlar com phishing?
Sim, com ataque proxy reverso (AiTM). O atacante te leva a uma página falsa, coleta código e usuário em tempo real, e usa antes que expire. Por isso TOTP sozinho não basta — combine com phishing-resistant como WebAuthn, ou implemente verificação de origem.
Vale a pena implementar HOTP (baseado em contador) em vez de TOTP?
Só em casos específicos (tokens hardware offline, automação M2M). Para usuário humano, TOTP é superior porque não tem problema de dessincronia. HOTP exige cuidado com o contador, e quando ele dessincroniza, a dor de cabeça é real.
Veredito
TOTP é elegante justamente porque é matemática determinística compartilhada. Nenhum dado precisa trafegar, nenhum servidor precisa te conhecer além do segredo combinado. Quando você entende o algoritmo, ele deixa de parecer mágica e vira só mais uma ferramenta no cinto de utilidades — que, aliás, vale a pena ter.
Se você vai implementar 2FA num produto, comece com pyotp ou otplib, leia a RFC 6238 inteira (são 16 páginas, vale o tempo) e adicione testes contra vetores conhecidos. Testar contra o próprio Google Authenticator é o canário que uso em qualquer deploy novo.
Gostou? Me segue no GitHub e deixa um comentário se tiver dúvida ou quiser aprofundar algum ponto.