Autenticação de dois fatores (2FA): implementando TOTP
1. Fundamentos do TOTP (Time-based One-Time Password)
TOTP (Time-based One-Time Password) é um mecanismo de autenticação de dois fatores que gera senhas temporárias baseadas no tempo. Diferentemente do HOTP (HMAC-based One-Time Password), que usa um contador incremental, o TOTP utiliza o timestamp Unix dividido por um intervalo fixo (geralmente 30 segundos) como entrada para o algoritmo.
Os componentes essenciais do TOTP são:
- Chave secreta: valor criptográfico compartilhado entre servidor e cliente
- Timestamp: tempo atual do sistema (Unix epoch)
- Intervalo de tempo: janela padrão de 30 segundos para validade do código
O TOTP é definido pela RFC 6238, que estende o algoritmo HOTP (RFC 4226) substituindo o contador incremental por um contador baseado no tempo. Ambos utilizam HMAC-SHA1 como função hash subjacente.
2. Geração Segura da Chave Secreta
A segurança do TOTP começa com a geração da chave secreta. É obrigatório utilizar um gerador criptograficamente seguro (CSPRNG - Cryptographically Secure Pseudo-Random Number Generator).
// Geração segura da chave em Node.js
const crypto = require('crypto');
function generateSecretKey(length = 20) {
// CSPRNG: crypto.randomBytes é seguro para uso criptográfico
const randomBytes = crypto.randomBytes(length);
return randomBytes;
}
A chave gerada deve ser codificada em Base32 para exibição ao usuário e armazenamento:
// Codificação Base32 da chave secreta
function encodeBase32(secretKey) {
// Buffer para Base32 (usando biblioteca como 'base32-encode')
const base32 = require('base32-encode');
return base32(secretKey, 'RFC4648', { padding: false });
}
No servidor, a chave secreta deve ser armazenada de forma segura:
- Criptografada com AES-256 antes de persistir no banco de dados
- Em módulos de segurança de hardware (HSM) para ambientes críticos
- Associada exclusivamente ao usuário, sem exposição em logs
3. Implementação do Lado do Servidor
O cálculo do código TOTP segue etapas precisas definidas pela RFC 6238:
// Implementação completa do TOTP no servidor
function generateTOTP(secretKey, timeStep = 30, digits = 6) {
// 1. Cálculo do contador de tempo
const timestamp = Math.floor(Date.now() / 1000);
const counter = Math.floor(timestamp / timeStep);
// 2. Converter contador para buffer de 8 bytes (big-endian)
const counterBuffer = Buffer.alloc(8);
counterBuffer.writeBigUInt64BE(BigInt(counter));
// 3. Aplicar HMAC-SHA1
const hmac = crypto.createHmac('sha1', secretKey);
hmac.update(counterBuffer);
const hmacResult = hmac.digest();
// 4. Extrair offset (últimos 4 bits do último byte)
const offset = hmacResult[hmacResult.length - 1] & 0x0f;
// 5. Extrair 4 bytes a partir do offset
const binaryCode = (hmacResult[offset] & 0x7f) << 24 |
(hmacResult[offset + 1] & 0xff) << 16 |
(hmacResult[offset + 2] & 0xff) << 8 |
(hmacResult[offset + 3] & 0xff);
// 6. Gerar código de 6 dígitos
const otp = binaryCode % Math.pow(10, digits);
return otp.toString().padStart(digits, '0');
}
4. Provisionamento do Dispositivo do Usuário
Para configurar o autenticador no dispositivo do usuário, é necessário gerar um URI no formato otpauth://:
// Geração do URI de configuração
function generateOTPAuthURI(issuer, user, secret, algorithm = 'SHA1', digits = 6, period = 30) {
const encodedIssuer = encodeURIComponent(issuer);
const encodedUser = encodeURIComponent(user);
const encodedSecret = encodeBase32(secret);
return `otpauth://totp/${encodedIssuer}:${encodedUser}` +
`?secret=${encodedSecret}` +
`&issuer=${encodedIssuer}` +
`&algorithm=${algorithm}` +
`&digits=${digits}` +
`&period=${period}`;
}
O QR Code pode ser gerado com bibliotecas como qrcode:
// Geração de QR Code para escaneamento
const QRCode = require('qrcode');
async function displayQRCode(otpauthURI) {
const qrCodeDataURL = await QRCode.toDataURL(otpauthURI);
// Exibir qrCodeDataURL como imagem para o usuário
return qrCodeDataURL;
}
Como alternativa, exiba a chave secreta em Base32 para digitação manual:
// Exibição da chave secreta para configuração manual
const manualKey = encodeBase32(secretKey);
console.log(`Chave secreta (digite manualmente no autenticador): ${manualKey}`);
5. Validação e Tratamento de Janelas de Tempo
A validação deve considerar possíveis dessincronizações de relógio entre servidor e cliente:
// Validação com janela de tolerância (skew)
function validateTOTP(token, secretKey, windowSize = 1) {
const timeStep = 30;
const currentTime = Math.floor(Date.now() / 1000);
const currentCounter = Math.floor(currentTime / timeStep);
// Verificar códigos dentro da janela de tolerância
for (let i = -windowSize; i <= windowSize; i++) {
const counter = currentCounter + i;
const expectedToken = generateTOTPFromCounter(secretKey, counter);
if (expectedToken === token) {
return {
valid: true,
counter: counter,
timeUsed: currentTime
};
}
}
return { valid: false };
}
Para prevenir reutilização do mesmo código, implemente tracking do timestamp do último uso:
// Prevenção de reutilização
function validateWithReusePrevention(token, secretKey, userRecord) {
const result = validateTOTP(token, secretKey);
if (result.valid) {
// Verificar se o código não foi usado anteriormente
if (result.timeUsed <= userRecord.lastTOTPTime) {
return { valid: false, reason: 'Código já utilizado' };
}
// Atualizar timestamp do último uso
userRecord.lastTOTPTime = result.timeUsed;
return { valid: true };
}
return { valid: false, reason: 'Código inválido' };
}
6. Fluxo Completo de Autenticação com TOTP
O fluxo completo de autenticação de dois fatores segue estas etapas:
- Login primário: usuário fornece usuário e senha
- Verificação de 2FA: se o 2FA está ativo, solicitar código TOTP
- Validação do código: servidor valida o token TOTP
- Concessão de acesso: se válido, criar sessão autenticada
// Fluxo completo de autenticação
async function authenticateWith2FA(username, password, totpToken) {
// Etapa 1: Validar credenciais primárias
const user = await validateCredentials(username, password);
if (!user) {
return { success: false, error: 'Credenciais inválidas' };
}
// Etapa 2: Verificar se 2FA está ativo
if (user.twoFactorEnabled) {
// Etapa 3: Validar código TOTP
const totpResult = validateWithReusePrevention(totpToken, user.totpSecret, user);
if (!totpResult.valid) {
return { success: false, error: 'Código 2FA inválido' };
}
}
// Etapa 4: Atualizar metadados e criar sessão
user.lastLoginAt = new Date();
await user.save();
return {
success: true,
session: createSession(user),
metadata: {
twoFactorEnabled: user.twoFactorEnabled,
activatedAt: user.twoFactorActivatedAt
}
};
}
Códigos de recuperação (backup codes) devem ser gerados e armazenados durante a ativação do 2FA:
// Geração de códigos de recuperação
function generateBackupCodes(count = 10) {
const codes = [];
for (let i = 0; i < count; i++) {
const code = crypto.randomBytes(4).toString('hex').toUpperCase();
codes.push({
code: code,
used: false,
hashedCode: crypto.createHash('sha256').update(code).digest('hex')
});
}
return codes;
}
7. Considerações de Segurança e Boas Práticas
Proteção contra timing attacks: Utilize comparação de strings em tempo constante para evitar vazamento de informações:
// Comparação segura contra timing attacks
function secureCompare(a, b) {
if (a.length !== b.length) {
return false;
}
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return result === 0;
}
Rate limiting: Implemente limitação de tentativas no endpoint de validação 2FA:
// Rate limiting para endpoint 2FA
const rateLimiter = new Map();
function checkRateLimit(userId) {
const now = Date.now();
const attempts = rateLimiter.get(userId) || [];
// Remover tentativas mais antigas que 5 minutos
const recentAttempts = attempts.filter(time => now - time < 300000);
if (recentAttempts.length >= 5) {
return { allowed: false, retryAfter: 300 };
}
recentAttempts.push(now);
rateLimiter.set(userId, recentAttempts);
return { allowed: true };
}
Riscos de engenharia social e phishing: Códigos TOTP são descartáveis e não devem ser compartilhados. Eduque os usuários sobre:
- Nunca fornecer códigos TOTP por telefone, email ou chat
- Verificar sempre o domínio do site antes de inserir o código
- Utilizar autenticadores oficiais e confiáveis
Referências
- RFC 6238 - TOTP: Time-Based One-Time Password Algorithm — Especificação oficial do algoritmo TOTP pela IETF, definindo o padrão para implementação
- RFC 4226 - HOTP: An HMAC-Based One-Time Password Algorithm — Base do algoritmo HOTP que fundamenta o TOTP, com detalhes sobre HMAC-SHA1 e extração de dígitos
- OWASP - Two-Factor Authentication Bypass — Guia da OWASP sobre ataques comuns ao 2FA e como mitigá-los em aplicações web
- NIST SP 800-63B - Digital Identity Guidelines: Authentication and Lifecycle Management — Diretrizes do NIST para implementação segura de autenticação multifator, incluindo TOTP
- Google Authenticator - Key URI Format — Documentação oficial do formato de URI para provisionamento de autenticadores compatíveis com Google Authenticator
- Auth0 - TOTP Two-Factor Authentication Implementation Guide — Guia prático de implementação de TOTP com exemplos de código e considerações de segurança