Security9 Min Lesezeit

E2EE im Chat: Double-Ratchet ohne Lehrbuch

Wie wir das Signal-Protokoll für unseren Team-Chat adaptiert haben — inklusive X3DH-Setup, SignedPreKey-Rotation und einem ehrlichen Vergleich von Signal- und Wire-Architektur.

Sebastian ·

End-to-End-Encryption im Team-Chat war eines der Features, die wir nicht hätten bauen müssen — die meisten Konkurrenten haben es nicht — aber wenn du DSGVO-Customers servicest, ist es eine harte Anforderung. Dieser Artikel beschreibt, wie wir das Signal-Protokoll für unseren Use-Case adaptiert haben.

Warum Signal-Protokoll, nicht TLS-allein

TLS schützt die Verbindung zwischen Client und Server. Das ist gut gegen Netzwerk-Angreifer, aber nicht gegen den Server selbst. Wenn ein User unserer Plattform vertraut, vertraut er implizit, dass wir nicht in der Lage sind, seine Nachrichten zu lesen — und ohne E2EE wäre das schlicht falsch, weil wir die Nachrichten verarbeiten müssen, um sie zuzustellen.

Mit echtem End-to-End-Encryption sieht der Server nur Ciphertext. Selbst wenn ein Angreifer Root-Zugriff auf unsere Box bekommt, kriegt er keine Klartext-Nachrichten zu sehen. Das ist eine fundamental andere Sicherheitseigenschaft, und sie ist nicht-verhandelbar für unseren Markt.

Das Signal-Protokoll ist der Goldstandard. Es kombiniert vier kryptographische Bausteine: X3DH für den initial Key-Agreement, Double-Ratchet für die fortlaufende Sitzungsverschlüsselung, asymmetrische Identity-Keys (Ed25519) und ephemere Diffie-Hellman-Schlüssel (X25519). Diese Kombination liefert Forward-Secrecy (kompromittierte Sitzungs-Keys legen keine vergangenen Nachrichten offen) und Post-Compromise-Recovery (auch nach einem Kompromiss kann die Sitzung sich selbst heilen).

X3DH in Praxis

X3DH steht für Extended-Triple-Diffie-Hellman. Wenn Alice mit Bob das erste Mal chatten will, holt sich Alice von unserem Server drei öffentliche Keys, die Bob vorher upgeloadet hat: seinen Identity-Key, einen SignedPreKey und einen OneTimePreKey. Alice generiert dann einen ephemeren Schlüssel und führt vier Diffie-Hellman-Operationen aus. Die vier Outputs werden via HKDF zu einem Shared-Secret zusammengeführt.

In unserer Implementierung muss jeder Client beim Onboarding einen Pool von OneTimePreKeys uploaden — typischerweise 100 auf einmal. Der Server reicht bei jedem Conversation-Start einen heraus und löscht ihn dann. Wenn der Pool unter 50 fällt, lädt der Client automatisch neue nach. Wir tracken die Pool-Größe pro User auf dem Server, weil ohne OneTimePreKeys X3DH auf einen schwächeren Modus fällt.

SignedPreKey-Rotation

Der SignedPreKey ist der mittelfristige Schlüssel. Er ist mit dem Identity-Key signiert (deswegen "Signed") und bleibt für einen definierten Zeitraum gültig. Signal rotiert ihn alle paar Wochen. Wir rotieren alle sieben Tage. Das ist ein Trade-off: Häufigere Rotation reduziert die Exposure-Fenster eines kompromittierten SignedPreKey, aber sie erfordert, dass Clients regelmäßig online sind, um neue Keys hochzuladen.

Die Rotation selbst ist eigentlich simpel: Der Client generiert einen neuen SignedPreKey, signiert ihn mit dem Identity-Key, lädt ihn hoch, und hält den alten Key noch sieben Tage vor, damit Out-of-Order-Messages, die mit dem alten Key initiiert wurden, noch entschlüsselt werden können. Nach sieben Tagen wird der alte Key gelöscht.

Double-Ratchet

Sobald X3DH die initiale Sitzung etabliert hat, übernimmt der Double-Ratchet. Bei jeder gesendeten Nachricht wird ein neuer Diffie-Hellman-Output berechnet und mit dem vorherigen Chain-Key gehasht, was einen neuen Message-Key produziert. Der Empfänger macht denselben Prozess. Das hat zur Folge, dass jede einzelne Nachricht mit einem eindeutigen Symmetric-Key verschlüsselt wird, der nirgendwo dauerhaft gespeichert wird.

Die "Double" im Namen bezieht sich darauf, dass es zwei verschachtelte Ratchets gibt: Den symmetrischen Ratchet (rotiert pro Message) und den asymmetrischen Diffie-Hellman-Ratchet (rotiert, sobald die Gegenseite eine Antwort schickt). Diese Kombination ist es, die das Protokoll so robust gegen Compromise macht.

Vergleich Signal vs Wire

Wire hat einen anderen Ansatz für Group-Chats gewählt, weil sie Multi-Device-First sind. Statt eines Group-Keys mit Sender-Keys (Signal's Ansatz) nutzt Wire eine Pairwise-Modellierung, in der jede Group eine Sammlung von Pairwise-Sitzungen ist. Das ist konzeptuell sauberer, aber die Message-Größe wächst mit der Group-Größe (du verschlüsselst die Nachricht N-mal). Signal hat das gleiche Modell mittlerweile umgesetzt mit MLS, aber wir haben uns gegen MLS entschieden, weil die Spec zum Zeitpunkt unserer Implementation noch nicht stabil genug war.

Unser Hybrid-Modell: Für 1:1-Chats reines Signal-Protokoll. Für Group-Chats unter 16 Teilnehmern Pairwise-Sessions (Wire-Style). Für Group-Chats über 16 Teilnehmer ein Sender-Key-Modell mit periodischer Sender-Key-Rotation. Das ist nicht so sauber wie ein einheitlicher Ansatz, aber es ist praktisch.

Performance auf Mobile

Double-Ratchet ist nicht gratis. Jede Nachricht braucht eine Curve25519-Operation, eine HKDF-Derivation, eine AES-256-GCM-Verschlüsselung. Auf einem Desktop-CPU sind das Mikrosekunden. Auf einem alten Mobile-Device können das mehrere Millisekunden pro Nachricht sein. Wir haben das gemessen: Auf einem iPhone 11 dauert ein Send-Receive-Cycle etwa 3-4ms. Auf einem Android-Mid-Range-Device von 2022 etwa 8-12ms.

Das klingt nach wenig, aber wenn du einen Chat-Channel mit 200 unread Messages aufmachst und alle deserialisieren musst, sind das 2-3 Sekunden Stalling. Wir cachen entschlüsselte Plaintext-Messages lokal, damit das nur beim allerersten Decrypt passiert. Aber das ist genau der Trade-off, den E2EE einkauft: Sie hast vollständige Sicherheit, aber jede Optimierung kostet entweder UX oder Sicherheit.

Was ich heute anders machen würde

Wenn ich morgen nochmal anfangen würde, würde ich MLS evaluieren, jetzt da die RFC stable ist. Das einheitliche Protokoll für 1:1 und Group ist konzeptuell viel sauberer als unser Hybrid. Aber das ist nicht-trivial zu migrieren — Sessions müssten neu etabliert werden, History würde geclamped. Solange unsere bestehende Implementation funktioniert und auditiert ist, bleiben wir dabei. Aber für Greenfield-Projekte ab 2026 würde ich MLS ernsthaft prüfen.