Referência de implementação da criptografia
A criptografia que é executada é mais estreita do que a criptografia que está escrita, e esta página existe por causa dessa diferença. Os sealed boxes do libsodium são a única criptografia de mensagens que o Railgun executa — e só são executados no caminho de cliente em que o módulo nativo libsignal está ausente. Na build desktop Electron, onde o libsignal de fato é carregado, o handler que criptografa mensagens diretas ainda é um espaço reservado que codifica o texto simples em base64. Mensagens de comunidade e de canal não são criptografadas em nenhum caminho. O protocolo no estilo Signal descrito na segunda metade desta página está escrito e revisado, e não está ligado a nenhum cliente que você possa baixar.
Esta é uma referência de arquitetura, não uma auditoria de segurança independente. As seções 1 e 2 descrevem o que é entregue. As seções 4 a 7 descrevem um projeto que o Railgun está construindo e estão marcadas como Planejado em toda parte. Nada aqui deve ser lido como afirmação de que um cliente Railgun impõe atualmente uma propriedade marcada como planejada.
O que é entregue hoje
| Caminho | Mecanismo | Estado |
|---|---|---|
| Mensagens diretas — builds de navegador e de desenvolvimento | Sealed box do libsodium — acordo de chaves X25519, criptografia autenticada XSalsa20-Poly1305. Selecionado quando o módulo nativo libsignal está ausente. | Criptografado |
| Mensagens diretas — build desktop Electron | O libsignal é carregado, então encryptDm é encaminhado para o handler IPC do Electron — um espaço reservado que codifica o texto simples em base64. O relay recebe texto legível. | Não criptografado |
| Mensagens de comunidade e de canal | Enviadas como texto simples embrulhado em base64. A criptografia de canais está escrita, mas retorna um marcador de texto simples. | Não criptografado |
| X3DH + Double Ratchet | Implementado sobre o libsignal, importado apenas como tipo. Nenhum caminho de execução o instancia. | Planejado |
| Armazenamento de chaves privadas | As chaves são geradas e mantidas no dispositivo. Nenhum caminho de código transmite material de chave privada ou de identidade. | Implementado |
1. Sealed boxes — a criptografia que é executada
Onde o Railgun de fato criptografa uma mensagem direta, ele o faz com um sealed box do libsodium — a construção crypto_box_seal. Ela combina o acordo de chaves X25519 com a criptografia autenticada XSalsa20-Poly1305. É criptografia real e bem revisada. É também uma construção deliberadamente simples, e seus limites importam. Quais clientes chegam a ela é o assunto da seção 2: as builds de navegador e de desenvolvimento chegam; a build desktop Electron não.
Como um sealed box é construído
// Sender has only the recipient's public key, PK_r
(pk_e, sk_e) = crypto_box_keypair() // fresh, single-use
nonce = BLAKE2b(pk_e || PK_r) // derived, not random
ct = crypto_box(plaintext, nonce, PK_r, sk_e)
sealed = pk_e || ct // ephemeral key is prepended
erase(sk_e)
O destinatário recupera a chave pública efêmera do início da mensagem, recalcula o mesmo segredo compartilhado com sua própria chave privada e abre o box. A tag Poly1305 faz com que um texto cifrado modificado falhe ao abrir, em vez de ser descriptografado em lixo.
O que isso lhe dá
- • Confidencialidade diante do relay e de qualquer um na rede.
- • Integridade — a adulteração é detectada, não descriptografada em silêncio.
- • Anonimato do remetente: a chave efêmera não revela nada sobre quem enviou.
- • Nenhuma configuração de sessão, então funciona quando o destinatário está offline.
O que isso não lhe dá
- • Nenhuma forward secrecy. A chave privada de longo prazo do destinatário abre todas as mensagens já enviadas a ele. Se ela vazar, o texto cifrado capturado vaza junto.
- • Nenhuma autenticação do remetente. Um sealed box é anônimo por construção; ele não prova nada sobre quem o escreveu.
- • Nenhuma recuperação pós-comprometimento. Não há ratchet para seguir adiante depois do comprometimento de uma chave.
Essas três lacunas são exatamente o que o X3DH e o Double Ratchet existem para fechar, e é por isso que esse trabalho está em andamento. Até que ele chegue, o resumo honesto de uma DM com sealed box é: forte contra um observador passivo da rede e contra o relay, fraco contra um adversário que eventualmente obtenha a chave do dispositivo do destinatário. Esse resumo só se aplica onde este código realmente é executado.
2. O que não é criptografado
Mensagens de comunidade e de canal — a maior parte do que os usuários do Railgun realmente escrevem — não são criptografadas hoje em nenhum caminho de cliente. A função de criptografia de canal existe, e é franca sobre si mesma: registra um aviso e retorna o texto simples embrulhado em base64 atrás de uma string marcadora, para que ninguém adiante o confunda com texto cifrado.
// What encryptChannel actually returns
logger.warn("Channel encryption not yet implemented")
ciphertext = base64("[DEVCRYPTO:PLAINTEXT]" + plaintext)
A consequência prática: para conteúdo de comunidade e de canal, o servidor do Railgun está na mesma posição de qualquer servidor de chat comum. Ele pode ler o que você escreve ali. A distribuição de chave de grupo — uma chave por canal entregue aos membros dentro de sealed boxes — é a correção planejada e não foi entregue.
Mensagens diretas na build desktop Electron
A segunda lacuna são as mensagens diretas no cliente que a maioria das pessoas usa, e é fácil enunciá-la ao contrário, então aqui está a lógica de seleção tal como o código a executa. initCrypto() pergunta ao processo principal se o libsignal foi carregado. @signalapp/libsignal-client é uma dependência real e é exigida na inicialização, então em uma build Electron entregue ele de fato carrega — e essa resposta é o que seleciona a implementação IPC do Electron em vez da do sealed box. Seu encryptDm encaminha para o handler crypto:encryptDm, que é um espaço reservado:
// crypto:encryptDm, Electron main process
const plaintextBytes = new TextEncoder().encode(plaintext)
ciphertext: toBase64(plaintextBytes) // not encryption
Portanto, os sealed boxes da seção 1 são alcançados quando o libsignal está ausente — no navegador e em desenvolvimento. No cliente desktop, uma mensagem direta chega ao relay como texto simples codificado em base64. Ali o libsignal é carregado para gerar uma chave de identidade e um pacote de prekeys; ele não estabelece sessão alguma e não faz ratchet de nada — crypto:ensureDmSession é um log de depuração sem corpo.
3. Curve25519 — a curva subjacente
Tanto os sealed boxes que são entregues quanto o projeto X3DH que não é se apoiam na mesma curva elíptica: Curve25519, projetada por Daniel J. Bernstein para troca de chaves Diffie-Hellman de alta velocidade. Esta seção descreve a curva em si e se aplica a ambos.
A equação da curva
y² = x³ + 486662x² + x
over the prime field 𝔽ₚ where p = 2²⁵⁵ − 19
O primo 2²⁵⁵ − 19 foi escolhido porque permite aritmética modular extremamente rápida. O coeficiente 486662 foi escolhido por ser o menor valor que produz uma curva segura com as propriedades de segurança exigidas.
Geração de chaves
1. Chave privada: Gere 32 bytes aleatórios usando um CSPRNG (gerador de números pseudoaleatórios criptograficamente seguro). Faça o clamping da chave zerando os 3 bits mais baixos e o bit mais alto, e definindo o segundo bit mais alto. Isso garante que a chave seja múltipla de 8 e esteja no intervalo válido.
a = random(32 bytes)
a[0] &= 248 // Clear low 3 bits
a[31] &= 127 // Clear high bit
a[31] |= 64 // Set second-highest bit
2. Chave pública: Calcule a multiplicação escalar da chave privada pelo ponto base G = 9.
A = a · G // Scalar multiplication on Curve25519
A segurança se apoia no Problema do Logaritmo Discreto em Curvas Elípticas (ECDLP): dados A e G, é computacionalmente inviável recuperar a. A Curve25519 oferece aproximadamente 128 bits de segurança.
Troca de chaves Diffie-Hellman
Duas partes (Alice e Bob) podem calcular um segredo compartilhado sem nunca transmiti-lo:
Alice: a (private), A = a·G (public)
Bob: b (private), B = b·G (public)
Alice computes: S = a · B = a · (b·G)
Bob computes: S = b · A = b · (a·G)
Both arrive at S = ab·G — the same shared secret
Um bisbilhoteiro que conhece A e B (ambas públicas) não consegue calcular S sem resolver o ECDLP. Essa é a suposição Diffie-Hellman Computacional (CDH).
Um sealed box é essa troca com um dos lados tornado efêmero e descartado. O X3DH, abaixo, é essa troca realizada quatro vezes sobre pares de chaves diferentes, de modo que o resultado autentique ambas as partes além de ocultar o conteúdo.
4. X3DH — Diffie-Hellman Triplo Estendido
PLANEJADOO X3DH resolve um problema difícil: como duas pessoas estabelecem uma sessão criptografada quando uma delas está offline? O Diffie-Hellman tradicional exige que ambas as partes estejam online. O X3DH usa pacotes de chaves enviados previamente para permitir o acordo de chaves assíncrono.
Tipos de chave
Chave de identidade (IK)
Par de chaves Curve25519 de longo prazo. Gerado uma única vez, identifica o usuário criptograficamente. Nunca muda, a menos que o usuário se registre novamente.
Pré-chave assinada (SPK)
Par de chaves Curve25519 de médio prazo, assinado pela chave de identidade. Rotacionado periodicamente. A assinatura prova que ele pertence ao titular da chave de identidade.
Pré-chave de uso único (OPK)
Pares de chaves Curve25519 efêmeros enviados em lotes. Cada um é usado uma vez e depois excluído. Fornece uma camada extra de forward secrecy para a mensagem inicial.
Chave efêmera (EK)
Um novo par de chaves Curve25519 gerado pelo remetente para cada nova sessão. Nunca armazenado no servidor.
O handshake X3DH
Quando Alice quer enviar mensagem a Bob (que pode estar offline), ela busca no servidor o pacote de pré-chaves de Bob e realiza quatro cálculos Diffie-Hellman:
// Alice has: IKₐ (her identity key), EKₐ (fresh ephemeral)
// Bob published: IKᵦ, SPKᵦ, OPKᵦ
DH1 = DH(IKₐ, SPKᵦ) // her identity ↔ his signed pre-key
DH2 = DH(EKₐ, IKᵦ) // her ephemeral ↔ his identity
DH3 = DH(EKₐ, SPKᵦ) // her ephemeral ↔ his signed pre-key
DH4 = DH(EKₐ, OPKᵦ) // her ephemeral ↔ his one-time pre-key
SK = KDF(DH1 || DH2 || DH3 || DH4)
// SK is the shared secret, derived via HKDF-SHA-256
Por que quatro operações DH?
- DH1 fornece autenticação mútua (ambas as chaves de identidade estão envolvidas)
- DH2 garante que a chave efêmera esteja vinculada à identidade de Bob
- DH3 fornece forward secrecy por meio da chave efêmera
- DH4 fornece forward secrecy adicional. Se nenhuma OPK estiver disponível, o X3DH ainda funciona apenas com DH1–DH3
DH1 é a operação que um sealed box não tem. É por isso que o caminho entregue não consegue dizer de quem veio uma mensagem.
KDF — função de derivação de chaves
As saídas DH brutas são combinadas usando HKDF-SHA-256 (função de derivação de chaves baseada em HMAC). O HKDF extrai entropia das saídas DH concatenadas e a expande em um segredo compartilhado uniformemente aleatório:
PRK = HKDF-Extract(salt="", input=DH1||DH2||DH3||DH4)
SK = HKDF-Expand(PRK, info="RailGunX3DH", length=32)
5. O algoritmo Double Ratchet
PLANEJADOAssim que o X3DH estabelece um segredo compartilhado inicial, o Double Ratchet assume. Ele usa dois "ratchets" interligados para derivar uma nova chave única para cada mensagem. O projeto protege as mensagens anteriores e pretende recuperar a proteção das mensagens futuras após um novo passo de ratchet.
Ratchet DH (assimétrico)
Cada vez que a direção da conversa muda (Alice → Bob, depois Bob → Alice), ocorre uma nova troca de chaves Diffie-Hellman usando chaves efêmeras novas. Isso avança a chave raiz um passo de "ratchet":
dh_out = DH(my_ratchet_key, their_ratchet_key)
root_key, chain_key = KDF(root_key, dh_out)
É isso que forneceria a segurança pós-comprometimento: um atacante que rouba o estado atual perde o acesso assim que ocorre um novo passo de ratchet DH.
Ratchet simétrico (cadeia de hash)
Entre os passos do ratchet DH, um ratchet baseado em hash deriva uma nova chave de mensagem a partir da chave de cadeia para cada mensagem:
message_key = HMAC-SHA256(chain_key, 0x01)
chain_key = HMAC-SHA256(chain_key, 0x02)
A chave de cadeia antiga é excluída após cada passo. A chave de mensagem criptografa exatamente uma mensagem e depois é excluída. Esse é o mecanismo de forward secrecy.
Progressão do ratchet
Visualizado, o Double Ratchet se parece com isto. Cada seta é uma função de mão única — você pode avançar, mas nunca voltar:
Root Key Chain:
RK₀ ──DH──→ RK₁ ──DH──→ RK₂ ──DH──→ RK₃ ...
Each RK step produces a Chain Key:
↓ ↓ ↓
CK₀ CK₁ CK₂
↓ ↓ ↓
Each CK step produces Message Keys:
CK₀→MK₀ CK₁→MK₃ CK₂→MK₅
CK₀→MK₁ CK₁→MK₄ CK₂→MK₆
CK₀→MK₂ CK₂→MK₇
Cada MKₙ criptografaria exatamente uma mensagem e depois seria excluída, de modo que um atacante que obtivesse MK₄ não poderia derivar MK₃ nem MK₅. No caminho do sealed box que é entregue hoje, não há chave por mensagem para roubar nem cadeia para percorrer — há uma única chave de longo prazo que abre tudo.
6. Forward secrecy — o que seria necessário
PLANEJADOForward secrecy significa que comprometer chaves de longo prazo não compromete chaves de sessão passadas. O Railgun não a tem hoje. Dois mecanismos a forneceriam, e ambos pertencem ao protocolo planejado descrito acima:
Chaves efêmeras no X3DH
O remetente gera um novo par de chaves efêmeras para cada nova sessão e exclui a metade privada após o handshake. Um atacante que mais tarde roube as chaves de identidade de ambas as partes ainda assim não consegue reconstruir o segredo da sessão. Os sealed boxes de fato usam uma chave efêmera nova do lado do remetente — mas a metade da troca correspondente ao destinatário é a chave de longo prazo dele, que é precisamente a razão pela qual a propriedade não se sustenta.
Exclusão das chaves do ratchet
O Double Ratchet exclui as chaves de cadeia e as chaves de mensagem antigas após o uso. A cadeia KDF é de mão única — dada CKₙ, você pode calcular CKₙ₊₁, mas não CKₙ₋₁, de modo que um comprometimento no instante T não revela nada enviado antes de T.
Segurança pós-comprometimento
O ratchet DH também fornece future secrecy: um atacante que compromete o estado atual da sessão o perde no passo de ratchet seguinte, que introduz uma aleatoriedade que ele não possui. Sem um ratchet, uma chave comprometida continua útil ao atacante por tempo indeterminado.
7. Arquitetura de relay e fila
O Railgun retransmite envelopes e pode enfileirá-los temporariamente para entrega offline. O relay é construído de modo a não precisar do texto simples das mensagens, mas hoje ele recebe texto simples em dois casos: mensagens de canal, que nenhum cliente criptografa, e mensagens diretas da build desktop Electron, cujo handler IPC codifica em base64 em vez de criptografar. Uma mensagem direta vinda do caminho do sealed box chega como texto cifrado que o relay não pode abrir.
O que o servidor vê
O servidor guarda:
- • Pacotes de chaves públicas
- • Informações de registro do usuário
- • Metadados e participação de comunidades/canais
- • Texto cifrado em sealed box que ele não pode abrir (DMs do caminho navegador/desenvolvimento)
- • Texto simples de DM codificado em base64 vindo da build desktop Electron
- • Conteúdo das mensagens de canal, até que a criptografia de canais seja entregue
- • Registros de auditoria de autenticação
O servidor nunca recebe:
- • Chaves privadas ou de identidade
- • Estado de sessão do cliente
- • Uma cópia durável do seu histórico de mensagens
As mensagens são retransmitidas em tempo real para destinatários online por WebSocket. Para dispositivos offline, os envelopes são enfileirados no Redis com um TTL máximo de 30 dias e excluídos assim que o dispositivo os confirma. Não há arquivo de mensagens no servidor — o histórico do cliente fica no armazenamento local do seu próprio dispositivo, então um dispositivo perdido é um histórico perdido.
8. Resumo das propriedades de segurança
| Propriedade | Mecanismo | Estado |
|---|---|---|
| Confidencialidade das DMs | Sealed boxes apenas no caminho navegador/desenvolvimento; o handler desktop Electron codifica em base64 | Parcial |
| Integridade das DMs | Tag Poly1305 no caminho do sealed box; o caminho desktop Electron envia base64 não autenticado | Parcial |
| As chaves permanecem no dispositivo | Armazenamento local de chaves; nenhum caminho de código transmite chaves privadas | Implementado |
| Entrega offline | Envelopes enfileirados no Redis com TTL de 30 dias, excluídos na confirmação | Implementado |
| Confidencialidade dos canais | A distribuição de chave de grupo não está implementada; o conteúdo é enviado legível | Não implementado |
| Autenticação do remetente | Os sealed boxes são anônimos por construção | Não implementado |
| Forward secrecy | Requer o Double Ratchet, que nenhum cliente instancia | Planejado |
| Recuperação pós-comprometimento | Requer o ratchet DH, que nenhum cliente instancia | Planejado |
| Auditoria independente | Nenhuma auditoria criptográfica de terceiros concluída é alegada | Não concluído |
| Garantia pós-quântica | Nenhuma garantia geral de segurança pós-quântica é alegada | Não alegado |
De onde vem esta página
Todo estado acima foi lido do código-fonte do cliente, e não de um documento de projeto. O repositório do cliente não é público no momento, então não podemos pedir que você mesmo verifique — o que é razão para pesar esta página como uma divulgação, não como prova. Perguntas sobre uma afirmação específica são bem-vindas em security@railgun.chat.