Zurück zur Startseite
Kryptografie

Referenz zur Verschlüsselungsimplementierung

Die Verschlüsselung, die tatsächlich läuft, ist enger gefasst als die Verschlüsselung, die geschrieben wurde, und genau diese Lücke ist der Gegenstand dieser Seite. libsodium sealed boxes sind die einzige Nachrichtenverschlüsselung, die Railgun ausführt — und sie werden ausschließlich auf dem Client-Pfad ausgeführt, auf dem das native libsignal-Modul fehlt. Im Electron-Desktop-Build, wo libsignal sehr wohl geladen wird, ist der Handler, der Direktnachrichten verschlüsselt, nach wie vor ein Platzhalter, der den Klartext base64-kodiert. Community- und Kanalnachrichten werden auf keinem Pfad verschlüsselt. Das in der zweiten Hälfte dieser Seite beschriebene Protokoll im Signal-Stil ist geschrieben und geprüft, und es ist in keinen Client eingebunden, den Sie herunterladen können.

Dies ist eine Architekturreferenz, kein unabhängiges Sicherheitsaudit. Die Abschnitte 1 und 2 beschreiben das, was ausgeliefert wird. Die Abschnitte 4 bis 7 beschreiben ein Design, auf das Railgun hinarbeitet, und sind durchgehend als Geplant gekennzeichnet. Nichts hier darf als Behauptung gelesen werden, dass ein Railgun-Client derzeit eine als geplant markierte Eigenschaft durchsetzt.

Was heute ausgeliefert wird

PfadMechanismusStatus
Direktnachrichten — Browser- und Entwicklungs-Buildslibsodium sealed box — X25519-Schlüsselvereinbarung, authentifizierte Verschlüsselung mit XSalsa20-Poly1305. Wird gewählt, wenn das native libsignal-Modul fehlt.Verschlüsselt
Direktnachrichten — Electron-Desktop-Buildlibsignal wird geladen, daher wird encryptDm an den Electron-IPC-Handler geleitet — einen Platzhalter, der den Klartext base64-kodiert. Das Relay empfängt lesbaren Text.Nicht verschlüsselt
Community- und KanalnachrichtenWerden als base64-verpackter Klartext gesendet. Die Kanalverschlüsselung ist geschrieben, gibt aber eine Klartext-Markierung zurück.Nicht verschlüsselt
X3DH + Double RatchetGegen libsignal implementiert, nur als Typ importiert. Kein Laufzeitpfad instanziiert es.Geplant
Speicherung privater SchlüsselSchlüssel werden auf dem Gerät erzeugt und dort gehalten. Kein Codepfad überträgt private oder Identitätsschlüssel.Implementiert

1. Sealed boxes — die Verschlüsselung, die läuft

Wo Railgun eine Direktnachricht überhaupt verschlüsselt, geschieht das mit einer libsodium sealed box — der Konstruktion crypto_box_seal. Sie verbindet die X25519-Schlüsselvereinbarung mit der authentifizierten Verschlüsselung XSalsa20-Poly1305. Das ist echte, gut geprüfte Kryptografie. Es ist zugleich eine bewusst einfache Konstruktion, und ihre Grenzen sind von Bedeutung. Welche Clients sie erreichen, behandelt Abschnitt 2: die Browser- und Entwicklungs-Builds tun es, der Electron-Desktop-Build nicht.

Wie eine sealed box aufgebaut wird

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

Die empfangende Seite gewinnt den ephemeren öffentlichen Schlüssel vom Anfang der Nachricht zurück, berechnet mit dem eigenen privaten Schlüssel dasselbe gemeinsame Geheimnis erneut und öffnet die Box. Der Poly1305-Tag bewirkt, dass ein verändertes Chiffrat sich gar nicht erst öffnen lässt, statt zu Unsinn entschlüsselt zu werden.

Was Ihnen das bringt

  • Vertraulichkeit gegenüber dem Relay und gegenüber jedem auf der Leitung.
  • Integrität — Manipulation wird erkannt und nicht stillschweigend entschlüsselt.
  • Anonymität des Absenders: Der ephemere Schlüssel verrät nichts darüber, wer gesendet hat.
  • Kein Sitzungsaufbau nötig, es funktioniert also auch, wenn die empfangende Seite offline ist.

