Torna alla home
Crittografia

Riferimento sull'implementazione della crittografia

La crittografia che viene eseguita è più ristretta della crittografia che è stata scritta, e questa pagina esiste per il divario tra le due. I sealed box di libsodium sono l'unica cifratura dei messaggi che Railgun esegue — e vengono eseguiti solo sul percorso client in cui il modulo nativo libsignal è assente. Nella build desktop Electron, dove libsignal viene effettivamente caricato, il gestore che cifra i messaggi diretti è ancora un segnaposto che codifica il testo in chiaro in base64. I messaggi di comunità e di canale non sono cifrati su nessun percorso. Il protocollo in stile Signal descritto nella seconda metà di questa pagina è scritto e revisionato, e non è collegato a nessun client che si possa scaricare.

Questo è un riferimento di architettura, non un audit di sicurezza indipendente. Le sezioni 1 e 2 descrivono ciò che viene distribuito. Le sezioni dalla 4 alla 7 descrivono un progetto verso cui Railgun sta lavorando e sono contrassegnate ovunque come Pianificato. Nulla di quanto è scritto qui deve essere letto come l'affermazione che un client Railgun applichi attualmente una proprietà contrassegnata come pianificata.

Che cosa viene distribuito oggi

PercorsoMeccanismoStato
Messaggi diretti — build browser e di sviluppoSealed box libsodium — accordo di chiave X25519, cifratura autenticata XSalsa20-Poly1305. Selezionato quando il modulo nativo libsignal è assente.Cifrato
Messaggi diretti — build desktop Electronlibsignal viene caricato, quindi encryptDm è instradato al gestore IPC di Electron — un segnaposto che codifica il testo in chiaro in base64. Il relay riceve testo leggibile.Non cifrato
Messaggi di comunità e di canaleInviati come testo in chiaro incapsulato in base64. La cifratura dei canali è scritta ma restituisce un marcatore di testo in chiaro.Non cifrato
X3DH + Double RatchetImplementato con libsignal, importato solo come tipo. Nessun percorso di runtime lo istanzia.Pianificato
Archiviazione delle chiavi privateLe chiavi sono generate e conservate sul dispositivo. Nessun percorso di codice trasmette materiale di chiave privata o di identità.Implementato

1. Sealed box — la cifratura che viene eseguita

Dove Railgun cifra effettivamente un messaggio diretto, lo fa con un sealed box di libsodium — la costruzione crypto_box_seal. Combina l'accordo di chiave X25519 con la cifratura autenticata XSalsa20-Poly1305. È crittografia reale e ben revisionata. È anche una costruzione deliberatamente semplice, e i suoi limiti contano. Quali client vi arrivano è l'argomento della sezione 2: le build browser e di sviluppo sì, la build desktop Electron no.

Come si costruisce un sealed box

// 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)

Il destinatario recupera la chiave pubblica effimera dall'inizio del messaggio, ricalcola lo stesso segreto condiviso con la propria chiave privata e apre il box. Il tag Poly1305 fa sì che un testo cifrato modificato non si apra, invece di decifrarsi in dati privi di senso.

Che cosa ti dà

  • Riservatezza nei confronti del relay e di chiunque si trovi sulla rete.
  • Integrità — la manomissione viene rilevata, non decifrata silenziosamente.
  • Anonimato del mittente: la chiave effimera non rivela nulla su chi lo ha inviato.
  • Nessuna configurazione di sessione, quindi funziona quando il destinatario è offline.

Che cosa non ti dà

  • Nessuna forward secrecy. La chiave privata a lungo termine del destinatario apre ogni messaggio che gli sia mai stato inviato. Se trapela, con essa trapela anche il testo cifrato intercettato.
  • Nessuna autenticazione del mittente. Un sealed box è anonimo per costruzione; non prova nulla su chi lo ha scritto.
  • Nessun recupero post-compromissione. Non esiste alcun ratchet per andare oltre la compromissione di una chiave.

Quelle tre lacune sono esattamente ciò che X3DH e il Double Ratchet esistono per colmare, ed è per questo che quel lavoro è in corso. Finché non arriverà, il riepilogo onesto di un DM con sealed box è: forte contro un osservatore passivo della rete e contro il relay, debole contro un avversario che alla fine ottenga la chiave del dispositivo di un destinatario. Quel riepilogo vale solo dove questo codice viene effettivamente eseguito.

2. Che cosa non è cifrato

