Capa do Ebook gratuito Criptografia Aplicada para Profissionais: o que usar, quando e por quê

Criptografia Aplicada para Profissionais: o que usar, quando e por quê

Novo curso

22 páginas

Armadilhas comuns: modos inseguros, reutilização de nonce/IV e padding

Capítulo 19

Tempo estimado de leitura: 14 minutos

+ Exercício

Este capítulo foca em armadilhas recorrentes que ainda aparecem em código de produção: (1) uso de modos inseguros ou inadequados ao objetivo, (2) reutilização de nonce/IV e (3) erros de padding. A ideia aqui não é reexplicar fundamentos nem “qual modo escolher” em geral, mas mostrar como essas falhas surgem na prática, por que quebram a segurança e como evitá-las com verificações objetivas e passos operacionais.

1) Modos inseguros (ou inseguros no contexto)

Um “modo” de operação define como um cifrador de bloco (como AES) é aplicado a mensagens maiores que um bloco. O problema comum não é apenas escolher um modo “fraco”, mas usar um modo correto para um objetivo errado. Em produção, isso costuma acontecer por legado, por copiar/colar exemplos antigos, ou por confundir “confidencialidade” com “confidencialidade + integridade”.

1.1 ECB: vazamento de padrões e estrutura

Armadilha: usar AES-ECB para “criptografar dados” por ser simples (não precisa IV). Em ECB, blocos iguais de plaintext viram blocos iguais de ciphertext. Isso revela padrões, repetições e estrutura. Mesmo que o atacante não recupere o plaintext completo, ele aprende muito sobre o conteúdo (por exemplo, campos repetidos, alinhamento, templates).

Sinais de que você está caindo nessa armadilha:

  • Você vê “ECB” em código, configuração, ou documentação interna.
  • O ciphertext tem o mesmo tamanho do plaintext arredondado para múltiplos do bloco, sem metadados de IV/nonce.
  • Há “criptografia” aplicada diretamente em registros estruturados (JSON, CSV, protobuf) sem randomização por mensagem.

Como corrigir na prática: substitua por um esquema com nonce/IV por mensagem e, idealmente, autenticação (AEAD). Se você não puder migrar tudo de uma vez, ao menos interrompa o uso para novos dados e planeje recriptografia gradual.

Continue em nosso aplicativo

Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.

ou continue lendo abaixo...
Download App

Baixar o aplicativo

1.2 CBC sem autenticação: maleabilidade e ataques de oráculo

Armadilha: usar AES-CBC para confidencialidade e “confiar” que isso basta. CBC sozinho não fornece integridade. Um atacante que consiga modificar o ciphertext pode induzir alterações previsíveis em partes do plaintext após a descriptografia (malleability). Além disso, quando o sistema retorna erros diferentes (por exemplo, “padding inválido” vs “MAC inválido” vs “JSON inválido”), pode surgir um oráculo que permite recuperar plaintext byte a byte em cenários reais.

Indicadores típicos:

  • Existe AES-CBC + PKCS#7, mas não há tag de autenticação (MAC/AEAD).
  • Há um “checksum” não criptográfico (CRC, hash simples) em vez de autenticação criptográfica.
  • Erros de descriptografia são propagados para o cliente com mensagens distintas.

Mitigação prática: se você está preso em CBC por compatibilidade, aplique autenticação correta (por exemplo, Encrypt-then-MAC com HMAC) e padronize erros (mesma resposta e tempo semelhante). Se puder migrar, use AEAD e elimine padding (ex.: modos de fluxo/contador).

1.3 CTR “puro” (ou stream cipher) sem autenticação: bit-flipping

Armadilha: usar AES-CTR (ou qualquer cifra de fluxo) como se fosse “criptografia completa”. CTR transforma o cifrador de bloco em uma cifra de fluxo: ciphertext = plaintext XOR keystream. Isso é ótimo para desempenho e evita padding, mas sem autenticação o atacante pode alterar bits do ciphertext e causar alterações controladas no plaintext após a descriptografia (bit-flipping). Em dados estruturados, isso pode virar escalonamento de privilégios, alteração de valores, flags etc.

Exemplo prático: um token criptografado em CTR contendo role=user. Um atacante que consiga adivinhar a posição e alterar bits pode tentar transformar em role=admin (nem sempre é trivial, mas o risco existe sempre que o formato é previsível).

Mitigação: use AEAD (que autentica) ou, se legado, adicione MAC e valide antes de descriptografar (ou no mínimo antes de usar o plaintext).

1.4 GCM/ChaCha20-Poly1305 usados errado: “modo seguro” não salva implementação

