Dois Meses Depois: O Que Aprendi ao Construir um Mensageiro E2EE

Dois Meses Depois: O Que Aprendi ao Construir um Mensageiro E2EE

Após dois meses de desenvolvimento de um mensageiro com criptografia de ponta a ponta (E2EE), o autor compartilha suas descobertas sobre a complexidade da arquitetura, a importância do papel do servidor e os desafios da implementação de protocolos como o Double Ratchet e o suporte a múltiplos dispositivos.

MundiX News·26 de junho de 2026·15 min de leitura·👁 1 views

Dois meses atrás, publiquei um artigo sobre meu projeto pessoal — um mensageiro com criptografia de ponta a ponta (E2EE). Para ser honesto, não esperava que ele gerasse tanto interesse. Recebi comentários, observações, perguntas e críticas bastante úteis. Em alguns pontos, fui corrigido de forma pertinente. Em outros, fui levado a reconsiderar decisões que, naquele momento, me pareciam adequadas. E, o mais gratificante, algumas das funcionalidades que estavam apenas no roadmap conseguiram ser implementadas nesse período.

A primeira parte do artigo pode ser encontrada em: [link para o artigo anterior]. O projeto está disponível em: [link para o GitHub]. De modo geral, nesses dois meses, percebi uma coisa simples: criar um chat não é tão difícil. Criar um mensageiro E2EE já é mais complexo. Mas entender por que o Signal levou tantos anos para ser desenvolvido foi uma história completamente diferente. À primeira vista, parece que a documentação existe, o X3DH, o Double Ratchet, o WebCrypto, artigos e especificações estão disponíveis. Basta juntar tudo com cuidado. É nesse momento que, em algum lugar distante, um engenheiro do Signal começa a rir.

Para esclarecer, o Chaos não é um substituto para o Signal nem um "mensageiro seguro pronto para uso", mas sim um projeto pessoal de código aberto no qual estou explorando como os sistemas E2EE funcionam internamente. Inicialmente, eu queria apenas entender como o X3DH e o Double Ratchet funcionavam. Depois, tornou-se interessante criar algo mais parecido com um sistema real do que um exemplo abstrato de 200 linhas: um backend, um frontend, comunicação em tempo real, banco de dados, entrega de mensagens, múltiplos dispositivos, observabilidade, um cliente desktop, deploy e segurança, além de casos de uso estranhos que surgem no momento mais inoportuno. Gradualmente, o projeto deixou de ser um "chatzinho no Spring Boot" e começou a se transformar em um sistema onde qualquer decisão sobre chaves afeta subitamente o banco de dados, o WebSocket, a experiência do usuário (UX), a pré-visualização de mensagens, a autenticação, a recuperação de sessões e até mesmo o que pode ser exibido na lista de chats. É por isso que ele ainda não se tornou entediante.

Atualmente, o projeto cresceu consideravelmente. O diagrama é, claro, uma grande simplificação. Na realidade, tudo é um pouco mais complexo, pois qualquer projeto vivo tem a propriedade de se transformar gradualmente em um pequeno data center no seu notebook. Atualmente, o projeto inclui: um frontend em React; uma aplicação desktop em Electron; um backend em Spring Boot; API REST; STOMP/WebSocket para comunicação em tempo real; PostgreSQL para usuários, dispositivos, mensagens, envelopes, prekeys e anexos; Redis para limites de taxa, sessões, status online e refresh tokens; Docker Compose; Prometheus; Grafana; Loki; Caddy; e manifestos Kubernetes. No entanto, a principal conclusão aqui não é a quantidade de tecnologias. A principal conclusão é que E2EE não é um crypto-service separado nem uma única função no código. É uma propriedade arquitetural de todo o sistema. Se o servidor não deve ver o texto plano, isso afeta quase tudo. E é nesse momento que você começa a entender que E2EE não é uma função encrypt(), mas uma restrição que gradualmente se espalha por todo o sistema.

