OpenCyber Academy
Engenharia · Capítulo 9 · Português
Código do curso: OCA-136Capítulo 09: OCA-136-C009

Capítulo 9 — Programação II

Ensino gratuito e aberto para quem deseja proteger pessoas e tecnologias. Não é preciso criar uma conta para estudar ou consultar as respostas.

Mapa da aula

Aula 09 publicada. Mapa, exemplo, EX017.

flowchart TD
  N0["9.1 Interfaces acima de implementações"]
  N1["9.2 Validação de entradas"]
  N2["9.3 Testes além dos exemplos"]
  N3["9.4 Dependências"]
  N4["Nota de engenharia"]
  N5["Síntese"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5
  N5 --> EX["Exemplo resolvido"]
  EX --> Q["Exercício EX017"]

Desenho lógico

flowchart LR
  N0["9.1 Interfaces acima de implementações"]
  N1["9.2 Validação de entradas"]
  N2["9.3 Testes além dos exemplos"]
  N3["9.4 Dependências"]
  N4["Nota de engenharia"]
  N5["Síntese"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX017"]
[9.1 Interfaces acima de implementações]
    |
    v
[9.2 Validação de entradas]
    |
    v
[9.3 Testes além dos exemplos]
    |
    v
[9.4 Dependências]
    |
    v
[Nota de engenharia]
    |
    v
[Síntese]
    |
    v
[Exemplo resolvido] --> [Exercício EX017]

Objetivos de aprendizagem

Você vai (1) estruturar o código de modo que os algoritmos sejam substituíveis; (2) interpretar e validar entradas não confiáveis, como certificados e compartilhamentos de chave; (3) escrever testes baseados em propriedades e de resposta conhecida; e (4) gerenciar dependências de bibliotecas criptográficas. O capítulo parte do Capítulo 4 e prepara a agilidade (Capítulo 39).

9.1 Interfaces acima de implementações

Escreva o código contra interfaces — kem.encapsulate(public_key), signer.sign(message) —, e não contra funções específicas de um algoritmo. Um identificador de algoritmo, escolhido pela política, seleciona a implementação. Essa é a base de programação da agilidade criptográfica (CSWP 39): a migração passa a ser uma mudança de configuração mais testes, e não uma reescrita.

O arquivo de política nomeia um algoritmo por uso, como ML-DSA-65 para assinaturas de documentos eletrônicos, e o código nunca o menciona. Quando a BPA passar mais tarde de uma configuração híbrida para uma puramente pós-quântica, a mudança será uma linha na política mais a execução dos testes. A revisão de código verifica que nenhum módulo contorna a interface importando diretamente uma biblioteca de algoritmo.

9.2 Validação de entradas

As chaves e os textos cifrados pós-quânticos têm comprimentos fixos por conjunto de parâmetros. A FIPS 203 exige verificações nas chaves de encapsulamento (uma verificação de módulo de que os coeficientes codificados estão abaixo de q) e nas chaves de desencapsulamento antes do uso. O código deve rejeitar comprimentos errados antes de chamar uma biblioteca e nunca "preencher" ou "truncar" para fazer as entradas caberem.

Uma chave de encapsulamento ML-KEM-768 precisa ter exatamente 1.184 bytes, e um texto cifrado exatamente 1.088. Verificar esses comprimentos primeiro dá uma mensagem de erro clara e evita passar dados malformados para um código que poderia se comportar de forma imprevisível. A verificação de módulo detecta chaves cujos coeficientes foram codificados com valores de 3329 ou mais, que um par malicioso poderia enviar para sondar fraquezas de implementação.

9.3 Testes além dos exemplos

Os testes de resposta conhecida confirmam saídas exatas. Os testes baseados em propriedades verificam invariantes sobre muitas entradas aleatórias: decapsulate(encapsulate(pk)) produz a mesma chave; uma assinatura é válida para a mensagem assinada e falha para qualquer mudança de um bit. Os testes negativos importam tanto quanto os positivos.

Um teste de propriedade para o assinador da BPA executa mil mensagens aleatórias, assina cada uma, verifica cada uma e depois inverte um bit aleatório da mensagem e da assinatura, esperando que as duas verificações falhem. Um teste que só verificasse a verificação bem-sucedida passaria mesmo que verify sempre retornasse verdadeiro, que é exatamente o bug que os testes negativos existem para detectar.

9.4 Dependências

Fixe as versões das bibliotecas criptográficas, acompanhe os seus alertas de segurança e registre quais algoritmos e conjuntos de parâmetros cada uma expõe. Nomes da época dos rascunhos (por exemplo, "Kyber768") e nomes finais (ML-KEM-768) não são intercambiáveis: a norma final mudou detalhes, então as implementações dos rascunhos não interoperam com a FIPS 203.

O arquivo de dependências da BPA registra o nome da biblioteca, a versão exata e os algoritmos que essa versão oferece sob os nomes finais. Quando uma versão da biblioteca renomeia ou remove um algoritmo de rascunho, o build falha no momento da atualização, e não no handshake de um parceiro. O acompanhamento dos alertas importa porque as bibliotecas pós-quânticas são mais novas e tiveram mais correções de segurança por ano do que o código RSA maduro.

9.5 Exemplo resolvido

O serviço de documentos eletrônicos da BPA chama rsa_sign() em 41 lugares. A refatoração introduz Signer, com as implementações RSA-PSS-3072 e ML-DSA-65, selecionadas por um arquivo de política. Uma suíte de testes percorre cada caminho de chamada com as duas. O diff é grande uma vez; a próxima migração será pequena.

Os 41 pontos de chamada são encontrados primeiro com uma busca por rsa_sign, e depois cada um é examinado em busca de pressupostos como um comprimento fixo de assinatura de 384 bytes numa coluna do banco de dados. As assinaturas ML-DSA-65 têm 3.309 bytes, então a mudança de esquema faz parte do mesmo item de trabalho. O arquivo de política começa com RSA-PSS-3072, para que nada mude para os usuários até a troca.

9.6 Erros comuns e limites

Nomes fixos no código transformam cada migração futura numa mudança de código em todos os pontos de chamada, que é exatamente o que a refatoração com Signer eliminou. As versões de rascunho e final diferem em detalhes, então uma biblioteca que ainda oferece o Kyber768 não interopera com o ML-KEM-768. Testar só os caminhos de sucesso deixa a validação e o tratamento de erros sem verificação, e os atacantes miram justamente esses caminhos com chaves e textos cifrados malformados.

Nota de engenharia

As interfaces também precisam de identificadores de algoritmo versionados que viajem com os dados. Uma assinatura guardada hoje precisa carregar um identificador que nomeie o seu algoritmo e conjunto de parâmetros, para que um verificador daqui a dez anos selecione a implementação certa e possa recusar as aposentadas. Sem identificadores, os sistemas adivinham pelo comprimento, o que quebra assim que dois algoritmos compartilham um tamanho. Trate o identificador como parte do conteúdo assinado ou autenticado sempre que o formato permitir, para que um atacante não possa rotular de novo uma assinatura fraca como forte. É esse pequeno hábito de projeto que depois torna segura, e não apenas conveniente, a migração guiada por política do Capítulo 39.

Exercícios

Síntese

Algoritmos substituíveis começam no código: interfaces, identificadores escolhidos pela política, validação estrita, testes de propriedade e negativos e um acompanhamento de dependências que distingue rascunho de final.

Respostas comentadas

EX017. generate_keypair() -> (public, secret), encapsulate(public) -> (ciphertext, shared_secret), decapsulate(secret, ciphertext) -> shared_secret, mais algorithm_id e sizes() consultados na implementação, nunca fixados no código pelos chamadores.

EX018. if len(ek) != 1184: raise ValueError(...); nunca preencher nem truncar; depois chamar a biblioteca, que também faz a verificação de módulo da FIPS 203.