Pular para o conteúdo
Estudos de Caso

A Anatomia de um Timing Attack e Como Mitigar com HMAC em Python

Makis Jeanty · 16 de Agosto de 2026


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.