Uma das coisas que mais mudou nesse período foi a minha compreensão do papel do servidor. Em um mensageiro comum, o servidor é frequentemente a fonte da verdade para quase tudo: o texto da mensagem, a pré-visualização da última mensagem, a busca, o histórico, os status, os anexos, a indexação. Em um mensageiro E2EE, o servidor deve ser muito mais "burro". E isso é um elogio. Ele deve aceitar envelopes criptografados, armazenar o texto cifrado e entregá-lo aos dispositivos corretos. Todo o resto deve passar por ele o mínimo possível. A entrega simplificada funciona assim: Alice criptografa a mensagem localmente. Para cada dispositivo de Bob, um envelope criptografado separado é criado. O servidor armazena o texto cifrado. O servidor entrega os envelopes a todos os dispositivos de Bob. Bob descriptografa a mensagem localmente. O recibo de leitura é enviado de volta como um evento separado. O servidor, nesse caso, não vê o texto da mensagem. Mas é importante notar: isso não significa que o servidor "não sabe nada". Ele ainda vê metadados: quem está se comunicando com quem, quando ocorreram os eventos, aproximadamente quantos dados foram transferidos, quais dispositivos estão ativos. E este é um grande tópico separado.

Um dos momentos engraçados foi com a pré-visualização da última mensagem. No servidor, o campo content aparece aproximadamente assim: [encrypted]. Inicialmente, isso é um pouco irritante. Você abre o banco de dados, olha para a mensagem e vê não o texto, mas um placeholder. O primeiro pensamento: "Pronto, algo quebrou de novo". E então vem a compreensão: não, meu amigo, é exatamente assim que está funcionando. O servidor não deve saber o que está dentro da mensagem. Em um aplicativo comum, a pré-visualização da última mensagem é uma simples consulta SQL. Em um mensageiro E2EE, tudo é mais interessante. A pré-visualização precisa ser armazenada localmente no cliente após a descriptografia. O servidor pode armazenar apenas um placeholder seguro, pois, caso contrário, ele volta a saber algo que não deveria. Em algum momento, me peguei pensando que, se você realmente quer resolver um problema no servidor, primeiro deve se perguntar: "O servidor deve mesmo saber a resposta?". Muitas vezes, a resposta correta é não. E depois disso, a arquitetura se torna menos conveniente. Mas é mais honesta.

A maior mudança foi que eu finalmente implementei o DH Ratchet. Nos comentários do primeiro artigo, foi corretamente apontado que ainda não havia um Double Ratchet completo. Havia a configuração inicial da sessão via X3DH e a atualização simétrica das chaves de mensagem, mas faltava uma parte importante — o DH Ratchet. E sem ele, a recuperação de break-in (break-in recovery) não é adequada. Nesses dois meses, cheguei a essa parte. Se explicarmos de forma muito grosseira, o Double Ratchet consiste em duas ideias. A primeira é uma cadeia simétrica de chaves. Para cada mensagem, uma nova chave de mensagem é usada, e as chaves antigas gradualmente se tornam inúteis. A segunda é o DH Ratchet. Quando o interlocutor responde com uma nova chave DH, as partes recalculam a chave raiz (root key) e obtêm novas cadeias de envio/recebimento (sending/receiving chains). O mais importante aqui é a recuperação de break-in. Se um invasor obtiver de alguma forma a chave da cadeia atual, isso é ruim. Mas após uma nova troca de chaves DH, o estado antigo deixa de ajudar a descriptografar novas mensagens. Não é mágica. Não é proteção absoluta contra tudo. Mas é uma propriedade muito importante do protocolo. Simplificadamente, um passo do DH Ratchet no código se parece com isto:

javascript
async function dhRatchetStep(session, remotePublicKey) {
  // Alternamos para a nova chave pública DH do interlocutor
  session.remoteDhPublicKey = remotePublicKey;

  // Atualizamos a cadeia de recebimento
  const receivingSecret = await deriveSharedSecret(
    session.ownDhKeyPair.privateKey,
    remotePublicKey
  );

  const receivingKeys = await deriveRootAndChainKey(
    session.rootKey,
    receivingSecret
  );

  session.rootKey = receivingKeys.rootKey;
  session.receivingChainKey = receivingKeys.chainKey;

  // Geramos um novo par de chaves DH para nossa cadeia de envio
  session.ownDhKeyPair = await generateDhKeyPair();

  const sendingSecret = await deriveSharedSecret(
    session.ownDhKeyPair.privateKey,
    remotePublicKey
  );

  const sendingKeys = await deriveRootAndChainKey(
    session.rootKey,
    sendingSecret
  );

  session.rootKey = sendingKeys.rootKey;
  session.sendingChainKey = sendingKeys.chainKey;

  session.sentCount = 0;
  session.receivedCount = 0;
}

No papel, tudo parece bonito. E é aqui que me peguei pensando pela primeira vez que ler a especificação do Signal é muito mais fácil do que integrá-la a um sistema real. Na realidade, começam a surgir questões desagradáveis: o que fazer com mensagens fora de ordem? Quantas chaves de mensagem puladas podem ser armazenadas? O que acontece se um dispositivo ficou offline por muito tempo? Como não quebrar o suporte a múltiplos dispositivos (multi-device)? Como se recuperar após a reinstalação do cliente? Onde a conveniência termina e a redução da segurança começa? Quanto mais eu lia a especificação do Signal, mais eu passava a respeitar as pessoas que não apenas inventaram tudo isso, mas também o levaram ao estado de produto.

Quando um usuário tem apenas um dispositivo, você pode fingir que tudo é relativamente claro. Existe Alice. Existe Bob. Existe uma sessão entre eles. Mas então Bob ganha um telefone, um laptop e mais um navegador que ele abriu "por cinco minutos" e depois não fechou por meio ano. E a imagem simples acaba. Agora, a mensagem precisa ser enviada não "para o usuário Bob", mas para cada dispositivo de Bob separadamente. Cada dispositivo tem suas próprias chaves, prekeys, sessões e envelopes. O servidor, nesse caso, se transforma em um mensageiro bastante honesto: "Eu não sei o que está dentro da caixa, mas sei para quais endereços levá-la". E isso é normal. Mas o suporte a múltiplos dispositivos sem verificação de dispositivos é apenas metade do caminho. Tecnicamente, adicionar um novo dispositivo não é tão difícil. É mais difícil provar ao usuário que este é realmente o dispositivo dele, e não alguém que se conectou silenciosamente à conta. É por isso que os números de segurança (safety numbers) e a verificação de dispositivos (device verification) apareceram no roadmap.

Outra história que ficou bem marcada foi a de dois horas de depuração devido a dois padrões. O cliente assina os dados usando WebCrypto. O Java no servidor deve verificar a assinatura. As chaves estão corretas. Os dados estão corretos. O algoritmo está correto. Tudo parece que deveria funcionar. Mas a assinatura não passa. Após várias horas, descobriu-se que o problema não era "criptografia" no sentido assustador da palavra. O problema estava no formato da assinatura. O WebCrypto retorna a assinatura ECDSA no formato IEEE P1363. E o Java/BouncyCastle espera ASN.1 DER nesse ponto. Ambos os lados fazem seu trabalho honestamente. Apenas um diz "olá" em um idioma, o outro espera "hello" em outro, e você fica entre eles pensando em que momento a vida tomou um rumo errado. No final, foi necessário um conversor:

java
byte[] derSignature = convertP1363ToDer(rawSignature);

boolean valid = signature.verify(derSignature);

O código é pequeno. Consumiu muitos nervos. E este é provavelmente um dos tipos de bugs mais úteis. Porque depois dele, você entende melhor não apenas o seu código, mas também as fronteiras entre o navegador, o Java e as bibliotecas criptográficas.