Was Ihnen das nicht bringt

  • Keine Forward Secrecy. Der langlebige private Schlüssel der empfangenden Seite öffnet jede Nachricht, die je an sie gesendet wurde. Wenn er durchsickert, sickert mitgeschnittenes Chiffrat mit ihm durch.
  • Keine Absenderauthentifizierung. Eine sealed box ist konstruktionsbedingt anonym; sie beweist nichts darüber, wer sie geschrieben hat.
  • Keine Wiederherstellung nach einer Kompromittierung. Es gibt keine Ratchet, die über eine Schlüsselkompromittierung hinausführt.

Genau diese drei Lücken sollen X3DH und die Double Ratchet schließen, und deshalb ist diese Arbeit im Gange. Bis sie fertig ist, lautet die ehrliche Zusammenfassung einer sealed-box-DM: stark gegenüber einem passiven Netzbeobachter und gegenüber dem Relay, schwach gegenüber einem Angreifer, der irgendwann den Geräteschlüssel der empfangenden Seite erlangt. Diese Zusammenfassung gilt nur dort, wo dieser Code tatsächlich läuft.

2. Was nicht verschlüsselt wird

Community- und Kanalnachrichten — der Großteil dessen, was die meisten Railgun-Nutzer tatsächlich schreiben — werden heute auf keinem Client-Pfad verschlüsselt. Die Funktion zur Kanalverschlüsselung existiert, und sie ist offen über sich selbst: Sie protokolliert eine Warnung und gibt den Klartext in base64 verpackt hinter einer Markierungszeichenkette zurück, damit ihn niemand weiter unten für ein Chiffrat hält.

// What encryptChannel actually returns

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

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

Die praktische Folge: Für Community- und Kanalinhalte befindet sich der Railgun-Server in derselben Lage wie jeder gewöhnliche Chat-Server. Er kann lesen, was Sie dort schreiben. Die Verteilung von Gruppenschlüsseln — ein Schlüssel pro Kanal, der den Mitgliedern in sealed boxes übergeben wird — ist die geplante Lösung und wird nicht ausgeliefert.

Direktnachrichten im Electron-Desktop-Build

Die zweite Lücke sind Direktnachrichten auf dem Client, den die meisten Menschen verwenden, und sie lässt sich leicht verkehrt herum darstellen; hier deshalb die Auswahllogik so, wie der Code sie ausführt. initCrypto() fragt den Hauptprozess, ob libsignal geladen wurde. @signalapp/libsignal-client ist eine echte Abhängigkeit und wird beim Start eingebunden, in einem ausgelieferten Electron-Build wird es also tatsächlich geladen — und diese Antwort ist es, die die Electron-IPC-Implementierung anstelle der sealed-box-Implementierung auswählt. Ihr encryptDm leitet an den Handler crypto:encryptDm weiter, und der ist ein Platzhalter:

// crypto:encryptDm, Electron main process

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

ciphertext: toBase64(plaintextBytes) // not encryption

Die sealed boxes aus Abschnitt 1 werden also erreicht, wenn libsignal fehlt — im Browser und in der Entwicklung. Auf dem Desktop-Client erreicht eine Direktnachricht das Relay als base64-kodierter Klartext. libsignal wird dort geladen, um einen Identitätsschlüssel und ein Prekey-Bundle zu erzeugen; es baut keine Sitzung auf und ratchet nichts — crypto:ensureDmSession ist ein Debug-Log ohne Rumpf.

3. Curve25519 — die zugrunde liegende Kurve

Sowohl die sealed boxes, die ausgeliefert werden, als auch das X3DH-Design, das nicht ausgeliefert wird, beruhen auf derselben elliptischen Kurve: Curve25519, entworfen von Daniel J. Bernstein für sehr schnellen Diffie-Hellman-Schlüsselaustausch. Dieser Abschnitt beschreibt die Kurve selbst und gilt für beide.

Die Kurvengleichung

y² = x³ + 486662x² + x

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

Die Primzahl 2²⁵⁵ − 19 wurde gewählt, weil sie extrem schnelle modulare Arithmetik ermöglicht. Der Koeffizient 486662 wurde als kleinster Wert gewählt, der eine sichere Kurve mit den geforderten Sicherheitseigenschaften ergibt.

Schlüsselerzeugung

