TOTP: o que é e como funciona o 2FA do Google Authenticator

TOTP: o que é e como funciona o 2FA do Google Authenticator

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) ou otplib (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 é:

  1. Detectar skew via endpoint /time no servidor de autenticação
  2. Sincronizar cliente e servidor via NTP quando possível
  3. 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.

Y

Yuri Sousa

Front-End Developer / Designer

Desenvolvedor apaixonado por criar experiências digitais acessíveis e visualmente perfeitas. Escrevo sobre desenvolvimento web, design e tecnologia.