É engraçado, mas Spring Boot, WebSocket, Docker e até mesmo Kubernetes não se mostraram a parte mais difícil. Sim, cada um deles tem suas peculiaridades. O Kubernetes, em geral, às vezes se comporta como se não fosse você quem está fazendo o deploy da aplicação, mas ele quem está conduzindo a entrevista. Mas os verdadeiros problemas não começaram aí. Os verdadeiros problemas começaram em torno de confiança, chaves e estado de sessões. Quanto mais o projeto avança, menos tempo é gasto em "apenas escrever código" e mais tempo é dedicado a entender qual comportamento deve ser considerado correto.

Resumindo, o projeto percorreu aproximadamente o seguinte caminho em dois meses:

Era: X3DH, Cliente Web, Dispositivo Único, Observabilidade Básica, Docker, Lógica de Chat do Servidor. Tornou-se: X3DH + DH Ratchet, Web + Desktop Electron, Multi-device, Prometheus + Grafana + Loki, Docker + Kubernetes, Servidor como roteador de envelopes criptografados.

Mas para mim, a principal mudança não está nesta lista. A principal mudança é que o modelo de pensamento mudou. No início do projeto, eu pensava aproximadamente assim: "Como posso enviar uma mensagem?". Agora, a pergunta soa diferente: "Quais dados o sistema tem o direito de saber para entregar a mensagem?". Este é um nível de inconveniência completamente diferente. E, parece, é aí que começa a verdadeira engenharia de E2EE.

Sim, parte do código repetitivo (boilerplate), testes e trechos rotineiros foram ajudados por Claude e ChatGPT. Mas quanto mais o projeto se desenvolvia, mais ficava claro que as questões sobre confiança, recuperação de sessões, comprometimento de dispositivos, forward secrecy e compromissos arquiteturais ainda precisavam ser resolvidas por conta própria. Em E2EE, o entendimento é mais importante do que a geração de código.

Para não criar falsas ilusões, aqui está o que eu mesmo ainda considero incompleto. Entrega de código frontend via Web. Mesmo que o backend não veja o texto plano, o cliente web ainda é carregado do servidor. Com um determinado modelo de ameaças, o servidor ou a infraestrutura de entrega podem tentar substituir o código do cliente. A aplicação desktop reduz parcialmente esse risco, mas não o elimina completamente. Números de segurança e verificação de dispositivos. Ainda não há um mecanismo conveniente que permita aos usuários verificar as impressões digitais das chaves e garantir que um novo dispositivo realmente pertença ao interlocutor. Sem isso, o suporte a múltiplos dispositivos permanece incompleto em termos de UX de confiança. Vazamento de metadados. O servidor não vê o texto das mensagens, mas ainda vê parte dos metadados: quando ocorreram os eventos, quais dispositivos estão conectados, para quem entregar o envelope, quais sessões estão ativas. Este é um conjunto separado de problemas que não pode ser honestamente ignorado. Revisão de segurança. O projeto não passou por uma auditoria criptográfica externa. E quando se trata de E2EE, é importante falar sobre isso abertamente. Uma auto-revisão de segurança é aproximadamente como ser seu próprio dentista: teoricamente interessante, praticamente melhor não fazer.

Atualmente, os planos incluem: Números de Segurança; Verificação de Dispositivos; Notificações Push; Cliente Android; revisão de segurança externa; trabalho contínuo em multi-device; melhoria da recuperação de sessões. Nesses dois meses, o projeto cresceu. Mas a principal conclusão não foi sobre a quantidade de código, nem sobre Docker, nem sobre Kubernetes, nem mesmo sobre Double Ratchet. A principal conclusão é que a complexidade do E2EE não começa onde aparecem AES-GCM ou X25519. Ela começa onde é preciso decidir: em quem se pode confiar; quais dados o servidor tem o direito de saber; o que fazer com vários dispositivos; como viver após um comprometimento; onde a segurança entra em conflito com a conveniência; como não quebrar tudo isso com um belo UX. Provavelmente é por isso que os grandes mensageiros se desenvolvem por anos. E eu, por enquanto, apenas continuo achando interessante investigar. E, parece, quanto mais fundo você cava, mais você começa a entender por que o Signal ainda lança artigos, especificações e novas versões do protocolo. Porque em tais sistemas, as perguntas, por algum motivo, nunca acabam. E sim, obrigado a todos que criticaram o primeiro artigo. Muitas das coisas que agora existem no projeto surgiram graças aos comentários. Então, se você vir um erro criptográfico, uma falha arquitetural ou apenas uma decisão estranha, não hesite em escrever. Da última vez, isso já ajudou.

