Capítulo 3 — Fundamentos de programação
Mapa da aula
Aula 03 publicada. Mapa, exemplo, EX005.
flowchart TD
N0["3.1 Scripts para implementadores"]
N1["3.2 Bytes e comprimentos"]
N2["3.3 Criptografia da biblioteca padrão"]
N3["3.4 Caso resolvido"]
N4["3.5 Atividade prática"]
N5["3.7 Lendo a documentação das bibliotecas"]
N6["Síntese"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
N6 --> EX["Exemplo resolvido"]
EX --> Q["Exercício EX005"]
Desenho lógico
flowchart LR
N0["3.1 Scripts para implementadores"]
N1["3.2 Bytes e comprimentos"]
N2["3.3 Criptografia da biblioteca padrão"]
N3["3.4 Caso resolvido"]
N4["3.5 Atividade prática"]
N5["3.7 Lendo a documentação das bibliotecas"]
N6["Síntese"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX005"]
[3.1 Scripts para implementadores]
|
v
[3.2 Bytes e comprimentos]
|
v
[3.3 Criptografia da biblioteca padrão]
|
v
[3.4 Caso resolvido]
|
v
[3.5 Atividade prática]
|
v
[3.7 Lendo a documentação das bibliotecas]
|
v
[Síntese]
|
v
[Exemplo resolvido] --> [Exercício EX005]
Objetivos de aprendizagem
Você vai (1) escrever pequenos scripts em Python com funções, dicionários e laços; (2) tratar bytes, codificações e comprimentos com precisão; (3) usar corretamente hashlib, hmac e secrets; e (4) escrever testes que possam falhar.
3.1 Scripts para implementadores
Os implementadores escrevem scripts para inventariar, verificar e automatizar. Um script de trinta linhas que analisa exportações de configuração e sinaliza serviços apenas clássicos vale mais do que uma checklist manual, porque pode rodar toda noite. Os laboratórios deste livro usam apenas a biblioteca padrão do Python, para que rodem em qualquer lugar.
O primeiro script desse tipo da KLS lê as configurações exportadas do ingress e dos balanceadores de carga, lista os grupos configurados por listener e imprime qualquer listener de nível 1 sem X25519MLKEM768. Rodando toda noite, ele encontrou a lista mal ordenada do portal do cliente muito antes do que o alerta de monitoramento do Capítulo 19 teria encontrado. Manter o script com menos de 50 linhas o manteve revisável.
3.2 Bytes e comprimentos
Chaves, textos cifrados e assinaturas são bytes com comprimentos exatos: as chaves de encapsulamento do ML-KEM-768 têm 1.184 bytes e os textos cifrados, 1.088 bytes (FIPS 203); as chaves públicas do ML-DSA-65 têm 1.952 bytes e as assinaturas, 3.309 bytes (FIPS 204). Um código que os guarda em colunas de banco de dados de tamanho fixo, campos base64 ou protocol buffers dimensionados para o ECDSA vai truncá-los ou rejeitá-los. Verifique sempre os comprimentos explicitamente e falhe de forma fechada.
Os comprimentos também são a validação mais barata que um implementador pode acrescentar. Uma função que rejeita uma chave ML-KEM-768 de 1.183 bytes, como faz o Laboratório V no Capítulo 15, apanha truncamentos, conjuntos de parâmetros errados e confusões de codificação antes de qualquer chamada à biblioteca. A KLS guarda o conjunto de parâmetros ao lado de cada chave, para que o comprimento esperado possa ser consultado em vez de adivinhado.
3.3 Criptografia da biblioteca padrão
secrets.token_bytes(n) fornece bytes aleatórios seguros; random, não. hashlib.sha256, hashlib.sha3_256 e hashlib.shake_256 calculam hashes; hmac.new(key, msg, hashlib.sha256) calcula MACs; hmac.compare_digest compara segredos em tempo constante. A biblioteca padrão do Python não implementa o ML-KEM nem o ML-DSA; o código de produção chama uma biblioteca verificada ou o provedor da plataforma.
Vale a pena conhecer o hashlib.shake_256, porque o ML-KEM e o ML-DSA usam SHAKE internamente; os implementadores o encontram nos testes de resposta conhecida, mesmo que nunca implementem os esquemas. O módulo secrets tira os seus valores do gerador do sistema operacional, que é o que os scripts da KLS usam para chaves de laboratório e vetores de teste.
3.4 Caso resolvido
Um desenvolvedor da KLS guarda chaves públicas numa coluna VARCHAR(512) com texto base64. O base64 expande os bytes em 4/3, então uma chave ML-DSA-65 de 1.952 bytes precisa de 2.604 caracteres e é truncada silenciosamente por um modo permissivo do banco de dados. Um teste que fizesse um valor aleatório de 1.952 bytes ir e voltar pela camada de armazenamento teria apanhado o problema:
import base64, secrets
key = secrets.token_bytes(1952)
enc = base64.b64encode(key).decode()
assert len(enc) == 2604 and base64.b64decode(enc) == key
print("round trip ok,", len(enc), "chars")
A correção teve duas partes: a coluna passou a um tipo binário dimensionado a partir da maior chave que a KLS planeja guardar, os 2.592 bytes do ML-DSA-87, e o banco de dados passou ao modo estrito, para que um valor grande demais gere um erro em vez de ser cortado. O teste de ida e volta agora roda para cada tipo de chave da tabela de comprimentos do inventário.
3.5 Atividade prática
Escreva check_len(kind, data) usando um dicionário com os comprimentos esperados das chaves de encapsulamento do ML-KEM-512/768/1024, e teste entradas válidas e inválidas.
Um bom arquivo de testes tem pelo menos seis casos: os três comprimentos corretos (800, 1.184 e 1.568 bytes), um valor com um byte a menos, um valor com um byte a mais e um comprimento correto passado com o rótulo de tipo errado, como 1.184 bytes rotulados ML-KEM-512. A função deve gerar um erro que nomeie o tipo, o comprimento esperado e o comprimento real. Rode os testes uma vez com um defeito proposital, um número errado no dicionário, para mostrar que eles podem falhar.
3.6 Erros comuns e limites
- Campos de tamanho fixo dimensionados para chaves clássicas.
randompara segredos.- Testes que só exercitam os caminhos de sucesso.
Na KLS, cada erro teve um custo. A coluna VARCHAR(512) truncou silenciosamente chaves ML-DSA num modo permissivo. Um script de teste de carga que montava tokens de sessão com random produziu valores previsíveis que um revisor conseguiu reproduzir a partir da semente. Um teste de armazenamento que só gravava chaves válidas passou por meses, enquanto o defeito de truncamento ficava escondido atrás dele. Os scripts são pequenos, mas cada um desses erros sobrevive justamente porque ninguém acha que um script pequeno precisa de testes de falha.
3.7 Lendo a documentação das bibliotecas
Antes de chamar qualquer API criptográfica, leia o que ela promete: qual conjunto de parâmetros uma função usa por padrão, se ela devolve erros ou lança exceções, se as chaves são expandidas ou guardadas como sementes e quais codificações ela aceita. A FIPS 203 e a FIPS 204 permitem que as chaves privadas sejam guardadas como sementes curtas que se expandem de forma determinística; duas bibliotecas podem discordar sobre qual forma exportam. Escreva no seu repositório uma nota curta para cada biblioteca que usar, com versão, padrões e codificações, e crie um link para ela no inventário, para que o próximo implementador não precise redescobrir tudo.
Exercícios
- EX005 — Dimensionamento em base64. Quantos caracteres base64 uma assinatura ML-DSA-65 de 3.309 bytes exige? Rubrica: 3 = 4.412, com o método (ceil(3309/3) × 4).
- EX006 — Tempo constante. Explique por que
a == bé arriscado para comparar tags de MAC e o que usar no lugar. Rubrica: 3 = vazamento de tempo por saída antecipada,hmac.compare_digest.
Síntese
Os scripts de implementação precisam tratar bytes e comprimentos com exatidão, usar aleatoriedade segura e comparação em tempo constante, e ser testados tanto na falha quanto no sucesso.
Continue estudando — capítulo 03 de 30
← Capítulo 02 · Índice de capítulos do curso · Capítulo 04 →
Siga para a próxima lição escrita.