Resumo

  • A RFC 3218 tratou texto de erro, forma de resposta e tempo de execução como saídas criptográficas. Distinguir um bloco RSAES-PKCS1-v1_5 malformado de outro bem formado que continha uma CEK errada podia sustentar um ataque adaptativo.
  • O random filling substituía uma extração inválida por uma CEK aleatória do tamanho esperado e prosseguia até o processamento do conteúdo e da integridade. A chave substituta não era correta nem recuperava dados; ela fazia a falha precoce parecer uma falha comum de chave errada.

O atacante não precisava ver o texto em claro. Bastava conseguir perguntar se o texto ainda oculto tinha o formato esperado.

Daniel Bleichenbacher mostrou em 1998 que esse único bit podia alimentar um ataque adaptativo de texto cifrado escolhido contra o PKCS #1 v1.5. O atacante capturava um texto cifrado RSA, criava uma transformação matemática, enviava o novo valor ao detentor da chave privada e observava se o bloco decifrado era considerado válido. Cada resposta estreitava o intervalo numérico em que o segredo podia estar. A chave privada permanecia no servidor; as decisões do servidor realizavam publicamente o trabalho necessário.

A RFC 3218 levou essa ameaça à Cryptographic Message Syntax. Publicada em janeiro de 2002 como Informational, ela não criou um novo formato de mensagem nem afirmou que todo uso de RSA fosse equivalente. Tratou de um problema de transição: implementações CMS já usavam RSA PKCS #1 v1.5 para transportar a chave simétrica que protegia o conteúdo, e a interoperabilidade instalada não poderia ser substituída de imediato.

O bloco v1.5 obedecia a uma gramática rígida: 00 02, pelo menos oito octetos aleatórios diferentes de zero, um separador 00 e o valor transportado. No CMS, o último valor era a content-encryption key, ou CEK. A operação RSA com a chave privada podia terminar e ainda assim produzir bytes que não formavam um bloco válido.

Essa diferença abria várias camadas de evidência. A operação RSA concluiu? O formato PKCS #1 era válido? A CEK tinha algoritmo e tamanho esperados? Era a mesma CEK usada pelo remetente? O decifrador de conteúdo produziu bytes? O padding, o MAC, a assinatura e a aplicação aceitaram o resultado? Cada pergunta tinha uma resposta própria.

O programa precisava das respostas internamente. O adversário não podia transformá-las em uma API de consulta.

O nome Million Message Attack descrevia uma escala operacional. Partindo de um cifrado capturado C, o atacante escolhia um multiplicador S e enviava C' = C * S^e mod n. A RFC estimava que cerca de uma transformação em 2^16 geraria um texto claro iniciado por 00 02 e que, no cenário CMS discutido, o processo total poderia chegar à ordem de 2^20 mensagens e respostas.

Esse número não era uma exigência fixa para qualquer chave ou implementação. Ele mostrava como a automação alterava o custo. Um milhão de interações humanas seria caro e evidente. Um agente de lista de e-mail, gateway ou serviço sem supervisão podia receber, decifrar, classificar e responder em ritmo de máquina. O atacante fornecia tráfego; o destinatário fornecia repetição.

Após uma consulta, o bloco PKCS #1 podia estar malformado. Também podia estar bem formado com uma CEK falsa. A CEK falsa podia causar uma falha de integridade. Sem integridade autenticada, bytes aleatórios podiam terminar por acaso com um padding CBC aparentemente aceitável. A transformação recuperar a CEK real era extremamente improvável.

O ataque não precisava identificar todos os resultados. Bastava separar o primeiro — erro de formato — dos fracassos posteriores causados por uma chave errada mas corretamente codificada. Uma mensagem de erro diferente serviria. Resposta versus silêncio, encerramento da conexão, devolução de e-mail, aviso de assinatura ou uma diferença de tempo reproduzível também serviriam.

A principal contramedida da RFC 3218 recebeu o nome de random filling. Quando a decodificação PKCS #1 detectava um formato inválido, o destinatário não encerrava o fluxo. Ele gerava uma CEK criptograficamente aleatória com o tamanho exigido pelo algoritmo de conteúdo e agia como se o desempacotamento RSA tivesse retornado esse valor.

O destinatário tentava decifrar o conteúdo com a chave inventada e executava os controles normais de padding, MAC, assinatura e aplicação. A CEK aleatória quase certamente levaria a um fracasso posterior. O erro visível e o tempo até ele deveriam se parecer com o caminho de um bloco bem formado que continha apenas a CEK errada.

Portanto, a CEK aleatória não era uma chave de recuperação. Ela não reconstruía o segredo do remetente, não preservava o conteúdo e não autorizava a mensagem. Era um estado descartável, criado para transportar a execução até um ponto de falha comum sem revelar a causa mais antiga.