🛡️⚡

Pare de pesquisar. Comece a hackear.

O MundiX é seu copiloto de pentest com IA: comandos exatos, análise de outputs e próximo passo na kill chain — em segundos.

Testar grátis por 7 dias →

Sem cartão para começar · Planos a partir de R$49/mês

📤 Compartilhar & Baixar

🧰 Ferramentas recomendadas

Divulgação: alguns links são patrocinados. Podemos receber comissão se você comprar — sem custo extra para você. Só indicamos o que faz sentido para a comunidade.

Aprendendo Kali Linux: Teste de segurança, pentest e hacking ético

Aprendendo Kali Linux: Teste de segurança, pentest e hacking ético

Com centenas de ferramentas pré-instaladas, a distribuição Kali Linux facilita o trabalho de os profissionais de segurança começarem a fazer testes de segurança rapidamente. No entanto, com mais de 600 ferramentas em seu arsenal, o Kali Linux também pode ser desafiador. A nova edição deste prático livro abrange as atualizações nas ferramentas e inclui uma melhor abordagem da análise forense e da engenharia reversa. Ric Messier, autor, não fica apenas no teste de segurança, mas também faz uma abordagem sobre a execução de análise forense, incluindo a análise em disco e na memória, assim como alguma análise básica de malware. • Explore as diversas ferramentas disponíveis no Kali Linux • Entenda o valor do teste de segurança e examine os tipos de teste disponíveis • Aprenda os aspectos básicos do pentest em todo o ciclo de vida do ataque • Instale o Kali Linux em vários sistemas, tanto físicos quanto virtuais • Descubra como usar diferentes ferramentas destinadas à segurança • Estruture um teste de segurança baseado nas ferramentas do Kali Linux • Estenda as ferramentas do Kali para criar técnicas de ataque avançadas • Use o Kali Linux para ajudar a criar relatórios quando o teste terminar “A abordagem concisa, clara e baseada na experiência adotada por Ric Messier para a introdução do Kali Linux e dos testes de cibersegurança é incomparável. Este livro é uma leitura excelente e acessível para iniciantes e um recurso valioso para qualquer pessoa.” —Alexander Arlt, Consultor sênior de segurança, Google

Ver na Amazon
Gshield 2 em 1 Hub Extensor Conector USB-C + USB-A e Adaptador de Rede Ethernet LAN RJ45 com 3 Entradas USB 3.0 até 5 Gbps em Liga de Alumínio para Computador e Notebook, Cinza

Gshield 2 em 1 Hub Extensor Conector USB-C + USB-A e Adaptador de Rede Ethernet LAN RJ45 com 3 Entradas USB 3.0 até 5 Gbps em Liga de Alumínio para Computador e Notebook, Cinza

Compatível com portas USB-C e USB-A, ideal para ampliar a conectividade de dispositivos como MacBook Pro e outros com portas USB-C. Inclui um adaptador USB-A extra, proporcionando uma conexão Ethernet estável e veloz de até 1 Gbps, perfeita para filmes, jogos online e videoconferências. Oferece três portas USB 3.0 com velocidades de transferência de até 5 Gbps, permitindo conectar mouse, teclado, discos rígidos e outros periféricos. Fabricado em alumínio durável, garantindo longa vida útil e resistência ao uso diário. Design compacto e leve, ideal para viagens de negócios e uso diário, facilitando o transporte e armazenamento. Funciona com Windows 10/8.1/8, Mac OS e Chrome OS, oferecendo versatilidade incomparável para diversas necessidades de conectividade. Assegura uma conectividade estável e rápida, perfeita para tarefas exigentes como transferência de dados, streaming e mais.