Armadilha: acreditar que “usei AES-GCM, então está tudo certo”. AEAD reduz classes inteiras de bugs, mas ainda há duas falhas comuns:

  • Reutilização de nonce (ver seção 2): em GCM isso pode ser catastrófico.
  • Não autenticar metadados importantes: esquecer de colocar contexto em AAD (por exemplo, user_id, tenant_id, versão do formato, tipo de mensagem). Isso abre espaço para “replay” em contexto errado ou confusão de protocolo (o ciphertext válido em um endpoint sendo aceito em outro).

Checklist prático para AEAD:

  • Nonce único por chave (nunca repetir).
  • AAD inclui: versão do esquema, identificador do tipo de payload, contexto (tenant/usuário), e qualquer campo que não deve ser alterado.
  • Tag é verificada sempre; se falhar, descarte sem tentar “recuperar” dados.

2) Reutilização de nonce/IV: o erro silencioso que quebra tudo

Nonce/IV é um valor por mensagem que garante que criptografias do mesmo plaintext sob a mesma chave resultem em ciphertexts diferentes (e/ou que o keystream não se repita). A regra prática é: nonce/IV deve ser único por chave. Em alguns modos, além de único, precisa ser imprevisível (aleatório). Em outros, pode ser um contador, desde que nunca repita.

O problema é que a reutilização pode acontecer sem ninguém perceber: o sistema “funciona”, testes passam, e só sob carga, reinícios ou múltiplas instâncias a colisão aparece.

2.1 Por que reutilizar nonce/IV é tão perigoso

O impacto depende do modo:

  • CTR e cifras de fluxo: reutilizar nonce/IV repete o keystream. Se o atacante obtiver dois ciphertexts C1 = P1 XOR K e C2 = P2 XOR K, ele calcula C1 XOR C2 = P1 XOR P2. Isso vaza relação direta entre plaintexts e frequentemente permite recuperar ambos quando há estrutura previsível.
  • GCM: além do problema de keystream, a reutilização de nonce pode permitir forjar tags e comprometer integridade de forma grave. Em termos práticos: uma única repetição pode ser suficiente para um incidente sério.
  • CBC: reutilizar IV com a mesma chave pode revelar quando mensagens começam com o mesmo bloco e pode facilitar análises e ataques em formatos previsíveis. Não é tão imediatamente “quebra total” quanto CTR/GCM, mas ainda é uma falha relevante.

2.2 Como a reutilização acontece em sistemas reais

Causas comuns:

  • Nonce aleatório com RNG fraco ou mal usado: baixa entropia, seed repetido, ou uso de geradores não criptográficos.
  • Contador reinicia: nonce baseado em contador que volta a zero após restart, deploy, crash ou rotação de pod/VM.
  • Concorrência: múltiplas threads/processos incrementando um contador sem sincronização, gerando duplicatas.
  • Multi-instância: várias réplicas usando o mesmo esquema de nonce (ex.: timestamp em milissegundos) e colidindo sob carga.
  • Truncamento: nonce derivado de timestamp/UUID mas truncado para caber no tamanho exigido, aumentando colisões.
  • Chave “compartilhada demais”: a mesma chave usada por muitos serviços/fluxos, elevando a chance de colisão de nonce.

2.3 Passo a passo prático: desenhando um esquema de nonce que não repete

A seguir um roteiro operacional que você pode aplicar em um serviço que criptografa eventos, tokens ou blobs.

Passo 1: defina a unidade de unicidade

Pergunta: “único em relação a quê?” A resposta correta é: único por chave. Então você precisa mapear onde a chave é usada: por serviço, por tenant, por tipo de dado, por ambiente. Quanto mais ampla a reutilização da chave, mais difícil garantir unicidade do nonce.

Passo 2: escolha estratégia de nonce compatível com o modo

  • Para AEADs comuns, o nonce pode ser aleatório (desde que o risco de colisão seja negligenciável) ou determinístico (contador) se você garantir não repetição.
  • Se você não consegue garantir sincronização global, prefira nonce aleatório de tamanho adequado, gerado por fonte criptográfica.
  • Se você precisa de determinismo (por exemplo, para evitar RNG em ambiente restrito), use contador + identificador único da instância, com persistência.

Passo 3: se usar contador, torne-o persistente e “namespaced”

Um padrão robusto é compor o nonce com duas partes:

  • ID da instância (fixo durante a vida da instância e único entre instâncias): por exemplo, 64 bits derivados de um identificador aleatório gerado no boot e persistido, ou um ID atribuído pelo orquestrador (desde que realmente único).
  • Contador monotônico (incrementado a cada criptografia): por exemplo, 64 bits.