I messaggi di comunità e di canale — la maggior parte di ciò che gli utenti Railgun scrivono davvero — non sono cifrati oggi su nessun percorso client. La funzione di cifratura dei canali esiste, ed è schietta su sé stessa: registra un avviso e restituisce il testo in chiaro incapsulato in base64 dietro una stringa marcatore, così che nessuno a valle lo scambi per testo cifrato.

// What encryptChannel actually returns

logger.warn("Channel encryption not yet implemented")

ciphertext = base64("[DEVCRYPTO:PLAINTEXT]" + plaintext)

La conseguenza pratica: per i contenuti di comunità e di canale, il server Railgun si trova nella stessa posizione di un qualsiasi server di chat ordinario. Può leggere ciò che vi scrivi. La distribuzione della chiave di gruppo — una chiave per canale consegnata ai membri dentro sealed box — è la correzione pianificata e non è distribuita.

Messaggi diretti nella build desktop Electron

La seconda lacuna riguarda i messaggi diretti sul client che la maggior parte delle persone usa, ed è facile enunciarla al contrario, quindi ecco la logica di selezione così come il codice la esegue. initCrypto() chiede al processo principale se libsignal è stato caricato. @signalapp/libsignal-client è una dipendenza reale ed è richiesta all'avvio, quindi in una build Electron distribuita viene caricato — e quella risposta è ciò che seleziona l'implementazione IPC di Electron invece di quella con sealed box. Il suo encryptDm inoltra al gestore crypto:encryptDm, che è un segnaposto:

// crypto:encryptDm, Electron main process

const plaintextBytes = new TextEncoder().encode(plaintext)

ciphertext: toBase64(plaintextBytes) // not encryption

Quindi i sealed box della sezione 1 vengono raggiunti quando libsignal è assente — nel browser e in sviluppo. Sul client desktop, un messaggio diretto arriva al relay come testo in chiaro codificato in base64. Lì libsignal è caricato per generare una chiave di identità e un pacchetto di prekey; non stabilisce alcuna sessione e non effettua alcun ratchet — crypto:ensureDmSession è un log di debug senza corpo.

3. Curve25519 — la curva sottostante

Sia i sealed box che vengono distribuiti sia il progetto X3DH che non lo è si basano sulla stessa curva ellittica: Curve25519, progettata da Daniel J. Bernstein per uno scambio di chiavi Diffie-Hellman ad alta velocità. Questa sezione descrive la curva in sé e si applica a entrambi.

L'equazione della curva

y² = x³ + 486662x² + x

over the prime field 𝔽ₚ where p = 2²⁵⁵ − 19

Il numero primo 2²⁵⁵ − 19 è stato scelto perché consente un'aritmetica modulare estremamente rapida. Il coefficiente 486662 è stato scelto come il valore più piccolo che produce una curva sicura con le proprietà di sicurezza richieste.

Generazione delle chiavi

1. Chiave privata: Genera 32 byte casuali usando un CSPRNG (generatore di numeri pseudocasuali crittograficamente sicuro). Applica il clamping alla chiave azzerando i 3 bit più bassi e il bit più alto, e impostando il secondo bit più alto. Questo garantisce che la chiave sia un multiplo di 8 e nell'intervallo valido.

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. Chiave pubblica: Calcola la moltiplicazione scalare della chiave privata con il punto base G = 9.

A = a · G // Scalar multiplication on Curve25519

La sicurezza si basa sul problema del logaritmo discreto su curva ellittica (ECDLP): dati A e G, è computazionalmente impraticabile recuperare a. Curve25519 fornisce circa 128 bit di sicurezza.

Scambio di chiavi Diffie-Hellman

Due parti (Alice e Bob) possono calcolare un segreto condiviso senza mai trasmetterlo:

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

Un intercettatore che conosce A e B (entrambe pubbliche) non può calcolare S senza risolvere l'ECDLP. Questa è l'ipotesi Diffie-Hellman computazionale (CDH).

Un sealed box è questo scambio con un lato reso effimero e poi scartato. X3DH, più sotto, è questo scambio eseguito quattro volte su coppie di chiavi diverse, in modo che il risultato autentichi entrambe le parti oltre a nascondere il contenuto.

4. X3DH — Diffie-Hellman triplo esteso

PIANIFICATO
Scritto con libsignal e ispezionabile nel codice sorgente. È importato solo come tipo — nessun client distribuito lo costruisce. Considera tutto ciò che è in questa sezione come il progetto di riferimento, non come il comportamento attuale.

X3DH risolve un problema difficile: come fanno due persone a stabilire una sessione cifrata quando una delle due è offline? Il Diffie-Hellman tradizionale richiede che entrambe le parti siano online. X3DH usa pacchetti di chiavi caricati in anticipo per consentire un accordo di chiave asincrono.

