Criptografia na prática para desenvolvedores: dados em repouso e em trânsito sem mágica
Simétrica vs assimétrica, AEAD, envelope encryption e KMS: um guia prático de criptografia em repouso e em trânsito para quem escreve o código.
Neste artigo
Um time subiu um serviço de assinaturas com uma coluna card_token cifrada no banco. Auditoria fez uma pergunta simples: onde está a chave que decifra isso? A resposta veio num arquivo config.rs versionado no Git, string hexadecimal literal, comitada havia oito meses e replicada em cada fork do repositório. A coluna estava cifrada, sim, mas a chave morava ao lado do cadeado, colada na porta. Esse é o retrato mais comum do que acontece quando criptografia entra num projeto como checkbox de compliance e não como decisão de engenharia: o algoritmo está correto, a matemática é impecável, e mesmo assim o sistema não protege nada. Criptografia não é o AES; é o AES mais tudo que orbita a chave. Este artigo é sobre esse resto que ninguém explica direito.
O objetivo aqui não é ensinar teoria dos números nem derivar por que curvas elípticas funcionam. É te dar os modelos mentais certos para decidir, no dia a dia, qual primitiva usar, onde ela se aplica, e quais são os erros que transformam cifragem forte em teatro de segurança. Vamos separar três eixos que costumam se misturar na cabeça de quem não trabalha com isso todo dia: simétrica versus assimétrica, repouso versus trânsito, e a distinção conceitual — crítica e quase sempre ignorada — entre cifrar, resumir com hash e apenas codificar.
Simétrica e assimétrica: duas ferramentas, dois problemas#
Criptografia simétrica usa a mesma chave para cifrar e decifrar. É rápida, opera em gigabytes por segundo com aceleração de hardware, e é o que você usa para proteger dados de verdade — o conteúdo de um arquivo, uma coluna de banco, o corpo de uma mensagem. O padrão de fato é o AES, e dentro dele a escolha certa quase sempre é o modo GCM, do qual falaremos adiante. ChaCha20-Poly1305 é a alternativa moderna preferível em ambientes sem aceleração AES em hardware, como muitos dispositivos móveis e embarcados. O problema que a simétrica não resolve sozinha é a distribuição: se as duas pontas precisam da mesma chave, como a chave chega até lá sem ser interceptada?
É aí que entra a criptografia assimétrica. Ela usa um par de chaves matematicamente ligadas: uma pública, que pode circular livremente, e uma privada, que nunca sai de quem a gerou. O que uma cifra, só a outra decifra. Isso resolve distribuição de chave e assinatura digital, mas paga um preço: é ordens de magnitude mais lenta e opera sobre blocos minúsculos de dados. RSA, o nome clássico, exige chaves de 3072 bits ou mais para segurança equivalente a uma chave simétrica modesta de 128 bits, e cifrar qualquer coisa maior que alguns bytes com RSA direto é abuso da ferramenta. A criptografia de curvas elípticas — ECC, com ECDSA para assinatura e ECDH para acordo de chave — entrega a mesma segurança com chaves muito menores e operações mais baratas, e por isso é o que a internet moderna adota.
Na prática, você quase nunca escolhe uma ou outra: os dois trabalham juntos num esquema híbrido. O assimétrico é usado uma vez, no início, para as duas pontas concordarem sobre uma chave simétrica efêmera; a partir daí todo o tráfego real viaja protegido por simétrica rápida. É exatamente assim que o TLS funciona, e é o padrão que você deve imitar sempre que precisar dos dois mundos. Regra de bolso: dados grandes e volume, simétrica; troca de chave, identidade e assinatura, assimétrica.
Cifrar, resumir e codificar não são a mesma coisa#
Esse é o erro conceitual que mais gera vulnerabilidade, e ele não é sobre algoritmo — é sobre não entender o que cada operação promete. Codificação, como Base64 ou URL-encoding, não tem absolutamente nada de segurança: é uma transformação reversível e pública, feita para transportar bytes por canais que só entendem texto. Qualquer um decodifica em um segundo. Ver um token em Base64 e achar que ele está "protegido" é um mal-entendido que já vazou muita credencial. Base64 é embrulho, não cofre.
Hashing é uma função de mão única: transforma qualquer entrada em um resumo de tamanho fixo e não existe operação inversa que recupere a entrada. Você usa hash para verificar integridade e para armazenar senhas — nesse caso com funções deliberadamente lentas e com sal, como argon2id ou bcrypt, jamais SHA-256 cru. A propriedade que importa é que você nunca precisa recuperar o valor original; só comparar se dois valores batem. Cifragem, por contraste, é reversível por desenho: existe uma chave que traz o texto claro de volta. Se você precisa ler o dado depois — um número de cartão para reembolso, um documento para exibir —, é cifragem. Se você só precisa confirmar que alguém sabe o valor — uma senha —, é hash. Confundir os dois leva a atrocidades como "cifrar" senhas com uma chave global recuperável, o que significa que um vazamento da chave expõe todas de uma vez, exatamente o que o hash existiria para impedir.
E há um detalhe operacional dentro do hashing que derruba sistemas: comparar dois hashes com o operador de igualdade comum vaza informação de tempo. Comparações byte a byte que param no primeiro caractere diferente permitem, com medição estatística, reconstruir o valor esperado caractere por caractere. Sempre use comparação de tempo constante — funções como constant_time_eq, hmac.compare_digest ou equivalente da sua linguagem — para qualquer comparação de segredo, token ou MAC.
Criptografia em repouso: proteger o dado parado#
Dado em repouso é o que está gravado em disco, banco, backup, bucket. Há camadas, e escolher a errada dá falsa sensação de segurança. Cifragem de disco inteiro, como LUKS ou a cifragem transparente do provedor de nuvem, protege contra um cenário específico: alguém rouba o disco físico ou o snapshot. É necessária, mas é quase inútil contra a ameaça real da maioria das aplicações, porque um atacante que comprometeu o processo com acesso ao banco lê tudo em texto claro — o sistema operacional já decifrou para ele. Disco cifrado protege o hardware, não a aplicação.
Cifragem em nível de coluna ou campo é o que protege dado sensível dentro de um banco potencialmente comprometido. Você cifra o CPF, o cartão, o token antes de gravar, e a coluna guarda apenas bytes opacos. Aqui mora a decisão que o time do começo do artigo errou: onde fica a chave? A resposta profissional é envelope encryption. Você não cifra o dado diretamente com uma chave mestra. Você gera uma chave de dados nova para cada registro ou lote — a DEK, data encryption key —, cifra o dado com ela, e então cifra a própria DEK com uma chave mestra — a KEK, key encryption key — que vive dentro de um KMS ou HSM e nunca sai de lá em texto claro. No banco você guarda o dado cifrado e a DEK cifrada, lado a lado. Para ler, você manda a DEK cifrada ao KMS, ele devolve a DEK em claro na memória por um instante, você decifra o dado e descarta a DEK.
Isso resolve três problemas de uma vez. A chave que realmente importa, a KEK, nunca toca o disco da sua aplicação nem o Git. Rotação fica barata: para trocar a KEK você só re-cifra as DEKs, que são minúsculas, sem tocar em terabytes de dado. E o raio de exposição de um vazamento fica contido, porque cada registro tem sua própria DEK. Serviços como AWS KMS, Google Cloud KMS, HashiCorp Vault e Azure Key Vault implementam exatamente esse fluxo, e a operação de "decifrar a DEK" é uma chamada de API auditada, com política de acesso e log de cada uso. A regra inegociável, que vale para todo este ecossistema, é uma só: chave nunca no código, nunca no repositório, nunca em variável de ambiente commitada. Chave vive em gerenciador de segredos, é injetada em runtime, e tem um cronograma de rotação escrito e testado — porque chave que nunca rotaciona é chave que, no dia do incidente, você descobre que não sabe rotacionar.
Criptografia em trânsito: proteger o dado em movimento#
Dado em trânsito é tudo que viaja pela rede entre dois pontos. A fronteira aqui é o TLS, e a regra é que ele cubra o caminho inteiro, não só o pedaço público. O erro clássico é terminar o TLS no balanceador de carga e trafegar do balanceador até os serviços internos em HTTP puro, no pressuposto de que "a rede interna é confiável". Rede interna não é confiável: um contêiner comprometido, um sniffer num segmento mal isolado, um serviço de log que captura pacotes — todos leem o texto claro desse trecho interno. Se o dado é sensível, o TLS não pode ter buracos no meio do trajeto.
Para comunicação entre serviços internos, o padrão robusto é mTLS — TLS mútuo, em que não só o cliente valida o certificado do servidor, mas o servidor também exige e valida o certificado do cliente. Isso transforma o certificado em identidade: o serviço A só aceita conexão de quem apresenta um certificado emitido pela CA interna, e um atacante sem esse certificado não fala com ninguém, mesmo estando dentro da rede. Malhas de serviço como Istio e Linkerd automatizam a emissão, rotação e validação desses certificados justamente porque fazer isso à mão em escala é inviável. O ganho é uma rede interna de confiança zero, onde estar dentro do perímetro não dá acesso a nada.
AEAD, nonce e derivação: os detalhes que decidem tudo#
Escolher AES não basta; o modo de operação é onde os sistemas quebram. O modo ECB, o mais ingênuo, cifra cada bloco independentemente, o que significa que blocos idênticos de texto claro viram blocos idênticos de texto cifrado — padrões visíveis vazam pela cifra, e a imagem clássica do pinguim ainda reconhecível depois de "cifrado" com ECB é o meme que resume o problema. ECB é proibido, ponto. O que você quer é cifragem autenticada, AEAD, e na prática isso significa AES-GCM ou ChaCha20-Poly1305. AEAD faz duas coisas ao mesmo tempo: cifra o dado e produz uma tag de autenticação que garante que o texto cifrado não foi adulterado. Sem autenticação, um atacante pode flipar bits do texto cifrado e você decifra lixo controlado sem perceber — a base de ataques de padding oracle. Cifrar sem autenticar é meio serviço.
O nonce — número usado uma vez, também chamado de IV nesse contexto — é a pegadinha mais perigosa do GCM. Ele não precisa ser secreto, mas precisa ser único para cada operação de cifragem sob a mesma chave. Reutilizar um nonce com a mesma chave em AES-GCM é catastrófico: não vaza só a mensagem, vaza material que permite forjar tags de autenticação para outras mensagens, quebrando a garantia inteira. Nunca use nonce fixo, nunca use contador que reinicia, nunca gere nonce com um PRNG fraco. Gere-o de fonte criptográfica robusta a cada operação, ou use um esquema determinístico que garanta unicidade. Esse é o tipo de erro que a biblioteca não impede e que só aparece em auditoria.
E há a derivação de chave. Nunca use uma senha diretamente como chave de cifragem: senhas têm entropia baixa e formato errado. Passe-a por uma função de derivação — Argon2, scrypt ou PBKDF2 com parâmetros fortes — que estica a senha em uma chave de tamanho correto e torna ataque de força bruta caro. Da mesma forma, para derivar múltiplas chaves de um segredo mestre, use um KDF como HKDF, que produz chaves independentes a partir de material de alta entropia. A tentação transversal a tudo isso é implementar criptografia própria — "roll your own crypto". Não faça. Use bibliotecas revisadas e de alto nível: libsodium, a ring e a rustls no Rust, a crypto padrão bem usada. Criptografia caseira falha de formas que só um criptanalista enxerga, e você não vai ser esse criptanalista no meio de uma sprint.
O ângulo de IA: proteger embeddings e pipelines#
Pipelines de IA acrescentam uma superfície que muita gente ignora porque parece "só matemática". Embeddings gerados a partir de dados sensíveis não são anônimos: são reconstruíveis. Ataques de inversão mostram que, a partir de vetores de embedding, é possível recuperar aproximações do texto original — o e-mail, o prontuário, o documento que gerou aquele vetor. Tratar o banco de vetores como se fosse dado inócuo é um erro. Aplique as mesmas regras: cifragem em repouso para o índice vetorial, TLS em trânsito entre o serviço de embedding e o vector store, e controle de acesso por tenant para não misturar vetores de clientes diferentes num mesmo espaço de busca. Chaves de API de provedores de modelo são segredo de primeira classe — KMS e injeção em runtime, jamais no cliente nem no notebook versionado. E dados de treino ou de contexto que passam por prompts merecem o mesmo cuidado de classificação que qualquer PII: uma vez dentro do pipeline, eles se espalham por logs, caches e traces se você não fechar cada porta.
Checklist prático para fechar#
Antes de considerar pronto qualquer código que toca criptografia, passe por estas perguntas em prosa, porque cada uma corresponde a um incidente real que já aconteceu com alguém. Onde está a chave, e ela consegue vazar junto com o dado que protege — se a resposta é "estão no mesmo lugar", pare tudo. Você está usando AEAD, isto é, AES-GCM ou ChaCha20-Poly1305, e não um modo sem autenticação, e jamais ECB. Cada operação de cifragem gera um nonce único sob aquela chave, sem reuso possível. Senhas vão para argon2id ou bcrypt com custo adequado, nunca para cifragem reversível nem para SHA cru. Comparações de segredo e MAC usam tempo constante. O TLS cobre o trajeto inteiro, sem trecho interno em texto claro, e comunicação serviço a serviço sensível usa mTLS. Você tem um procedimento de rotação de chave escrito e testado, não só uma intenção. E você está usando uma biblioteca criptográfica revisada, não uma implementação sua. Criptografia funciona quando é aborrecidamente convencional: os algoritmos padrão, bem parametrizados, com a chave guardada onde ninguém alcança e um plano para o dia em que algo der errado. A parte difícil nunca foi cifrar — foi gerenciar a chave e não confundir as ferramentas. Acerte esses dois e o resto é biblioteca.