Requisitos:

  • O contador deve ser armazenado de forma durável (arquivo, banco local, ou storage transacional) para não reiniciar após reboot.
  • Incremento deve ser atômico (lock, atomic, ou transação) para não repetir sob concorrência.
  • Se o contador chegar ao limite, você deve rotacionar a chave antes de overflow.

Passo 4: se usar nonce aleatório, trate colisão como risco de engenharia

Nonce aleatório funciona bem quando o tamanho é suficiente e a fonte é criptográfica. Ainda assim, em sistemas de altíssimo volume, você deve estimar o risco de colisão (efeito “aniversário”). Em vez de “achar que é improvável”, estabeleça limites de volume por chave e rotacione chaves antes de atingir volumes que tornem colisões plausíveis.

Passo 5: serialize nonce junto com o ciphertext

Nonce/IV normalmente não é secreto. O erro comum é “perder” o nonce e tentar derivá-lo de novo. Armazene/transmita o nonce como prefixo do ciphertext ou em campo separado, e inclua versão do formato.

Formato sugerido (exemplo conceitual, não padrão universal): [version][nonce][ciphertext][tag]

Passo 6: monitore duplicatas

Em pipelines críticos, vale instrumentar telemetria: amostrar nonces gerados e detectar repetição por chave (por exemplo, com bloom filter por janela de tempo ou logs de auditoria). Isso não substitui o design correto, mas ajuda a detectar regressões.

3) Padding: onde “detalhes” viram vulnerabilidade

Padding é o preenchimento necessário quando você usa um modo que exige blocos completos (como CBC) e o plaintext não tem tamanho múltiplo do bloco. O padding mais comum é PKCS#7: você adiciona N bytes, cada um com o valor N, para completar o bloco.

As armadilhas com padding aparecem em três frentes: (1) validação incorreta, (2) diferenças de erro/tempo (oráculos) e (3) confusão de camadas (descriptografar antes de autenticar, ou usar padding em formatos que não precisam).

3.1 Erros clássicos de implementação de PKCS#7

Problemas típicos em código customizado:

  • Não validar todos os bytes do padding: checar apenas o último byte (N) e remover N bytes sem verificar se todos são iguais a N.
  • Aceitar N=0 ou N>block_size: PKCS#7 válido tem 1..block_size.
  • Remover padding antes de verificar integridade: em esquemas com MAC, a ordem errada pode expor oráculos.
  • Mensagens de erro diferentes: “padding inválido” vs “MAC inválido” vs “UTF-8 inválido”.

Mesmo quando você usa bibliotecas, a armadilha pode estar no tratamento de exceções: retornar códigos HTTP diferentes, logs observáveis externamente, ou tempos de resposta distintos.

3.2 Padding oracle: como surge sem você perceber

Um padding oracle acontece quando um atacante consegue enviar ciphertexts modificados e observar alguma diferença que indique se o padding após descriptografia foi aceito. Essa diferença pode ser:

  • Mensagem de erro diferente.
  • Código de status diferente.
  • Tamanho da resposta diferente.
  • Tempo de resposta estatisticamente diferente.

Com esse “sinal”, o atacante pode adaptar tentativas e recuperar plaintext de forma incremental. Em APIs, isso pode ocorrer em endpoints de “descriptografar token”, “importar blob”, “processar cookie”, “webhook assinado/criptografado” etc.

3.3 Passo a passo prático: eliminando padding e oráculos

O caminho mais seguro é evitar a necessidade de padding em novos formatos e reduzir superfícies onde ciphertexts não confiáveis são processados.

Passo 1: identifique onde existe padding hoje

  • Procure por AES-CBC, “PKCS7”, “PKCS5”, “pad/unpad”.
  • Procure por bibliotecas que retornam exceções específicas de padding.
  • Mapeie endpoints que aceitam ciphertext do cliente (cookies, parâmetros, headers, payloads).

Passo 2: pare de aceitar ciphertexts não autenticados

Se o sistema aceita ciphertext vindo do cliente, a regra operacional é: não processe plaintext algum se a autenticação falhar. Em CBC legado, isso significa validar MAC antes de tentar remover padding (Encrypt-then-MAC). Em AEAD, significa verificar a tag e abortar.

Passo 3: unifique erros e reduza canais laterais

  • Retorne uma resposta genérica para falhas de descriptografia/autenticação (por exemplo, “token inválido”).
  • Evite expor detalhes em logs acessíveis ao usuário final.
  • Busque tempos de execução consistentes no caminho de erro (na medida do possível), evitando early-return com diferenças grandes.