1. Privater Schlüssel: Erzeugen Sie 32 zufällige Bytes mit einem CSPRNG (kryptografisch sicherer Pseudozufallszahlengenerator). Klemmen Sie den Schlüssel, indem Sie die untersten 3 Bits und das oberste Bit löschen und das zweithöchste Bit setzen. Das stellt sicher, dass der Schlüssel ein Vielfaches von 8 ist und im gültigen Bereich liegt.

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. Öffentlicher Schlüssel: Berechnen Sie die Skalarmultiplikation des privaten Schlüssels mit dem Basispunkt G = 9.

A = a · G // Scalar multiplication on Curve25519

Die Sicherheit beruht auf dem Problem des diskreten Logarithmus auf elliptischen Kurven (ECDLP): Bei gegebenem A und G ist es rechnerisch nicht durchführbar, a zu rekonstruieren. Curve25519 bietet ungefähr 128 Bit Sicherheit.

Diffie-Hellman-Schlüsselaustausch

Zwei Parteien (Alice und Bob) können ein gemeinsames Geheimnis berechnen, ohne es je zu übertragen:

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

Ein Lauscher, der A und B kennt (beide öffentlich), kann S nicht berechnen, ohne das ECDLP zu lösen. Das ist die Computational-Diffie-Hellman-Annahme (CDH).

Eine sealed box ist dieser Austausch, bei dem eine Seite ephemer gemacht und anschließend verworfen wird. X3DH, weiter unten, ist derselbe Austausch, viermal über verschiedene Schlüsselpaare ausgeführt, sodass das Ergebnis beide Parteien authentifiziert und nicht nur den Inhalt verbirgt.

4. X3DH — Extended Triple Diffie-Hellman

GEPLANT
Gegen libsignal geschrieben und im Quellcode nachprüfbar. Es wird nur als Typ importiert — kein ausgelieferter Client konstruiert es. Betrachten Sie alles in diesem Abschnitt als Zieldesign, nicht als aktuelles Verhalten.

X3DH löst ein schwieriges Problem: Wie bauen zwei Personen eine verschlüsselte Sitzung auf, wenn eine von ihnen offline ist? Klassisches Diffie-Hellman verlangt, dass beide Parteien online sind. X3DH nutzt vorab hochgeladene Schlüsselbündel, um eine asynchrone Schlüsselvereinbarung zu ermöglichen.

Schlüsseltypen

Identitätsschlüssel (IK)

Langlebiges Curve25519-Schlüsselpaar. Einmal erzeugt, identifiziert es die Nutzerin oder den Nutzer kryptografisch. Es ändert sich nur, wenn eine Neuregistrierung erfolgt.

Signierter Prekey (SPK)

Mittelfristiges Curve25519-Schlüsselpaar, signiert mit dem Identitätsschlüssel. Wird regelmäßig rotiert. Die Signatur belegt, dass es dem Inhaber des Identitätsschlüssels gehört.

Einmal-Prekey (OPK)

Ephemere Curve25519-Schlüsselpaare, die stapelweise hochgeladen werden. Jedes wird einmal benutzt und dann gelöscht. Bietet eine zusätzliche Ebene Forward Secrecy für die erste Nachricht.

Ephemerer Schlüssel (EK)

Ein frisches Curve25519-Schlüsselpaar, das die sendende Seite für jede neue Sitzung erzeugt. Wird niemals serverseitig gespeichert.

Der X3DH-Handshake

Wenn Alice Bob eine Nachricht schreiben will (der offline sein kann), holt sie Bobs Prekey-Bundle vom Server und führt vier Diffie-Hellman-Berechnungen durch:

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

Warum vier DH-Operationen?

  • DH1 liefert gegenseitige Authentifizierung (beide Identitätsschlüssel sind beteiligt)
  • DH2 stellt sicher, dass der ephemere Schlüssel an Bobs Identität gebunden ist
  • DH3 liefert Forward Secrecy über den ephemeren Schlüssel
  • DH4 liefert zusätzliche Forward Secrecy. Ist kein OPK verfügbar, funktioniert X3DH auch allein mit DH1–DH3

DH1 ist die Operation, die eine sealed box nicht hat. Deshalb kann der ausgelieferte Pfad Ihnen nicht sagen, von wem eine Nachricht stammt.

KDF — Schlüsselableitungsfunktion