Tipi di chiave

Chiave di identità (IK)

Coppia di chiavi Curve25519 a lungo termine. Generata una sola volta, identifica l'utente in modo crittografico. Non cambia mai, a meno che l'utente non si registri di nuovo.

Pre-chiave firmata (SPK)

Coppia di chiavi Curve25519 a medio termine, firmata dalla chiave di identità. Ruotata periodicamente. La firma dimostra che appartiene al titolare della chiave di identità.

Pre-chiave monouso (OPK)

Coppie di chiavi Curve25519 effimere caricate in lotti. Ciascuna è usata una volta e poi eliminata. Fornisce un ulteriore livello di forward secrecy per il messaggio iniziale.

Chiave effimera (EK)

Una nuova coppia di chiavi Curve25519 generata dal mittente per ogni nuova sessione. Mai memorizzata lato server.

L'handshake X3DH

Quando Alice vuole scrivere a Bob (che potrebbe essere offline), recupera dal server il pacchetto di pre-chiavi di Bob ed esegue quattro calcoli 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

Perché quattro operazioni DH?

  • DH1 fornisce l'autenticazione reciproca (sono coinvolte entrambe le chiavi di identità)
  • DH2 garantisce che la chiave effimera sia legata all'identità di Bob
  • DH3 fornisce la forward secrecy tramite la chiave effimera
  • DH4 fornisce ulteriore forward secrecy. Se non è disponibile alcuna OPK, X3DH funziona comunque con le sole DH1–DH3

DH1 è l'operazione che un sealed box non ha. È il motivo per cui il percorso distribuito non può dirti da chi proviene un messaggio.

KDF — funzione di derivazione della chiave

Gli output DH grezzi sono combinati usando HKDF-SHA-256 (funzione di derivazione della chiave basata su HMAC). HKDF estrae entropia dagli output DH concatenati e la espande in un segreto condiviso uniformemente casuale:

PRK = HKDF-Extract(salt="", input=DH1||DH2||DH3||DH4)

SK = HKDF-Expand(PRK, info="RailGunX3DH", length=32)

5. L'algoritmo Double Ratchet

PIANIFICATO
Nessuna release di Railgun effettua oggi il ratchet delle chiavi. Sul percorso con sealed box ogni messaggio diretto è cifrato con la stessa chiave a lungo termine del destinatario; sul percorso desktop Electron non è cifrato affatto.

Una volta che X3DH ha stabilito un segreto condiviso iniziale, subentra il Double Ratchet. Usa due "ratchet" interdipendenti per derivare una nuova chiave unica per ogni messaggio. Il progetto protegge i messaggi precedenti ed è pensato per ripristinare la protezione dei messaggi futuri dopo un nuovo passo di ratchet.

Ratchet DH (asimmetrico)

Ogni volta che la direzione della conversazione cambia (Alice → Bob, poi Bob → Alice), avviene un nuovo scambio di chiavi Diffie-Hellman con chiavi effimere nuove. Questo fa avanzare la chiave radice di uno scatto di "ratchet":

dh_out = DH(my_ratchet_key, their_ratchet_key)

root_key, chain_key = KDF(root_key, dh_out)

Questo è ciò che fornirebbe la sicurezza post-compromissione: un aggressore che ruba lo stato attuale perde l'accesso non appena si verifica un nuovo passo di ratchet DH.

Ratchet simmetrico (catena di hash)

Tra un passo di ratchet DH e l'altro, un ratchet basato su hash deriva per ogni messaggio una nuova chiave di messaggio dalla chiave di catena:

message_key = HMAC-SHA256(chain_key, 0x01)

chain_key = HMAC-SHA256(chain_key, 0x02)

La vecchia chiave di catena viene eliminata dopo ogni passo. La chiave di messaggio cifra esattamente un messaggio, poi viene eliminata. Questo è il meccanismo della forward secrecy.

Progressione del ratchet

Visualizzato, il Double Ratchet ha questo aspetto. Ogni freccia è una funzione a senso unico — si può andare avanti ma mai indietro:

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₇

Ogni MKₙ cifrerebbe esattamente un messaggio e poi verrebbe eliminata, così che un aggressore che ottenga MK₄ non potrebbe derivare MK₃MK₅. Sul percorso con sealed box distribuito oggi non c'è alcuna chiave per messaggio da rubare né alcuna catena da percorrere — c'è un'unica chiave a lungo termine che apre tutto.

6. Forward secrecy — che cosa servirebbe

PIANIFICATO