Passo 4: migre formatos para modos que não exigem padding

Em novos payloads, prefira esquemas que não dependem de padding (por exemplo, modos de contador/fluxo com autenticação). Isso reduz uma classe inteira de bugs. Se você precisa manter compatibilidade, versionamento de payload ajuda: aceite v1 (CBC+padding) apenas para leitura e emita v2 (AEAD) para novos dados.

Passo 5: se você for obrigado a manter PKCS#7, valide corretamente

Se a migração não é imediata, garanta que a validação de padding seja completa e que não haja oráculo observável. Em termos de checklist:

  • Verificar N está entre 1 e block_size.
  • Verificar todos os últimos N bytes são iguais a N.
  • Não diferenciar mensagens de erro.
  • Não expor exceções internas.
  • Autenticar antes de descriptografar/remover padding (quando aplicável).

4) Padrões de falha em “cola” de sistemas: onde as armadilhas se escondem

Muitos incidentes não vêm do algoritmo, mas da integração: serialização, versionamento, metadados e reuso de componentes. Alguns padrões recorrentes:

4.1 Reuso de chave entre propósitos diferentes

Mesmo com nonce único, usar a mesma chave para “criptografar tokens” e “criptografar arquivos” aumenta o raio de impacto e dificulta garantir unicidade de nonce em todos os fluxos. Além disso, facilita confusão de protocolo: um ciphertext válido em um contexto pode ser aceito em outro se não houver AAD/contexto forte.

Mitigação: separe chaves por propósito e inclua contexto em AAD (tipo de mensagem, versão, serviço, tenant).

4.2 Falta de versionamento do formato criptográfico

Sem versão, você fica preso a decisões antigas (como CBC+padding) e tende a criar “gambiarras” para compatibilidade, aumentando superfície de oráculo e erros de parsing.

Mitigação: sempre inclua um byte/campo de versão no payload criptografado e trate migração como parte do design.

4.3 Confundir encoding com criptografia

É comum ver base64/hex comprimindo/transformando dados e alguém assumir que “está protegido”. Isso não é criptografia. O perigo aqui é operacional: dados sensíveis acabam em logs, URLs, analytics e caches.

Mitigação: revise fluxos de dados e garanta que “proteção” significa criptografia/autenticação reais, e que dados sensíveis não são serializados em locais indevidos.

5) Exercícios de revisão (para aplicar no seu código hoje)

Use esta lista como roteiro de auditoria rápida em um repositório ou serviço.

5.1 Caça a modos inseguros

  • Existe “ECB” em qualquer lugar? Se sim, trate como bug.
  • Existe CBC sem autenticação? Onde o ciphertext entra no sistema?
  • Existe CTR/stream sem autenticação? Quais campos do plaintext são previsíveis?
  • Em AEAD, o AAD inclui contexto suficiente para impedir troca de contexto (tenant, tipo, versão)?

5.2 Caça a reutilização de nonce/IV

  • Como o nonce é gerado? Aleatório criptográfico ou contador?
  • Se contador: ele persiste entre reinícios? É atômico sob concorrência? É único entre instâncias?
  • Se aleatório: qual o tamanho? Existe limite de mensagens por chave? Há rotação planejada?
  • O nonce é armazenado junto com o ciphertext? Há risco de “regerar” nonce?

5.3 Caça a padding/oráculos

  • Há qualquer unpad manual? Está validando todos os bytes?
  • Erros de padding são distinguíveis externamente (mensagem, status, tempo)?
  • O sistema tenta parsear JSON/UTF-8 antes de confirmar autenticidade?
  • Há endpoints que aceitam ciphertext do cliente e retornam erros detalhados?

Ao aplicar esses exercícios, você normalmente encontra pelo menos um ponto frágil: um token legado em CBC, um contador de nonce que reinicia em deploy, ou uma exceção de padding vazando para o cliente. O valor aqui é transformar “criptografia correta no papel” em “criptografia robusta em produção”, onde reinícios, concorrência, observabilidade e integração com múltiplos serviços são a regra.

Agora responda o exercício sobre o conteúdo:

Ao usar um modo AEAD como AES-GCM ou ChaCha20-Poly1305, qual prática é necessária para evitar falhas graves mesmo estando em um modo considerado seguro?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

AEAD ajuda, mas nao corrige erros de uso: repetir nonce por chave pode ser catastrófico e esquecer AAD permite confusao de contexto. A tag deve ser verificada e, se falhar, o dado deve ser descartado.

Próximo capitúlo

Armadilhas comuns: validação de certificados, downgrade e MITM

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.