Die rohen DH-Ausgaben werden mit HKDF-SHA-256 (HMAC-basierte Schlüsselableitungsfunktion) kombiniert. HKDF extrahiert Entropie aus den aneinandergehängten DH-Ausgaben und expandiert sie zu einem gleichverteilt zufälligen gemeinsamen Geheimnis:

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

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

5. Der Double-Ratchet-Algorithmus

GEPLANT
Kein Railgun-Release ratcht heute Schlüssel. Auf dem sealed-box-Pfad wird jede Direktnachricht an denselben langlebigen Empfängerschlüssel verschlüsselt; auf dem Electron-Desktop-Pfad wird sie überhaupt nicht verschlüsselt.

Sobald X3DH ein anfängliches gemeinsames Geheimnis hergestellt hat, übernimmt die Double Ratchet. Sie verwendet zwei ineinandergreifende "Ratchets", um für jede Nachricht einen neuen, einzigartigen Schlüssel abzuleiten. Das Design schützt frühere Nachrichten und soll den Schutz künftiger Nachrichten nach einem frischen Ratchet-Schritt wiederherstellen.

DH-Ratchet (asymmetrisch)

Jedes Mal, wenn die Gesprächsrichtung wechselt (Alice → Bob, dann Bob → Alice), findet ein neuer Diffie-Hellman-Schlüsselaustausch mit frischen ephemeren Schlüsseln statt. Das "ratcht" den Wurzelschlüssel vorwärts:

dh_out = DH(my_ratchet_key, their_ratchet_key)

root_key, chain_key = KDF(root_key, dh_out)

Das ist es, was Post-Compromise-Sicherheit liefern würde: Ein Angreifer, der den aktuellen Zustand stiehlt, verliert den Zugriff, sobald ein neuer DH-Ratchet-Schritt erfolgt.

Symmetrische Ratchet (Hash-Kette)

Zwischen den DH-Ratchet-Schritten leitet eine hashbasierte Ratchet für jede Nachricht einen neuen Nachrichtenschlüssel aus dem Kettenschlüssel ab:

message_key = HMAC-SHA256(chain_key, 0x01)

chain_key = HMAC-SHA256(chain_key, 0x02)

Der alte Kettenschlüssel wird nach jedem Schritt gelöscht. Der Nachrichtenschlüssel verschlüsselt genau eine Nachricht und wird dann gelöscht. Das ist der Mechanismus für Forward Secrecy.

Ratchet-Verlauf

Bildlich dargestellt sieht die Double Ratchet so aus. Jeder Pfeil ist eine Einwegfunktion — man kann vorwärts gehen, aber niemals rückwärts:

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₇

Jedes MKₙ würde genau eine Nachricht verschlüsseln und dann gelöscht werden, sodass ein Angreifer, der MK₄ erlangt, weder MK₃ noch MK₅ ableiten könnte. Auf dem sealed-box-Pfad, der heute ausgeliefert wird, gibt es keinen Schlüssel pro Nachricht, den man stehlen könnte, und keine Kette, die man entlanggehen könnte — es gibt einen langlebigen Schlüssel, der alles öffnet.

6. Forward Secrecy — was dafür nötig wäre

GEPLANT

Forward Secrecy bedeutet, dass die Kompromittierung langlebiger Schlüssel frühere Sitzungsschlüssel nicht kompromittiert. Railgun hat sie heute nicht. Zwei Mechanismen würden sie liefern, und beide gehören zum oben beschriebenen geplanten Protokoll:

Ephemere Schlüssel in X3DH

Die sendende Seite erzeugt für jede neue Sitzung ein frisches ephemeres Schlüsselpaar und löscht dessen private Hälfte nach dem Handshake. Ein Angreifer, der später die Identitätsschlüssel beider Parteien stiehlt, kann das Sitzungsgeheimnis dennoch nicht rekonstruieren. Sealed boxes verwenden auf der Senderseite durchaus einen frischen ephemeren Schlüssel — aber die Hälfte des Austauschs auf der Empfängerseite ist deren langlebiger Schlüssel, und genau deshalb gilt die Eigenschaft nicht.

Löschen der Ratchet-Schlüssel

Die Double Ratchet löscht alte Kettenschlüssel und Nachrichtenschlüssel nach der Verwendung. Die KDF-Kette ist einwegig — aus CKₙ lässt sich CKₙ₊₁ berechnen, aber nicht CKₙ₋₁, sodass eine Kompromittierung zum Zeitpunkt T nichts preisgibt, was vor T gesendet wurde.