La forward secrecy significa che la compromissione delle chiavi a lungo termine non compromette le chiavi di sessione passate. Railgun oggi non ce l'ha. Due meccanismi la fornirebbero, ed entrambi appartengono al protocollo pianificato descritto sopra:

Chiavi effimere in X3DH

Il mittente genera una nuova coppia di chiavi effimere per ogni nuova sessione ed elimina la metà privata dopo l'handshake. Un aggressore che in seguito rubi le chiavi di identità di entrambe le parti non può comunque ricostruire il segreto di sessione. I sealed box usano effettivamente una chiave effimera nuova sul lato del mittente — ma la metà dello scambio del destinatario è la sua chiave a lungo termine, ed è precisamente per questo che la proprietà non vale.

Eliminazione delle chiavi del ratchet

Il Double Ratchet elimina dopo l'uso le vecchie chiavi di catena e le chiavi di messaggio. La catena KDF è a senso unico — data CKₙ, puoi calcolare CKₙ₊₁ ma non CKₙ₋₁, quindi una compromissione al tempo T non rivela nulla di ciò che è stato inviato prima di T.

Sicurezza post-compromissione

Il ratchet DH fornisce anche la future secrecy: un aggressore che compromette lo stato di sessione attuale lo perde al passo di ratchet successivo, che introduce una casualità di cui non dispone. Senza un ratchet, una chiave compromessa resta utile all'aggressore a tempo indeterminato.

7. Architettura di relay e coda

Railgun inoltra le buste e può metterle temporaneamente in coda per la consegna offline. Il relay è costruito in modo da non aver bisogno del testo in chiaro dei messaggi, ma oggi riceve testo in chiaro in due casi: i messaggi di canale, che nessun client cifra, e i messaggi diretti dalla build desktop Electron, il cui gestore IPC codifica in base64 invece di cifrare. Un messaggio diretto proveniente dal percorso con sealed box arriva come testo cifrato che il relay non può aprire.

Che cosa vede il server

Il server conserva:

  • Pacchetti di chiavi pubbliche
  • Informazioni di registrazione dell'utente
  • Metadati e appartenenze di comunità/canali
  • Testo cifrato in sealed box che non può aprire (DM dal percorso browser/sviluppo)
  • Testo in chiaro di DM codificato in base64 dalla build desktop Electron
  • Contenuto dei messaggi di canale, finché non sarà distribuita la cifratura dei canali
  • Record di audit dell'autenticazione

Il server non riceve mai:

  • Chiavi private o di identità
  • Stato della sessione del client
  • Una copia durevole della cronologia dei tuoi messaggi

I messaggi sono inoltrati in tempo reale ai destinatari online tramite WebSocket. Per i dispositivi offline, le buste sono messe in coda in Redis con un TTL massimo di 30 giorni ed eliminate una volta che il dispositivo le ha confermate. Non esiste alcun archivio dei messaggi lato server — la cronologia del client risiede nell'archiviazione locale sul tuo dispositivo, quindi un dispositivo perso è una cronologia persa.

8. Riepilogo delle proprietà di sicurezza

ProprietàMeccanismoStato
Riservatezza dei DMSealed box solo sul percorso browser/di sviluppo; il gestore desktop Electron codifica in base64Parziale
Integrità dei DMTag Poly1305 sul percorso con sealed box; il percorso desktop Electron invia base64 non autenticatoParziale
Le chiavi restano sul dispositivoArchivio di chiavi locale; nessun percorso di codice trasmette chiavi privateImplementato
Consegna offlineBuste in coda in Redis con un TTL di 30 giorni, eliminate alla confermaImplementato
Riservatezza dei canaliLa distribuzione della chiave di gruppo non è implementata; il contenuto è inviato leggibileNon implementato
Autenticazione del mittenteI sealed box sono anonimi per costruzioneNon implementato
Forward secrecyRichiede il Double Ratchet, che nessun client istanziaPianificato
Recupero post-compromissioneRichiede il ratchet DH, che nessun client istanziaPianificato
Audit indipendenteNon si dichiara alcun audit crittografico di terze parti completatoNon completato
Garanzia post-quantisticaNon si dichiara alcuna garanzia generale di sicurezza post-quantisticaNon dichiarato

Da dove viene questa pagina

Ogni stato riportato sopra è stato letto dal codice sorgente del client e non da un documento di progettazione. Il repository del client attualmente non è pubblico, quindi non possiamo chiederti di verificarlo tu stesso — il che è un motivo per valutare questa pagina come una dichiarazione, non come una prova. Le domande su una specifica affermazione sono benvenute a security@railgun.chat.