A Anatomia de um Timing Attack e Como Mitigar com HMAC em Python
Comparar dois tokens com `==` parece inofensivo — e é exatamente assim que nasce uma das vulnerabilidades mais elegantes da segurança prática: o **timing attack**. Este artigo disseca a anatomia do ataque e mostra a mitigação usada em produção neste próprio site, no webhook de confirmação de pagamento.
---
## 1. A anatomia do vazamento
A comparação de strings do Python é otimizada para desistir cedo:
```python
"segredo123" == "abcdef..." # falha no 1º caractere: rapidíssima
"segredo123" == "segredo999" # falha no 8º caractere: um pouco mais lenta
```
Essa diferença — nanossegundos por caractere — é um **canal lateral**. Um atacante que consiga medir o tempo de resposta com precisão estatística pode descobrir o token um caractere por vez: testa `a...`, `b...`, `c...` e observa qual prefixo demora *ligeiramente* mais para ser rejeitado. Repetindo milhares de vezes para eliminar o ruído da rede, o custo do ataque cai de exponencial (força bruta cega) para **linear** no tamanho do token.
## 2. A mitigação: comparação em tempo constante
A biblioteca padrão do Python resolve isso com uma função cujo tempo de execução não depende de *onde* as strings divergem:
```python
import hmac
if not hmac.compare_digest(token_recebido, token_esperado):
return HttpResponse(status=403)
```
O `hmac.compare_digest` percorre **sempre** todos os bytes antes de responder. O canal de tempo se fecha: rejeitar `a...` demora o mesmo que rejeitar `segredo12X`.
É exatamente assim que o endpoint `/api/webhook/kiwify/` deste site valida o token de confirmação de pagamento — e com uma decisão de projeto adicional: se o token **não está configurado** no ambiente, o webhook responde `503` e rejeita tudo (*fail-closed*), em vez de aceitar requisições sem verificação.
## 3. HMAC: de comparar tokens a assinar mensagens
O mesmo módulo resolve um problema mais geral: provar que uma mensagem veio de quem possui um segredo compartilhado, sem transmitir o segredo.
```python
import hashlib, hmac
assinatura = hmac.new(
chave_secreta.encode(), payload.encode(), hashlib.sha256
).hexdigest()
```
O HMAC-SHA256 combina a chave e a mensagem de forma que: (1) sem a chave, é computacionalmente inviável forjar a assinatura; (2) qualquer alteração no payload invalida a verificação. É o mecanismo padrão de autenticação de webhooks (GitHub, Stripe, Kiwify) — e a verificação da assinatura recebida deve, novamente, usar `compare_digest`.
## 4. Bônus: HMAC como pseudonimizador LGPD
Um uso menos conhecido: transformar identificadores pessoais em **pseudônimos estáveis** para relatórios e telemetria:
```python
pseudonimo = "v1:" + hmac.new(
SECRET_V1.encode(), session_key.encode(), hashlib.sha256
).hexdigest()
```
O mesmo `session_key` sempre gera o mesmo pseudônimo (permite contar visitantes únicos), mas sem a chave ninguém reverte ou correlaciona os valores. O prefixo `v1:` versiona a chave: ao rotacioná-la, pseudônimos de períodos diferentes deixam de ser vinculáveis entre si — propriedade desejável sob a LGPD, que trata dados pseudonimizados como dados pessoais sujeitos a salvaguardas.
---
## Regras práticas
1. **Nunca** compare segredos com `==` — sempre `hmac.compare_digest`.
2. Webhook sem token configurado deve **rejeitar** (fail-closed), nunca aceitar.
3. Assinatura de payload = HMAC-SHA256 com chave compartilhada.
4. Pseudonimização de identificadores = HMAC com chave versionada.
Quatro linhas de disciplina que fecham uma classe inteira de vulnerabilidades.
Comentários
Seja o primeiro a comentar.
Artigos relacionados
Como Estruturei uma Arquitetura Web Resiliente com Django 6, Docker e Cloudflare em VPS
Guia prático e estudo de caso real sobre como provisionar um servidor de produção resiliente com SSL Full, containerização não-root, cache em Redis e monitoramento de saúde.
Do Zero à Produção: Django + Docker em VPS
Guia completo de como subir uma aplicação Django 6 com Channels, Daphne, Docker, Nginx e MySQL em uma VPS Ubuntu 24.04.
RUNBOOK que Funciona às 3h da Manhã: Arquitetura de Resposta a Incidentes
Detalhes da arquitetura de observabilidade e resposta a incidentes em Django 6: separação de liveness vs readiness probes, segurança em webhooks e painel administrativo.