Uma CEK fixa de fallback destruiria essa propriedade. A constante poderia parecer conveniente para testes ou para evitar uma chamada ao gerador aleatório em um erro. Mas um atacante que soubesse o valor poderia cifrar o conteúdo CMS com ele, anexar uma chave RSA deliberadamente malformada e enviar o conjunto. Se o destinatário entrasse na ramificação de fallback, o conteúdo seria válido; caso contrário, falharia. A defesa criaria um oráculo mais limpo.

O valor substituto precisava ser novo, imprevisível e ter o tamanho certo em cada uso. Reutilizá-lo daria uma identidade estável à ramificação que deveria desaparecer.

O instante da geração aleatória também podia vazar. Se o gerador fosse lento e chamado apenas depois do erro, a latência marcaria o caminho. A RFC 3218 sugeriu gerar um candidato aleatório para todas as mensagens e descartá-lo quando a extração legítima funcionasse. Assim, as duas rotas arcariam com custo semelhante.

Isso não provava tempo constante para todo o sistema. Acesso à memória, parser, logs, filas de trabalho, comportamento de rede e ações da aplicação ainda podiam divergir. O ponto era evitar que uma mensagem de erro uniforme mascarasse uma execução claramente diferente.

O tamanho da CEK criava outra fronteira. Uma biblioteca genérica de RSA podia saber que o bloco era sintaticamente válido e não saber se o algoritmo de conteúdo esperava 8, 16, 24 ou 32 bytes. Se a camada CMS superior rejeitasse um tamanho errado com uma exceção distinta, o oráculo simplesmente subiria na pilha.

A camada que conhecia o algoritmo também precisava substituir valores de tamanho inválido. A defesa cruzava limites de software. A primitiva criptográfica mais baixa não podia assegurar sozinha uma propriedade que dependia de contexto ausente nela.

Verificações adicionais aumentavam o custo do ataque: conferir todos os octetos de padding, o tamanho da CEK e, quando aplicável, os bits de paridade de algoritmos DES. Cada condição reduzia a probabilidade de uma transformação aleatória ser aceita.

Ainda assim, um oráculo raro continuava sendo um oráculo. Se o destinatário revelasse qual condição falhou, o atacante poderia continuar, gastando mais consultas para obter o mesmo tipo de informação.

O CBC sem autenticação mostrava como “o decifrador retornou dados” era uma evidência fraca. Um texto claro aleatório podia acabar em bytes parecidos com padding. Se a implementação olhasse apenas o último octeto para decidir o corte, a RFC 3218 estimava uma aparente validade de aproximadamente 1/32. Verificar todos os bytes exigidos reduzia a chance para perto de 1/255.

Nenhuma coincidência tornava o conteúdo significativo. Um MAC ou uma assinatura ofereciam um ponto posterior de rejeição muito mais forte. Isso ainda não autorizava expor separadamente o veredito PKCS #1 inicial; a proteção dependia de dissolver essa ramificação nas falhas posteriores.

OAEP fornecia uma direção criptográfica mais limpa. O PKCS #1 v2.0 havia especificado RSAES-OAEP, e a RFC 3218 indicava que o ataque discutido não se aplicava a ele. Mas OAEP era incompatível no fio com v1.5. Remetentes, destinatários, certificados, identificadores de algoritmo e programas CMS instalados precisavam concordar com a mudança.

Nomear a primitiva melhor não desligava o legado. O random filling era uma medida de coexistência: enquanto o formato antigo recebesse tráfego, sua capacidade de revelar distinções precisava permanecer limitada.

O TLS já havia enfrentado perigo semelhante com premaster secrets cifrados por RSA. Suas especificações faziam o servidor continuar com um valor aleatório, em vez de denunciar uma falha de formato ou versão. A ideia era parente, mas as evidências posteriores eram diferentes. Um handshake TLS, uma mensagem CMS e um agente de e-mail possuíam estados, respostas e efeitos próprios.

A disciplina geral vivia acima da primitiva: um parser interno não deve responder a uma pergunta que o protocolo externo não pode responder com segurança. Cabe ao protocolo definir quanto trabalho continua, qual rejeição final aparece e quais canais laterais precisam ser igualados.

A RFC 3218 não afirmou que o random filling autenticava o remetente, removia todo canal de tempo, reparava uma chave privada comprometida ou provava uma implementação segura. Também não apresentou um censo de adoção. Sua contribuição era menor e duradoura: quando uma diferença interna pode ser consultada repetidamente, o tratamento de erros passa a integrar o criptossistema.