Ver na Amazon
Hacking APIs: Breaking Web Application Programming Interfaces

Hacking APIs: Breaking Web Application Programming Interfaces

Hacking APIs is a crash course on web API security testing that will prepare you to penetration-test APIs, reap high rewards on bug bounty programs, and make your own APIs more secure. You'll learn how REST and GraphQL APIs work in the wild and set up a streamlined API testing lab with Burp Suite and Postman. Then you'll master tools useful for reconnaissance, endpoint analysis, and fuzzing, such as Kiterunner and OWASP Amass. Next, you'll learn to perform common attacks, like those targeting an API's authentication mechanisms and the injection vulnerabilities commonly found in web applications. You'll also learn techniques for bypassing protections against these attacks. In the book's nine guided labs, which target intentionally vulnerable APIs, you'll practice: Enumerating APIs users and endpoints using fuzzing techniques Using Postman to discover an excessive data exposure vulnerability Performing a JSON Web Token attack against an API authentication process Combining multiple API attack techniques to perform a NoSQL injection Attacking a GraphQL API to uncover a broken object level authorization vulnerability

Ver oferta
Gray Hat Hacking: The Ethical Hacker's Handbook, Sixth Edition

Gray Hat Hacking: The Ethical Hacker's Handbook, Sixth Edition

Up-to-date strategies for thwarting the latest, most insidious network attacks This fully updated, industry-standard security resource shows, step by step, how to fortify computer networks by learning and applying effective ethical hacking techniques. Based on curricula developed by the authors at major security conferences and colleges, the book features actionable planning and analysis methods as well as practical steps for identifying and combating both targeted and opportunistic attacks. Gray Hat Hacking: The Ethical Hacker's Handbook, Sixth Edition clearly explains the enemy's devious weapons, skills, and tactics and offers field-tested remedies, case studies, and testing labs. You will get complete coverage of Internet of Things, mobile, and Cloud security along with penetration testing, malware analysis, and reverse engineering techniques. State-of-the-art malware, ransomware, and system exploits are thoroughly explained. Fully revised content includes 7 new chapters covering the latest threats Includes proof-of-concept code stored on the GitHub repository Authors train attendees at major security conferences, including RSA, Black Hat, Defcon, and B-Sides

Ver na Amazon
Bloqueador USB de privacidade de porta USB para PC, notebook, bloco de laptop,

Bloqueador USB de privacidade de porta USB para PC, notebook, bloco de laptop,

Proteção de privacidade aprimorada: protege o link de transmissão de dados para evitar roubo de informações, fornecendo proteção de segurança robusta que protege a privacidade do usuário durante transferências de arquivos e garante uma conexão segura para interações de dispositivos sem preocupações em vários ambientes Uso a longo prazo: a camada protetora resistente ao desgaste, combinada com um corpo de metal resistente, oferece gerenciamento de calor confiável e qualidade duradoura durante o uso diário Entrega eficiente de energia: a tecnologia de chip inteligente garante a identificação automática dos requisitos de energia, fornecendo carregamento eficiente alinhando-se com vários protocolos de carregamento rápido para maior conveniência Proteção contra sobrecarga: evitando riscos de sobrecarga, este bloqueador de dados USB protege a vida útil da bateria e garante um desempenho estável, mantendo um fluxo estável de energia para melhorar a longevidade do dispositivo de forma eficaz Prático de transportar: com atenção à portabilidade, este bloqueador de dados USB oferece um design compacto que é leve e fácil de transportar, melhorando a conveniência do usuário e operação eficiente

Ver na Amazon

📩 Newsletter MundiX

Receba novidades de cibersegurança + um checklist de pentest grátis. Sem spam.

Ao assinar você concorda em receber e-mails. Cancele quando quiser.