DJB e Segurança-por-Construção: qmail até Curve25519
Um olhar prático sobre as ideias de segurança-por-construção de Daniel J. Bernstein — de qmail a Curve25519 — e o que “cripto simples e verificável” significa na prática.

O que Segurança-por-Construção Significa (Sem o Jargão)
Segurança-por-construção significa construir um sistema de modo que erros comuns sejam difíceis de cometer — e o dano de erros inevitáveis seja limitado. Em vez de depender de uma longa lista de verificação ("lembre-se de validar X, sanitizar Y, configurar Z..."), você projeta o software para que o caminho mais seguro seja também o mais fácil.
Pense nisso como embalagens à prova de crianças: não pressupõe que todos serão perfeitamente cuidadosos; pressupõe que humanos estão cansados, ocupados e às vezes erram. Bom design reduz o quanto de “comportamento perfeito” você exige de desenvolvedores, operadores e usuários.
Por que a simplicidade reduz o risco
Problemas de segurança frequentemente se escondem na complexidade: muitos recursos, muitas opções, muitas interações entre componentes. Cada botão extra pode criar um novo modo de falha — uma forma inesperada de o sistema quebrar ou ser mal usado.
A simplicidade ajuda de duas maneiras práticas:
- Menos código para auditar: menos ramos, menos casos especiais, menos comportamentos ocultos.
- Menos formas de configurar mal: quando há um padrão seguro em vez de dez escolhas “flexíveis”, há menos espaço para insegurança acidental.
Não se trata de minimalismo por si só. Trata-se de manter o conjunto de comportamentos pequeno o bastante para que você consiga realmente entendê-lo, testá-lo e raciocinar sobre o que acontece quando algo dá errado.
O que este post cobre (e o que não cobre)
Este post usa o trabalho de Daniel J. Bernstein como exemplos concretos de segurança-por-construção: como qmail procurou reduzir modos de falha, como o pensamento de tempo-constante evita vazamentos invisíveis, e como Curve25519/X25519 e NaCl empurram para criptografia que é mais difícil de usar de forma errada.
O que não será feito: oferecer uma história completa da criptografia, provar algoritmos como seguros, ou afirmar que existe uma única “melhor” biblioteca para todo produto. E também não fingirá que bons primitivos resolvem tudo — sistemas reais ainda falham por manejo de chaves, erros de integração e lacunas operacionais.
O objetivo é simples: mostrar padrões de design que tornam resultados seguros mais prováveis, mesmo quando você não é um especialista em criptografia.
Quem é Daniel J. Bernstein e Por Que As Pessoas Citam Seu Trabalho
Daniel J. Bernstein (frequentemente “DJB”) é um matemático e cientista da computação cujo trabalho aparece repetidamente em engenharia prática de segurança: sistemas de email (qmail), primitivos e protocolos criptográficos (notavelmente Curve25519/X25519) e bibliotecas que empacotam criptografia para uso no mundo real (NaCl).
As pessoas citam DJB não porque ele escreveu a única forma “certa” de fazer segurança, mas porque seus projetos compartilham um conjunto consistente de instintos de engenharia que reduzem o número de maneiras pelas quais as coisas podem dar errado.
O que engenheiros emprestam do estilo DJB
Um tema recorrente é interfaces menores e mais enxutas. Se um sistema expõe menos pontos de entrada e menos escolhas de configuração, fica mais fácil revisar, testar e menos provável de ser mal usado por engano.
Outro tema é pressupostos explícitos. Falhas de segurança frequentemente vêm de expectativas não declaradas — sobre aleatoriedade, comportamento de tempo, tratamento de erros ou como chaves são armazenadas. Escritos e implementações de DJB tendem a tornar o modelo de ameaça concreto: o que está protegido, de quem e em que condições.
Finalmente, há uma tendência para padrões seguros e correção entediante. Muitos desenhos nessa tradição tentam eliminar arestas cortantes que levam a bugs sutis: parâmetros ambíguos, modos opcionais e atalhos de desempenho que vazam informação.
Não é uma biografia — é uma perspectiva de engenharia
Este artigo não é uma história de vida nem um debate sobre personalidades. É uma leitura de engenharia: quais padrões você observa em qmail, pensamento em tempo-constante, Curve25519/X25519 e NaCl, e como esses padrões mapeiam para a construção de sistemas que são mais simples de verificar e menos frágeis em produção.
qmail: Um Exemplo Prático de Projetar para Menos Modos de Falha
qmail foi construído para resolver um problema pouco glamouroso: entregar email de forma confiável tratando o servidor de email como um alvo de alto valor. Sistemas de email ficam na internet, aceitam entrada hostil o dia todo e tocam dados sensíveis (mensagens, credenciais, regras de roteamento). Historicamente, um bug em um daemon de email monolítico podia significar compromisso total do sistema — ou perda silenciosa de mensagens que ninguém nota até ser tarde demais.
Dividir o trabalho, encolher o raio de explosão
Uma ideia definidora em qmail é quebrar “entrega de email” em pequenos programas que fazem um trabalho cada: receber, enfileirar, entrega local, entrega remota, etc. Cada peça tem uma interface estreita e responsabilidades limitadas.
Essa separação importa porque falhas se tornam locais:
- Se um componente crasha, não corrompe automaticamente a fila nem derruba o sistema inteiro.
- Se um componente tem um bug de segurança, o atacante não ganha instantaneamente os privilégios de todas as outras partes.
- Se você consegue raciocinar sobre um componente isoladamente, pode testá-lo e auditá-lo mais efetivamente.
Isto é segurança-por-construção em forma prática: projetar o sistema para que “um erro” seja menos provável de virar “falha total”.
Hábitos de design que valem a pena copiar
qmail também modela hábitos que se traduzem bem além do email:
- Fronteiras claras: definir exatamente quais entradas um componente aceita e quais saídas produz. Contratos pequenos e explícitos são mais fáceis de fazer cumprir.
- Tratamento estrito da entrada: trate tudo vindo da rede como potencialmente malicioso; valide cedo, rejeite casos estranhos e evite “adivinhações úteis”.
- Privilégio mínimo por padrão: execute componentes apenas com as permissões necessárias, para que um bug não vire automaticamente um takeover completo.
A conclusão não é “use qmail”. É que frequentemente você consegue ganhos de segurança grandes ao redesenhar em torno de menos modos de falha — antes de escrever mais código ou adicionar mais botões.
Reduzindo a Superfície de Ataque Através de Interfaces Enxutas
“Superfície de ataque” é a soma de todos os lugares onde seu sistema pode ser cutucado, provocado ou enganado para fazer a coisa errada. Uma analogia útil é uma casa: toda porta, janela, abertura da garagem, chave sob o tapete e slot de entrega é um ponto de entrada potencial. Você pode instalar fechaduras melhores, mas também fica mais seguro tendo menos pontos de entrada.
Software é igual. Toda porta de rede que você abre, formato de arquivo que aceita, endpoint de administração que expõe, botão de configuração que adiciona e hook de plugin que suporta aumenta o número de formas pelas quais as coisas podem falhar.
Interfaces enxutas: APIs menores, menos modos de falha
Uma “interface enxuta” é uma API que faz menos, aceita menos variação e recusa input ambíguo. Frequentemente isso parece restritivo — mas é mais fácil de proteger porque há menos caminhos de código para auditar e menos interações surpreendentes.
Considere dois desenhos:
- Interface ampla: “Envie qualquer tipo de arquivo; nós detectamos o formato; compressão opcional; encriptação opcional; metadados opcionais; múltiplos esquemas de auth.”
- Interface enxuta: “Envie bytes; você deve declarar o tipo de conteúdo de uma pequena allowlist; tamanho máximo fixo; encriptação tratada internamente; um método de auth.”
O segundo desenho reduz o que atacantes podem manipular. Também reduz o que sua equipe pode configurar mal acidentalmente.
Por que menos opções pode ser mais seguro
Opções multiplicam testes. Se você suporta 10 toggles, você não tem 10 comportamentos — você tem combinações. Muitos bugs de segurança vivem nessas costuras: “esta flag desativa uma checagem”, “este modo pula validação”, “esta configuração legado contorna limites de taxa”. Interfaces enxutas transformam “segurança escolha-sua-própria-aventura” em um caminho bem iluminado.
Checklist: onde a complexidade se esconde
Use isto para detectar superfície de ataque que cresce silenciosamente:
- Muitos tipos de entrada: múltiplos formatos de arquivo, encodings ou parsing por “auto-detect”.
- Muitas formas de entrar: portas de rede extras, painéis de admin, endpoints de debug, webhooks.
- Feature flags que mudam lógica de segurança: toggles que alteram validação, autenticação ou comportamento criptográfico.
- Pluggability: scripts, templates, plugins ou “expressões customizadas” avaliadas em tempo de execução.
- Modos de compatibilidade retroativos: protocolos legados, cifrões antigos, versões de API deprecadas.
- Defaults implícitos: comportamento que muda dependendo de variáveis de ambiente ou configuração faltante.
Quando você não pode encolher a interface, torne-a estrita: valide cedo, rejeite campos desconhecidos e mantenha “funções poderosas” atrás de endpoints separados e claramente escopados.
Pensamento em Tempo-Constante: Prevenindo Vazamentos Que Você Não Vê
Comportamento “constant-time” significa que uma computação leva (aproximadamente) o mesmo tempo independentemente de valores secretos como chaves privadas, nonces ou bits intermediários. O objetivo não é ser rápido; é ser entediante: se um atacante não pode correlacionar tempo de execução com segredos, fica muito mais difícil extrair esses segredos por observação.
Vazamentos por tempo importam porque atacantes nem sempre precisam quebrar a matemática. Se conseguem executar a mesma operação muitas vezes (ou observá-la em hardware compartilhado), diferenças minúsculas — microssegundos, nanossegundos, até efeitos de cache — podem revelar padrões que, acumulados, levam à recuperação de chaves.
Onde a variabilidade de tempo se infiltra
Mesmo código “normal” pode se comportar diferente dependendo dos dados:
- Ramos baseados em dados secretos:
if (secret_bit) { ... }muda o fluxo de controle e frequentemente o tempo de execução. - Lookups em tabelas indexadas por segredos: exemplo clássico é uma tabela onde índices dependentes de segredo puxam diferentes linhas de cache.
- Efeitos de cache e memória: padrões de acesso à memória dependentes de segredo podem vazar através de caches de CPU, page faults ou prefetch.
- Instruções de tempo variável: algumas operações de números grandes, divisão ou loops com saída antecipada podem levar mais tempo para certas entradas.
Formas de alto nível para auditar risco de tempo
Você não precisa ler assembly para obter valor de uma auditoria:
- Trace a influência de segredos: liste quais variáveis são secretas (chaves privadas, segredos compartilhados, tags de autenticação) e onde elas fluem.
- Procure sinais de alerta: ifs dependentes de segredo, índices de array, loops com terminação baseada em segredo e lógica de “caminho rápido/caminho lento”.
- Trate dependências como parte do modelo de ameaça: verifique se bibliotecas criptográficas declaram comportamento em tempo-constante para as operações relevantes.
- Teste variância: execute operações muitas vezes com segredos diferentes e meça distribuições; diferenças grandes e consistentes são um sinal de alerta.
Pensamento em tempo-constante é menos sobre heroísmos e mais sobre disciplina: projetar código para que segredos não governem o tempo de execução.
Curve25519 e X25519: Cripto que Tenta Ser Difícil de Usar Incorretamente
Acordo de chave em curvas elípticas é uma forma de dois dispositivos criarem o mesmo segredo compartilhado mesmo tendo enviado apenas mensagens “públicas” pela rede. Cada lado gera um valor privado (mantido em segredo) e um valor público correspondente (seguro para enviar). Após trocar valores públicos, ambos combinam seu valor privado com o valor público do outro para chegar a um segredo compartilhado idêntico. Um bisbilhoteiro vê os valores públicos mas não consegue reconstruir de forma viável o segredo compartilhado, então as partes derivam chaves de encriptação e conversam em privado.
Por que Curve25519/X25519 ficaram populares
Curve25519 é a curva subjacente; X25519 é a função padronizada e específica de acordo de chaves construída sobre ela. O apelo vem em grande parte da segurança-por-construção: menos armadilhas, menos escolhas de parâmetros e menos maneiras de escolher uma configuração insegura acidentalmente.
Elas também são rápidas em uma ampla gama de hardware, o que importa para servidores que manejam muitas conexões e para telefones que querem economizar bateria. E o desenho encoraja implementações que são mais fáceis de manter em tempo-constante (ajudando a resistir a ataques de temporização), o que reduz o risco de um atacante extrair segredos medindo pequenas diferenças de desempenho.
O que faz — e o que não faz
X25519 provê acordo de chave: ajuda duas partes a derivar um segredo compartilhado para encriptação simétrica.
Não fornece autenticação por si só. Se você usar X25519 sem também verificar com quem está falando (por exemplo, com certificados, assinaturas ou uma chave pré-compartilhada), ainda pode ser enganado e acabar conversando com a parte errada. Em outras palavras: X25519 ajuda a prevenir espionagem, mas não impede personificação sozinho.
A Grande Ideia do NaCl: Menos Escolhas, Menos Erros
NaCl (a biblioteca Networking and Cryptography Library) foi construída com um objetivo simples: dificultar que desenvolvedores de aplicação montem criptografia insegura por acidente. Em vez de oferecer um bufê de algoritmos, modos, regras de padding e botões de configuração, o NaCl empurra você em direção a um pequeno conjunto de operações de alto nível já conectadas de forma segura.
“box” e “secretbox” como blocos mais seguros
As APIs do NaCl são nomeadas pelo que você quer fazer, não por quais primitivos você quer costurar.
crypto_box("box"): encriptação autenticada por chave pública. Você fornece sua chave privada, a chave pública do destinatário, um nonce e uma mensagem. Recebe um ciphertext que (a) esconde a mensagem e (b) prova que veio de alguém que conhece a chave correta.crypto_secretbox("secretbox"): encriptação autenticada por chave compartilhada. Mesma ideia, mas com uma chave secreta única.
O benefício chave é que você não escolhe separadamente “modo de encriptação” e “algoritmo de MAC” e depois espera ter combinado certo. Os padrões do NaCl forçam composições modernas e resistentes a mau uso (encrypt-then-authenticate), então modos de falha comuns — como esquecer checagens de integridade — ficam muito menos prováveis.
A troca: menos escolhas vs flexibilidade
A rigidez do NaCl pode parecer limitante se você precisa de compatibilidade com protocolos legados, formatos especializados ou algoritmos exigidos por regulações. Você troca “posso ajustar todos os parâmetros” por “posso enviar algo seguro sem ser expert em criptografia”.
Para muitos produtos, esse é exatamente o ponto: constranger o espaço de projeto para que menos bugs possam existir. Se realmente precisar de customização, você pode descer para primitivos de nível mais baixo — mas aí está aceitando de volta as arestas cortantes.
Defaults Seguros e o Custo de Muitos Botões
“Seguro por padrão” significa que a opção mais segura e razoável é o que você obtém quando não faz nada. Se um desenvolvedor instala uma biblioteca, copia um exemplo rápido ou usa padrões do framework, o resultado deve ser difícil de usar de forma errada e difícil de enfraquecer por acidente.
Defaults importam porque a maioria dos sistemas reais roda com eles. Times se movem rápido, documentação é folheada e configuração cresce organicamente. Se o padrão é “flexível”, isso frequentemente se traduz em “fácil de configurar mal”.
Como defaults criam risco silenciosamente
Falhas cripto não são sempre causadas por “má matemática”. Frequentemente vêm de escolher uma configuração perigosa porque ela estava disponível, era familiar ou fácil.
Armadilhas comuns de defaults:
- Aleatoriedade fraca ou previsível: usar PRNGs não criptográficos, reutilizar seeds ou recorrer a fontes de baixa entropia em contêineres/VMs. Se geração de chaves depende de aleatoriedade instável, tudo o que depende delas herda a fraqueza.
- Algoritmos obsoletos ainda suportados por compatibilidade: deixar SHA-1, MD5 ou tamanhos antigos de RSA habilitados “por precaução”, e descobrir que foram usados em produção porque o sistema negociou para eles.
- Modos e parâmetros customizados ou incomuns: oferecer muitos botões para modos de cifra de bloco, regras de padding, tratamento de nonce ou esquemas caseiros. Quanto mais escolhas, mais formas de acidentalmente criar um protocolo que parece encriptado mas não é seguro.
Regra prática: menos opções, resultados mais seguros
Prefira stacks que façam o caminho seguro ser o caminho mais fácil: primitivos revisados, parâmetros conservadores e APIs que não peçam decisões frágeis.
Quando possível, escolha bibliotecas e designs que:
- defaultem para algoritmos modernos e revisados amplamente
- removam opções deprecadas em vez de escondê-las em “configurações avançadas”
- tornem operações inseguras impossíveis (ou pelo menos dolorosamente explícitas)
Segurança-por-construção é, em parte, recusar transformar toda decisão em um dropdown.
Como “Simples e Verificável” Se Parece em Código Real
“Verificável” não quer dizer “formalmente provado” na maioria dos times de produto. Quer dizer que você pode construir confiança de forma rápida, repetível e com menos oportunidades de interpretar mal o que o código está fazendo.
O que “verificável” pode significar (praticamente)
Um código fica mais verificável quando:
- Legibilidade é alta: funções pequenas, nomes claros e mínimo “mágico”. Você consegue explicar o fluxo para um engenheiro novo sem um quadro cheio de exceções.
- Existem vetores de teste conhecidos: dado um input, o output é fixo e documentado (crítico para cripto). Isso pega mudanças acidentais que ainda “funcionam” em testes casuais.
- Builds são reproduzíveis: a mesma fonte produz o mesmo binário, então você pode confirmar que o que está rodando é o que foi revisado.
- Auditorias são factíveis: não “baratas”, mas limitadas — auditores conseguem cobrir os caminhos importantes sem se afogar em opções e estados de configuração.
Por que caminhos de código simples são mais fáceis de revisar
Cada ramo, modo e feature opcional multiplica o que os revisores precisam raciocinar. Interfaces mais simples reduzem o conjunto de estados possíveis, o que melhora a qualidade da revisão em duas maneiras:
- Revisores podem focar em poucos fluxos críticos de segurança em vez de perseguir casos de borda.
- É mais fácil notar quando algo está “errado” (uma alocação inesperada, um parsing arriscado, uma comparação sensível ao tempo).
Um fluxo leve de verificação que você pode adotar
Mantenha as coisas entediantes e repetíveis:
- Testes: adicione testes unitários mais vetores conhecidos para cada primitivo; rode-os no CI a cada mudança.
- Revisão: exija uma checklist focada em segurança para mudanças que toquem chaves, aleatoriedade, serialização e comparações.
- Monitoramento: logue razões de falha em alto nível (sem segredos), alerte picos em falhas de decrypt/verify e acompanhe versões de dependências para saber quando código cripto mudou sob você.
Essa combinação não substitui revisão especializada, mas eleva o piso: menos surpresas, detecção mais rápida e código que você realmente consegue raciocinar.
Onde Sistemas Cripto Ainda Falham (Mesmo com Bons Primitivos)
Mesmo escolhendo primitivos bem reconhecidos como X25519 ou uma API mínima ao estilo NaCl “box”/“secretbox”, sistemas ainda quebram nas partes confusas: integração, codificação e operações. A maioria dos incidentes do mundo real não é “a matemática estava errada”, mas “a matemática foi usada errado”.
Armadilhas de integração (os suspeitos usuais)
Erros no manejo de chaves são comuns: reusar chaves de longo prazo onde se espera uma chave efêmera, armazenar chaves no controle de versão, ou confundir “chave pública” e “chave secreta” porque ambas são apenas arrays de bytes.
Uso indevido de nonce é reincidente. Muitos esquemas de encriptação autenticada exigem um nonce único por chave. Duplicar um nonce (via reset de contador, condições de corrida entre processos ou suposições de “aleatório suficiente”) pode quebrar confidencialidade ou integridade.
Problemas de encoding e parsing criam falhas silenciosas: base64 vs hex, perda de zeros à esquerda, endianness inconsistente ou aceitar múltiplos encodings que comparam diferente. Esses bugs podem transformar “assinatura verificada” em “verificou outra coisa”.
Tratamento de erros pode ser perigoso em ambas direções: retornar erros detalhados que ajudam atacantes, ou ignorar falhas de verificação e continuar mesmo assim.
Armadilhas operacionais que anulam boa cripto
Segredos vazam por logs, relatórios de crash, analytics e endpoints de debug. Chaves também acabam em backups, imagens de VM e variáveis de ambiente compartilhadas de forma ampla. Ao mesmo tempo, atualizações de dependências (ou a falta delas) podem deixá-lo preso a uma implementação vulnerável mesmo se o desenho foi sólido.
Checklist de mitigação (para não-criptoengenheiros)
- Trate nonces como requisito de design: documente regras de unicidade e teste reuso.
- Defina um encoding canônico para chaves/mensagens; rejeite qualquer outro.
- Falhe fechado: se a verificação falhar, pare e retorne um erro genérico.
- Mantenha segredos fora dos logs; adicione testes automáticos de redação de logs.
- Armazene chaves em um gerenciador de segredos dedicado; rotacione e restrinja acesso.
- Prenda e revise dependências criptográficas; agende atualizações e auditorias.
Escolhendo Abordagens de Engenharia Cripto para Seu Produto
Bons primitivos não produzem automaticamente um produto seguro. Quanto mais escolhas você expõe — modos, paddings, encodings, “ajustes” customizados — mais formas times podem, acidentalmente, construir algo frágil. Uma abordagem de segurança-por-construção começa escolhendo um caminho de engenharia que reduza pontos de decisão.
Um framework prático de decisão
Use uma biblioteca de alto nível (APIs one-shot como “encripte esta mensagem para aquele destinatário”) quando:
- Seu time não é dedicado a trabalho de criptografia.
- Você precisa de defaults seguros (tratamento de nonce, autenticação, formatos de chave) mais do que flexibilidade.
- Quer minimizar código “colar” que pode reintroduzir modos de falha.
Compose primitivos de nível mais baixo (AEADs, hashes, acordo de chaves) somente quando:
- Você tem um spec de protocolo claro e reais requisitos de interoperabilidade.
- Pode atribuir propriedade para revisões, vetores de teste e manutenção a longo prazo.
- Pode provar que não está reinventando um protocolo que já existe.
Uma regra útil: se seu documento de design contém “vamos escolher o modo depois” ou “vamos só ter cuidado com nonces”, você já está pagando por muitos botões.
Perguntas para fornecedores e times internos
Peça respostas concretas, não linguagem de marketing:
- Design da API: a API torna estados inseguros difíceis de representar? Tamanhos de nonce, tamanhos de chave e escolhas de algoritmo são restritas?
- Defaults: o que acontece se desenvolvedores fornecem apenas uma chave e plaintext? A encriptação é sempre autenticada (AEAD), ou é possível fazer apenas “encriptar”?
- Postura contra canais laterais: quais operações são pensadas para ser tempo-constante? Qual é o modelo de ameaça assumido para vazamento por tempo, cache e ramos?
- Gerenciamento de chaves: como chaves são geradas, armazenadas, rotacionadas e zeradas na memória? Formatos de chave são explícitos e versionados?
- Auditorias e manutenção: quando foi a última auditoria independente? Como vulnerabilidades são tratadas? Há um changelog mostrando alterações relevantes para segurança?
Higiene de engenharia que compensa
Trate cripto como código crítico para segurança: mantenha a superfície da API pequena, trave versões, adicione testes de respostas conhecidas e rode fuzzing em parsers/serialização. Documente o que você não vai suportar (algoritmos, formatos legados) e construa migrações em vez de “switches de compatibilidade” que ficam para sempre.
Ações Práticas: Aplicando Segurança-por-Construção Esta Semana
Security-by-construction não é uma ferramenta nova que você compra — é um conjunto de hábitos que tornam categorias inteiras de bugs mais difíceis de existir. O fio comum na engenharia ao estilo DJB é: mantenha coisas simples o suficiente para raciocinar, faça interfaces enxutas que limitem mau uso, escreva código que se comporte da mesma forma mesmo sob ataque e escolha defaults que falhem de forma segura.
Os pontos para manter no seu quadro branco
- Simplicidade é um recurso de segurança. Componentes menores, menos estados e menos ramos de configuração deixam menos lugares para comportamento surpreendente.
- Interfaces enxutas previnem uso “criativo”. Prefira APIs que aceitam um formato correto ao invés de muitas entradas “quase corretas”.
- Pensamento em tempo-constante reduz vazamentos invisíveis. Mesmo que seu primitivo seja sólido, o código ao redor pode vazar segredos por tempo, ramos ou padrões de acesso à memória.
- Defaults seguros vencem opções infinitas. Cada botão adiciona uma nova combinação para testar — e geralmente uma nova forma de configurar de forma errada.
Lista de ações de uma semana para times
- Inventário: liste todos os lugares onde você usa criptografia (configurações TLS, hashing de senhas, assinatura de tokens, acordo de chaves, geração de números aleatórios). Anote a biblioteca exata e a configuração em uso.
- Substitua padrões arriscados: remova cripto caseira, parsing/encoding “esperto” e APIs ricas que podem ser mal usadas. Padronize um pequeno conjunto de primitivos opinativos e uma única forma de chamá-los.
- Constrain interfaces: encapsule chamadas criptográficas por trás de um módulo interno estreito com superfície mínima (poucos parâmetros, tipos fortes, validação clara de entrada).
- Adicione testes que peguem regressões: vetores conhecidos para primitivos, fuzz tests para parsers e checagens de “sem ramos dependentes de segredo” em caminhos quentes.
- Trave defaults: defina bases seguras no código (não em wikis) e exija revisão explícita para desviar.
Se quiser uma checklist estruturada para esses passos, considere adicionar uma página interna de “inventário cripto” junto aos seus docs de segurança (por exemplo, /security).
Uma nota sobre “segurança-por-construção” em entrega rápida de apps
Essas ideias não se limitam a bibliotecas cripto — aplicam-se a como você constrói e entrega software. Se estiver usando um fluxo “vibe-coding” (por exemplo, Koder.ai, onde você cria apps web/servidor/mobile via chat), os mesmos princípios aparecem como restrições de produto: manter um pequeno número de stacks suportados (React na web, Go + PostgreSQL no backend, Flutter no mobile), enfatizar planejamento antes de gerar mudanças e tornar rollback barato.
Na prática, recursos como modo de planejamento, snapshots e rollback e exportação de código-fonte ajudam a reduzir o “raio de explosão” de erros: você pode revisar intenção antes das mudanças irem ao ar, reverter rapidamente quando algo dá errado e verificar que o que está rodando coincide com o que foi gerado. Isso é o mesmo instinto de segurança-por-construção do qmail — aplicado a pipelines modernos de entrega.
Perguntas frequentes
O que “security-by-construction” significa na prática?
Security-by-construction é projetar software de forma que o caminho mais seguro seja também o mais fácil. Em vez de depender de longas listas de verificação, você restringe o sistema para que erros comuns sejam difíceis de cometer e erros inevitáveis tenham impacto limitado (menor “raio de explosão”).
Por que a simplicidade reduz o risco de segurança?
A complexidade cria interações ocultas e casos de borda que são difíceis de testar e fáceis de configurar de forma errada.
Ganho prático da simplicidade inclui:
- menos caminhos de código para auditar e fuzzing
- menos combinações de configuração que podem desativar proteções acidentalmente
- mais facilidade para raciocinar sobre modos de falha quando algo dá errado
O que é uma “tight interface” e como eu projeto uma?
Uma interface "apertada" faz menos e aceita menos variação. Evita entradas ambíguas e reduz modos opcionais que geram “segurança por configuração”.
Abordagem prática:
- allowlist de entradas (tipos, tamanhos, encodings)
- rejeitar campos desconhecidos em vez de parsing “tentar o melhor”
- manter operações poderosas/perigosas atrás de endpoints separados e bem escopados
O que o qmail pode ensinar sistemas modernos sobre limitar o raio de explosão?
qmail divide o processamento de email em pequenos programas (receber, enfileirar, entregar, etc.) com responsabilidades estreitas. Isso reduz modos de falha porque:
- uma queda em um componente tem menos probabilidade de corromper tudo
- um bug em um componente não concede automaticamente privilégios totais
- cada componente fica mais fácil de testar e auditar isoladamente
O que é “constant-time” e por que devo me importar?
Comportamento em tempo constante busca tornar tempo de execução (e muitas vezes padrões de acesso à memória) independentes de valores secretos. Isso importa porque atacantes podem, às vezes, inferir segredos medindo tempo, efeitos de cache ou diferenças entre “caminho rápido” e “caminho lento” em muitas execuções.
Trata-se de prevenir vazamentos “invisíveis”, não apenas de escolher algoritmos fortes.
Como posso detectar riscos de vazamento por tempo sem ler assembly?
Comece identificando o que é secreto (chaves privadas, segredos compartilhados, chaves de MAC, tags de autenticação) e procure lugares onde esses segredos influenciam fluxo de controle ou acesso à memória.
Sinais de alerta:
- ramos
ifbaseados em dados secretos - buscas em arrays/tabelas indexadas por segredos
- loops que saem cedo com base em segredos
- comparações que retornam cedo (igualdade não em tempo-constante)
Também verifique se sua dependência criptográfica declara explicitamente comportamento em tempo-constante para as operações que você utiliza.
Por que Curve25519/X25519 são consideradas “mais difíceis de usar errado”?
X25519 é a função padronizada de acordo de chaves construída sobre Curve25519. Tornou-se popular porque reduz "armadilhas": menos parâmetros para escolher, bom desempenho e um desenho que facilita implementações em tempo-constante.
Pense nela como uma faixa padrão mais segura para acordo de chaves — desde que você ainda trate autenticação e gerenciamento de chaves corretamente.
O X25519 autentica automaticamente a outra parte?
Não. X25519 realiza acordo de chaves (um segredo compartilhado), mas não prova com quem você está se comunicando.
Para prevenir personificação, combine com autenticação, por exemplo:
- certificados/assinaturas (por exemplo, em TLS)
- uma chave pré-compartilhada
- um esquema de assinatura em nível de aplicação
Sem autenticação, você pode acabar “seguramente” falando com a parte errada.
Qual é a grande ideia por trás das APIs “box” e “secretbox” do NaCl?
NaCl reduz erros oferecendo operações de alto nível já combinadas de forma segura, em vez de expor muitos algoritmos e modos.
Blocos comuns:
crypto_box: criptografia autenticada por chave pública (suas chaves + chaves do destinatário + nonce → ciphertext)crypto_secretbox: criptografia autenticada por chave compartilhada
O benefício prático é evitar erros de composição comuns (por exemplo, criptografar sem proteção de integridade).
Onde sistemas reais falham mesmo usando bons primitivos?
Mesmo bons primitivos falham quando integração e operações são descuidadas. Armadilhas comuns:
- reuso de nonce (reset de contadores, condições de corrida entre processos, suposições de “aleatório suficiente")
- encodings inconsistentes (hex vs base64, zeros à esquerda perdidos, ordem de bytes)
- tratamento de erros inseguro (mensagens detalhadas que ajudam atacantes ou ignorar falhas de verificação)
- vazamento de chaves via logs, relatórios de crash, backups ou variáveis de ambiente muito amplas
Mitigações:
- documentar regras de unicidade de nonce e testar reuso
- impor um encoding canônico e rejeitar os demais
- falhar fechado em verificações com mensagens genéricas
- manter chaves num gerenciador de segredos, rotacionar e restringir acesso