Post-Compromise-Sicherheit

Die DH-Ratchet liefert außerdem Future Secrecy: Ein Angreifer, der den aktuellen Sitzungszustand kompromittiert, verliert ihn beim nächsten Ratchet-Schritt, der Zufall einbringt, den er nicht hat. Ohne Ratchet bleibt ein kompromittierter Schlüssel für den Angreifer unbegrenzt nützlich.

7. Relay- und Queue-Architektur

Railgun leitet Umschläge weiter und kann sie für die Zustellung an Offline-Geräte vorübergehend in eine Queue legen. Das Relay ist so gebaut, dass es den Nachrichtenklartext nicht benötigt, doch heute empfängt es in zwei Fällen Klartext: bei Kanalnachrichten, die kein Client verschlüsselt, und bei Direktnachrichten aus dem Electron-Desktop-Build, dessen IPC-Handler base64-kodiert statt zu verschlüsseln. Eine Direktnachricht vom sealed-box-Pfad trifft als Chiffrat ein, das das Relay nicht öffnen kann.

Was der Server sieht

Der Server hält:

  • Bündel öffentlicher Schlüssel
  • Registrierungsdaten der Nutzerkonten
  • Metadaten und Mitgliedschaften von Communitys und Kanälen
  • Sealed-box-Chiffrat, das er nicht öffnen kann (DMs aus dem Browser-/Entwicklungspfad)
  • Base64-kodierten DM-Klartext aus dem Electron-Desktop-Build
  • Inhalte von Kanalnachrichten, bis die Kanalverschlüsselung ausgeliefert wird
  • Audit-Aufzeichnungen zur Authentifizierung

Der Server erhält niemals:

  • Private oder Identitätsschlüssel
  • Sitzungszustand des Clients
  • Eine dauerhafte Kopie Ihres Nachrichtenverlaufs

Nachrichten werden in Echtzeit über WebSocket an Online-Empfänger weitergeleitet. Für Offline-Geräte werden Umschläge in Redis mit einer maximalen TTL von 30 Tagen in die Queue gelegt und gelöscht, sobald das Gerät sie bestätigt. Es gibt kein serverseitiges Nachrichtenarchiv — der Client-Verlauf liegt im lokalen Speicher Ihres eigenen Geräts, ein verlorenes Gerät ist also ein verlorener Verlauf.

8. Zusammenfassung der Sicherheitseigenschaften

EigenschaftMechanismusStatus
Vertraulichkeit von DirektnachrichtenSealed boxes nur auf dem Browser-/Entwicklungspfad; der Electron-Desktop-Handler base64-kodiertTeilweise
Integrität von DirektnachrichtenPoly1305-Tag auf dem sealed-box-Pfad; der Electron-Desktop-Pfad sendet nicht authentifiziertes base64Teilweise
Schlüssel bleiben auf dem GerätLokaler Schlüsselspeicher; kein Codepfad überträgt private SchlüsselImplementiert
Zustellung an Offline-GeräteUmschläge in Redis mit einer TTL von 30 Tagen in der Queue, bei Bestätigung gelöschtImplementiert
Vertraulichkeit von KanälenDie Verteilung von Gruppenschlüsseln ist nicht implementiert; Inhalte werden lesbar gesendetNicht implementiert
AbsenderauthentifizierungSealed boxes sind konstruktionsbedingt anonymNicht implementiert
Forward SecrecyErfordert die Double Ratchet, die kein Client instanziiertGeplant
Wiederherstellung nach einer KompromittierungErfordert die DH-Ratchet, die kein Client instanziiertGeplant
Unabhängiges AuditEs wird kein abgeschlossenes kryptografisches Audit durch Dritte behauptetNicht abgeschlossen
Post-Quanten-GarantieEs wird keine pauschale Post-Quanten-Sicherheitsgarantie behauptetNicht behauptet

Woher diese Seite stammt

Jeder Status oben wurde aus dem Client-Quellcode abgelesen und nicht aus einem Entwurfsdokument. Das Client-Repository ist derzeit nicht öffentlich, wir können Sie also nicht bitten, es selbst zu prüfen — was ein Grund ist, diese Seite als Offenlegung zu werten und nicht als Beweis. Fragen zu einer bestimmten Aussage sind willkommen unter security@railgun.chat.