Como o TOTP funciona: o algoritmo por trás dos códigos do seu aplicativo autenticador
Algumas vezes por semana você digita um código de 6 dígitos do seu aplicativo autenticador e ele bate com o que o servidor espera, mesmo que o celular e o servidor nunca conversem entre si. Então como o TOTP funciona? O mesmo segredo compartilhado produz um número novo a cada 30 segundos, e cada lado calcula esse número sozinho, com um algoritmo pequeno e determinístico. Nenhum código trafega pela rede. Nenhum servidor central distribui o número.
O TOTP (Time-based One-Time Password), definido na RFC 6238, transforma um segredo compartilhado mais o horário atual em um código numérico curto: ele roda um HMAC sobre o tempo e trunca o resultado. Como a autenticação de dois fatores (2FA) depende de os dois lados calcularem o mesmo valor sem nunca trocá-lo, esse algoritmo carrega sozinho todo o modelo de confiança.
Este guia percorre o algoritmo inteiro com números concretos e depois cobre a parte que a maioria das explicações deixa de fora: como um servidor verifica um código na prática, e o que a 2FA barra e o que não barra. Você pode calcular um código ao vivo no nosso gerador de TOTP enquanto lê.
O que é o TOTP, na prática?
O TOTP (Time-based One-Time Password), definido na RFC 6238, é um algoritmo que combina um segredo compartilhado com o horário atual para produzir um código curto que muda em um intervalo fixo. Tanto o aplicativo autenticador quanto o servidor guardam o mesmo segredo, leem o mesmo relógio e executam a mesma matemática, então chegam ao mesmo código sem nunca transmiti-lo.
Esse último ponto é o que faz o TOTP funcionar. Durante a configuração, só o segredo é enviado, nunca um código, e daí em diante cada lado deriva os próprios códigos. Na rede só há duas coisas para interceptar: o segredo no momento do cadastro e os 6 dígitos que o usuário digita no login. Pense em três entradas que se condensam em uma saída:
| Entrada | Papel | Valor típico |
|---|---|---|
| Segredo compartilhado | A chave de longa duração, acordada uma vez no cadastro | JBSWY3DPEHPK3PXP (Base32) |
| Passo de tempo | O contador que avança | janela de 30 segundos |
| Saída | O código curto derivado dos dois | 324550 |
O segredo costuma ser escrito em Base32 (as letras A–Z e os dígitos 2–7) porque esse alfabeto não distingue maiúsculas de minúsculas e sobrevive a ser impresso, digitado ou colocado num QR code. Você cadastra um segredo escaneando uma URI otpauth://, que pode virar um QR code de autenticador, ou digitando a string Base32 à mão.
TOTP vs HOTP vs SMS vs passkeys: o panorama da 2FA
O TOTP é uma opção entre várias, e para escolher bem ajuda ver onde ele se encaixa em relação às outras. A relação mais curta de guardar: o TOTP é o HOTP com o contador substituído pelo número de passos de tempo desde o epoch Unix. O resto é um equilíbrio entre resistência a phishing, conveniência e a infraestrutura de que você precisa.
| Mecanismo | Motor | Tempo de vida do código | Resistente a phishing? | Precisa de rede? | Uso típico |
|---|---|---|---|---|---|
| HOTP (RFC 4226) | Contador incremental | Até ser usado | Não | Não | Tokens de hardware, legado |
| TOTP (RFC 6238) | Horário atual | ~30 segundos | Não | Não (após o cadastro) | Aplicativos autenticadores |
| SMS OTP | Servidor envia um código | Alguns minutos | Não | Sim (celular) | Alternativa para consumidores |
| Aprovação por push | Aviso do servidor ao dispositivo | Por solicitação | Parcialmente | Sim | 2FA por aplicativo |
| Passkey / FIDO2 | Desafio de chave pública | Por solicitação | Sim (vinculado à origem) | Sim | Contas modernas |
Repare no padrão dessa tabela. TOTP e HOTP funcionam offline depois de cadastrados, o que os torna resilientes e privados, mas nenhum dos dois resiste a phishing sozinho: uma página falsa convincente pode pedir o código e repassá-lo. O SMS acrescenta um canal de rede com sua própria superfície de ataque. As passkeys fecham a brecha de phishing ao vincular a credencial à origem do site, e por isso são para onde a indústria está indo. O TOTP fica num meio-termo confortável, forte e onipresente sem custar nada, e é por isso que continua tão comum.
Como o algoritmo do TOTP funciona, passo a passo
O algoritmo inteiro são quatro passos. Vamos rodar cada um com o segredo de teste da RFC JBSWY3DPEHPK3PXP e um horário Unix fixo de 1700000000, para que você possa reproduzir cada número.
- Decodifique o segredo Base32 para os bytes brutos da chave.
- Calcule o contador de passo de tempo a partir do horário Unix atual.
- Aplique o HMAC ao contador com a chave secreta.
- Trunque o digest para um código de 6 dígitos.
Passo 1 — Decodificar o segredo Base32 para bytes
O Base32 empacota 5 bits em cada caractere, e o decodificador reagrupa esses caracteres de volta em bytes de 8 bits. O segredo JBSWY3DPEHPK3PXP decodifica para os 10 bytes brutos 48 65 6c 6c 6f 21 de ad be ef. A chave do HMAC é esse vetor de bytes, não a string imprimível.
Passo 2 — Calcular o contador de passo de tempo
O contador é o número de passos de tempo inteiros decorridos desde um ponto de partida: T = floor((unixTime − T0) / period). Os padrões da RFC são T0 = 0 (o epoch Unix) e period = 30. Com unixTime = 1700000000, temos T = floor(1700000000 / 30) = 56666666. Esse inteiro vira um valor big-endian de 8 bytes: 00 00 00 00 03 60 aa 2a. Como o contador só muda quando começa uma nova janela de 30 segundos, cada código fica estável durante uma janela inteira e depois salta.
Passo 3 — Aplicar o HMAC ao contador com o segredo
O algoritmo roda um HMAC-SHA1 sobre o contador de 8 bytes usando os bytes do segredo como chave. O HMAC é uma função unidirecional com chave: sem o segredo, você não reverte o digest nem forja um válido, e por isso o código não dá para falsificar. Para as nossas entradas, o digest são os 20 bytes 1d 70 6e 94 1a c7 6b 6d 4a 46 dd 6f af a4 5f e3 35 11 bf 86.
Passo 4 — Truncamento dinâmico para um código de 6 dígitos (RFC 4226)
Um digest de 20 bytes é longo demais para digitar, então o truncamento dinâmico da RFC 4226 extrai um número dele. Pegue o nibble inferior do último byte como deslocamento: o último byte é 0x86, cujo nibble inferior é 6, logo o deslocamento é 6. Leia 4 bytes a partir desse deslocamento (6b 6d 4a 46), zere o bit mais alto do primeiro para manter o número positivo, e você fica com o inteiro 1802324550. Reduza módulo 10^6 e preencha com zeros à esquerda: 1802324550 % 1000000 = 324550. Esse é o código que o seu aplicativo mostra para este segredo neste instante.
O algoritmo em JavaScript, usando a API nativa Web Crypto do navegador, sem dependências. Cada comentário liga um bloco a um dos quatro passos acima:
// TOTP per RFC 6238 — SHA-1, 6 digits, 30s period (the defaults).
async function generateTotp(base32Secret, unixTime = Date.now() / 1000) {
// Step 1: decode the Base32 secret (A-Z, 2-7) to raw key bytes.
const alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let bits = '';
for (const ch of base32Secret.replace(/=+$/, '').toUpperCase()) {
bits += alpha.indexOf(ch).toString(2).padStart(5, '0');
}
const keyBytes = new Uint8Array(
bits.match(/.{8}/g).map((b) => parseInt(b, 2)));
// Step 2: counter = number of 30s steps since the epoch (8-byte big-endian).
let counter = Math.floor(unixTime / 30);
const msg = new Uint8Array(8);
for (let i = 7; i >= 0; i--) {
msg[i] = counter & 0xff;
counter = Math.floor(counter / 256);
}
// Step 3: HMAC-SHA1 the counter with the secret key.
const key = await crypto.subtle.importKey(
'raw', keyBytes, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
const hmac = new Uint8Array(await crypto.subtle.sign('HMAC', key, msg));
// Step 4: dynamic truncation (RFC 4226) -> 6-digit code.
const offset = hmac[hmac.length - 1] & 0x0f;
const binary = ((hmac[offset] & 0x7f) << 24) | (hmac[offset + 1] << 16) |
(hmac[offset + 2] << 8) | hmac[offset + 3];
return (binary % 1_000_000).toString().padStart(6, '0');
}
const code = await generateTotp('JBSWY3DPEHPK3PXP', 1700000000);
console.log(code); // -> "324550"
O mesmo algoritmo em Python, usando apenas a biblioteca padrão (hmac e struct):
import base64, hmac, hashlib, struct, time
def totp(secret, for_time=None, period=30, digits=6, digest='sha1'):
if for_time is None:
for_time = time.time()
# Step 1: Base32-decode the secret to raw key bytes.
key = base64.b32decode(secret.upper())
# Step 2: counter = number of time steps since the epoch (8-byte big-endian).
counter = int(for_time // period)
msg = struct.pack(">Q", counter)
# Step 3: HMAC the counter with the secret.
h = hmac.new(key, msg, digest).digest()
# Step 4: dynamic truncation (RFC 4226) -> N-digit code.
offset = h[-1] & 0x0f
binary = ((h[offset] & 0x7f) << 24 |
(h[offset + 1] & 0xff) << 16 |
(h[offset + 2] & 0xff) << 8 |
(h[offset + 3] & 0xff))
return str(binary % (10 ** digits)).zfill(digits)
print(totp('JBSWY3DPEHPK3PXP', 1700000000)) # -> 324550
As duas implementações imprimem 324550 para o nosso horário fixo, e as duas reproduzem os vetores de teste oficiais da RFC 6238 (por exemplo, o vetor SHA-1 em T = 59 dá 94287082). Se você trocar SHA-1 por SHA-256 ou SHA-512, ou mudar a quantidade de dígitos, o verificador do outro lado precisa fazer exatamente as mesmas escolhas, senão os códigos nunca vão coincidir.
Verificando um código TOTP no servidor
Gerar um código é metade do sistema. A outra metade é o servidor decidir se aceita os 6 dígitos que o usuário acabou de digitar, e é nesse passo que moram as decisões sensíveis à segurança.
O servidor não guarda códigos. Ele guarda o segredo e, no momento do login, recalcula o código esperado a partir desse segredo e do horário atual, e aí compara. A complicação é a deriva de relógio: o dispositivo do usuário e o servidor quase nunca concordam no segundo exato, então uma verificação de igualdade estrita rejeitaria códigos perto da fronteira de uma janela. A saída é uma pequena janela de validação. Aceite o passo atual e um passo de cada lado, ou seja, confira os códigos dos contadores T−1, T e T+1. Uma janela mais larga tolera mais deriva, mas amplia a superfície de adivinhação, por isso a janela 1 (uma tolerância de ±30 segundos) é o equilíbrio mais comum. É essa mesma tolerância de ±1 passo que você vê na aba Verificar da ferramenta.
import { createHmac, timingSafeEqual } from 'crypto';
function verifyTotp(secret, code, { window = 1, period = 30, digits = 6 } = {}) {
const counter = Math.floor(Date.now() / 1000 / period);
const submitted = Buffer.from(code);
// Check the current step and ±window steps for clock drift.
for (let i = -window; i <= window; i++) {
const expected = Buffer.from(totpAt(secret, counter + i, digits));
// Constant-time compare so timing can't leak a partial match.
if (expected.length === submitted.length &&
timingSafeEqual(expected, submitted)) {
return counter + i; // matched step — store it to block replay
}
}
return false;
}
Mais dois detalhes separam o que apenas “funciona” do que é “seguro”. Primeiro, prevenção de replay: guarde o último contador que você aceitou para cada usuário e rejeite qualquer código de um passo igual ou anterior, para que um código capturado uma vez não sirva de novo dentro da mesma janela. Por isso verifyTotp devolve o passo correspondente, e não um simples true. Segundo, limitação de taxa: um código de 6 dígitos é um entre um milhão de valores, e uma janela de ±1 deixa três deles válidos a cada instante, então, sem throttling, um atacante pode fazer força bruta no espaço inteiro. Bloqueie a conta ou aplique backoff depois de um punhado de falhas. E o segredo é uma chave de longa duração: criptografe-o em repouso, deixe-o fora do controle de versão e trate-o como uma senha. Gere códigos de recuperação fortes junto com ele, pensando no dia em que algum dispositivo sumir.
Contra o que o TOTP protege — e contra o que não protege
O TOTP é um avanço real em relação a usar só senha, mas não é mágica, e as páginas de marketing costumam pular por cima das lacunas. A divisão real é esta.
| O TOTP barra | O TOTP NÃO barra |
|---|---|
| Senhas vazadas ou reutilizadas | Phishing em tempo real / adversário no meio |
| Credential stuffing | Malware que lê o segredo do dispositivo |
| Força bruta remota de senha | Fluxos frágeis de recuperação de conta que pulam a 2FA |
| Um vazamento de banco que expõe só hashes de senha | (isso exige outras defesas) |
Os ganhos são grandes. Como o login passa a exigir um código que só o segredo consegue produzir, uma senha vazada deixa de bastar, o que corta de cara o credential stuffing e a força bruta remota. Se o seu banco de dados vazar, mas os segredos TOTP estiverem criptografados em repouso, o atacante ainda não consegue cunhar códigos.
As lacunas são tão reais quanto. Um proxy de phishing em tempo real (uma página de adversário no meio) pode mostrar ao usuário uma réplica perfeita, capturar o código ao vivo e repassá-lo ao site verdadeiro dentro da mesma janela. O TOTP não tem como perceber que o código foi digitado no lugar errado. Um malware no dispositivo que rouba o segredo o derrota por completo, e um fluxo desleixado de “esqueci minha 2FA” pode contorná-lo de vez. Vale desfazer uma confusão comum: ataques de troca de SIM derrotam os códigos únicos por SMS, não o TOTP, porque o TOTP não tem nenhum canal de número de telefone, então não há nada para um atacante redirecionar.
E daí para frente? Passkeys e FIDO2/WebAuthn são vinculados à origem, então resistem a phishing por construção: a credencial simplesmente se recusa a autenticar no domínio errado. Trate o TOTP como um degrau forte e disponível em qualquer lugar acima das senhas, não como o ponto de chegada. Ele se encaixa com o resto da sua pilha de autenticação: veja boas práticas de segurança com JWT para a camada de token de sessão que vem em cima de um login verificado, e hashing de senha (bcrypt vs Argon2) para a camada de senha em repouso que a 2FA complementa.
Armadilhas comuns ao implementar o TOTP
A maioria dos bugs de TOTP não está no algoritmo, que a RFC já fixa, mas na fiação em volta dele. Estes são os que mais pegam quem implementa.
- Deriva do relógio do servidor. Se o servidor não estiver rodando NTP, a noção dele de “agora” se afasta da do dispositivo do usuário e os códigos param de coincidir para todo mundo. Ligue a sincronização de tempo por rede em todos os hosts.
- Segredos em texto puro ou commitados. Um segredo num arquivo de configuração que foi parar no git é uma porta dos fundos permanente. Guarde-o criptografado num gerenciador de segredos, nunca no controle de versão.
- Sem proteção contra replay. Se você aceita um código sem registrar em que passo ele coincidiu, o mesmo código funciona de novo dentro da janela. Persista o último passo usado por usuário e rejeite a reutilização.
- Uma janela larga ou estreita demais. Larga demais multiplica os códigos adivinháveis e enfraquece a segurança; estreita demais rejeita usuários legítimos por causa de uma deriva mínima. A janela 1 é o equilíbrio de praxe.
- Parâmetros incompatíveis. Se o cadastro grava SHA-256 e 8 dígitos na URI
otpauth://, mas o verificador assume SHA-1 e 6 dígitos, nenhum código vai validar nunca. Leia o algoritmo, os dígitos e o período da URI e use-os dos dois lados. - Sem códigos de backup ou recuperação. Quando um celular se perde, o único caminho de volta é uma rota de recuperação. Emita códigos de recuperação na configuração e deixe-os tão fortes quanto a conta exigir; a mesma lógica por trás da entropia de senha vale para segredos de recuperação.
Perguntas frequentes
O TOTP é à prova de phishing?
Não. O TOTP barra senhas vazadas e força bruta remota, mas um proxy de phishing em tempo real pode mostrar um login falso, capturar o código ao vivo e repassá-lo ao site verdadeiro dentro da mesma janela de 30 segundos. Quem resolve o phishing são as passkeys e o FIDO2, que vinculam a credencial à origem do site.
O TOTP é mais seguro que a 2FA por SMS?
Sim. Os códigos por SMS trafegam pela rede celular e podem ser interceptados via troca de SIM ou ataques SS7, e dependem da segurança da sua operadora. O TOTP não tem nenhum canal de número de telefone e nunca transmite o código, então não há nada a interceptar em trânsito. O segredo é trocado uma única vez, na configuração.
O que acontece se eu perder o celular ou o aplicativo autenticador?
Você precisa de um backup preparado com antecedência. As opções são códigos de recuperação salvos quando você configurou a 2FA, um segundo dispositivo cadastrado com o mesmo segredo, ou o segredo Base32 original guardado em algum lugar seguro. Sem uma dessas, perder o dispositivo significa ficar trancado para fora da conta.
Como um servidor verifica um código TOTP?
Ele recalcula o código esperado a partir do segredo compartilhado e do horário atual, depois confere o código enviado contra o passo de tempo atual e um passo de cada lado para permitir a deriva de relógio. Ele também registra qual passo coincidiu, para que o mesmo código não possa ser repetido, e limita a taxa de tentativas para barrar a adivinhação.
Por que os códigos TOTP mudam a cada 30 segundos?
Trinta segundos é o período padrão da RFC 6238: longo o bastante para ler e digitar o código com tranquilidade, curto o bastante para que um código capturado por um atacante expire quase de imediato. Alguns sistemas usam um período de 60 segundos, que a URI otpauth:// registra para que o verificador o respeite.
Dois dispositivos podem compartilhar um mesmo segredo TOTP?
Sim. Qualquer dispositivo com o mesmo segredo Base32 e o relógio sincronizado gera códigos idênticos, porque o algoritmo é determinístico. É assim que funcionam os backups de autenticador em vários dispositivos, e também por isso o segredo precisa continuar privado: quem o copia consegue produzir todos os códigos futuros.
O TOTP é a mesma coisa que o Google Authenticator?
Não. O TOTP é o algoritmo aberto definido na RFC 6238. Google Authenticator, Authy e 1Password são aplicativos que o implementam. Como o padrão é compartilhado, qualquer aplicativo compatível funciona com qualquer serviço que use TOTP, sem prender você a um fornecedor específico.
Conclusão
As ideias centrais são curtas o bastante para caber na cabeça:
- O TOTP transforma um segredo compartilhado mais o horário atual em um código, via HMAC e truncamento.
- Os dois lados calculam o código por conta própria; ele nunca é enviado pela rede.
- Verifique com uma janela de ±1 passo, mais proteção contra replay e limitação de taxa.
- Ele barra ataques de senha, mas não o phishing em tempo real, onde as passkeys entram.
- Mantenha os relógios do servidor sincronizados com NTP e o segredo criptografado e privado.
Quer ver o algoritmo cuspir números reais e conferir sua própria janela de verificação? Abra o gerador de TOTP / 2FA para calcular, configurar e verificar códigos inteiramente no navegador, com o segredo nunca saindo do seu dispositivo.