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.

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.
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:
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.
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.
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.
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.
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 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.
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:
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”.
qmail também modela hábitos que se traduzem bem além do email:
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.
“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.
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:
O segundo desenho reduz o que atacantes podem manipular. Também reduz o que sua equipe pode configurar mal acidentalmente.
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.
Use isto para detectar superfície de ataque que cresce silenciosamente:
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.
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.
Mesmo código “normal” pode se comportar diferente dependendo dos dados:
if (secret_bit) { ... } muda o fluxo de controle e frequentemente o tempo de execução.Você não precisa ler assembly para obter valor de uma auditoria:
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.
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.
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.
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.
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.
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 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.
“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”.
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:
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:
Segurança-por-construção é, em parte, recusar transformar toda decisão em um dropdown.
“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.
Um código fica mais verificável quando:
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:
Mantenha as coisas entediantes e repetíveis:
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.
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”.
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.
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.
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.
Use uma biblioteca de alto nível (APIs one-shot como “encripte esta mensagem para aquele destinatário”) quando:
Compose primitivos de nível mais baixo (AEADs, hashes, acordo de chaves) somente quando:
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.
Peça respostas concretas, não linguagem de marketing:
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.
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.
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).
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.
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”).
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:
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:
qmail divide o processamento de email em pequenos programas (receber, enfileirar, entregar, etc.) com responsabilidades estreitas. Isso reduz modos de falha porque:
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.
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:
if baseados em dados secretosTambém verifique se sua dependência criptográfica declara explicitamente comportamento em tempo-constante para as operações que você utiliza.
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.
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:
Sem autenticação, você pode acabar “seguramente” falando com a parte errada.
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 compartilhadaO benefício prático é evitar erros de composição comuns (por exemplo, criptografar sem proteção de integridade).
Mesmo bons primitivos falham quando integração e operações são descuidadas. Armadilhas comuns:
Mitigações: