Zero-Knowledge-Kurztext: Inhalte Ende-zu-Ende schützen

6 Min Lesezeit Serie: product #zero-knowledge#notes#krypto#privacy

Ein Dienst, der seinen eigenen Inhalt nicht entschlüsseln kann, hat eine Eigenschaft, die die meisten "Ende-zu-Ende-verschlüsselten" Anbieter nicht haben. Warum das URL-Fragment das entscheidende Detail ist und wie One-View-Garantien sauber umgesetzt werden.

Problem

"Ende-zu-Ende-verschlüsselt" ist als Label im Markt massiv überdehnt. In vielen Produkten bedeutet es: der Schlüssel existiert kurzzeitig beim Anbieter, wird nach Nutzung verworfen, und der Anbieter verspricht, ihn nicht zu kopieren. Das ist ein Vertrauensversprechen und dabei weder struktureller noch nachprüfbarer Schutz. Wer personenbezogene oder anderweitige sensible Daten (z.B. Passwörter) verarbeitet, kann dem Anbieter nicht grundsätzlich trauen, nicht weil er bösartig ist, sondern weil Anordnungen, Insider-Zugriffe und erfolgreiche Angriffe existieren.

Zero-Knowledge ist hier die richtige Lösung: der Server kann den Inhalt nicht entschlüsseln, weil er den Schlüssel nie gesehen hat, auch nicht kurzzeitig. Das ist wesentlich stärker als Ende-zu-Ende-Verschlüsselung. Für Kurzmitteilungen mit hoher Vertraulichkeit (einmalig zu übertragende Passwörter, Zugangsdaten, Adressen, kurze Nachrichten) ist Zero-Knowledge der strukturell sauberste Schutz und zugleich eine Garantie: die tatsächlich übertragenen Daten können transparent in den DevTools , der Verschlüsselungscode im Javascript, nachvollzogen werden.

Kurze Antwort

Der Entschlüsselungs-Schlüssel entsteht im Browser des Senders als 32-Byte-Zufallswert, wird per crypto.getRandomValues() generiert, verschlüsselt den Inhalt mit AES-256-GCM und landet im URL-Fragment (nach dem #). Der Browser schickt das Fragment nie an den Server. Der Server sieht nur den Ciphertext, den Nonce und das Salt. Das ist sinnvoll und sicher.

Der Empfänger klickt den Link. Sein Browser hält das Fragment lokal, fragt nur die Ciphertext-Zeile beim Server ab, entschlüsselt im Browser und zeigt den Klartext an. Für One-View-Notes wird die Zeile serverseitig beim ersten erfolgreichen Abruf unmittelbar gelöscht. Danach existiert weder Klartext noch Chiffrat mehr. Wenn man den Browsertab schließt (oder wieder abstürzt) dann ist die Notiz unwiederbringlich verloren und muss erneut verschickt werden.

Tiefgang

Das URL-Fragment als lokales Geheimnis

RFC 3986 definiert den Fragment-Bezeichner (der Teil nach #) explizit als clientseitig auszuwertend. Browser schicken das Fragment nicht im HTTP-Request an den Server, nicht im Referer-Header, nicht in Logs. Es ist die einzige Stelle in einer URL, die dem Server strukturell unzugänglich bleibt. Einfach nachzuprüfen in den DevTools Ihres Browsers.

Das macht das Fragment zum idealen Transportmittel für ein Geheimnis, das zeitgleich sehr benutzerfreundlich ist. Der Absender erzeugt den Schlüssel, verschlüsselt, speichert nur den chiffrierten Text auf dem Server. Dann baut er eine URL, die den Schlüssel ins Fragment hängt. Der Link kann in Messenger, E-Mail, Chat weitergereicht werden, jeder, der ihn vollständig erhält, kann die Notiz entschlüssel - einmalig oder auch mehrmalig, optional um eine zweite, unabhängige, Verschlüsselung erweitert. Dann müssen beide Teile vorliegen um die Notiz entschlüsseln zu können.

Schlüsselerzeugung und Schema

Dernium macht die eingesetzte Verschlüsselung bewusst offen und nachprüfbar. Wir sind hier transparent und schreiben keine bloßen Marketing-Zeilen: Alles kann tatsächlich nachvollzogen und verifiziert werden.

Im Browser, mit Web Crypto API:

const key = crypto.getRandomValues(new Uint8Array(32)); // 256 bit
const nonce = crypto.getRandomValues(new Uint8Array(12)); // 96 bit GCM
const subtleKey = await crypto.subtle.importKey(
 'raw', key, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']
);
const ciphertext = await crypto.subtle.encrypt(
 { name: 'AES-GCM', iv: nonce }, subtleKey, plaintextBytes
);
// Server bekommt: ciphertext || nonce || (optional) salt
// URL wird: https://notes.example.de/n/<id>#k=<base64url(key)>

Der Schlüssel geht ins Fragment und nicht an den Server. GCM liefert Authentizität mit; eine Manipulation am Ciphertext führt zum vollständigen Versagen beim Entschlüsseln - der gesamte Text wird unlesbar. Aufgrund von adaptivem Padding ist noch nicht einmal die konkrete Textlänge der verschlüsselten Notiz erkennbar.

Passwort als zweite Schicht

Für Fälle, in denen der Link selbst in falsche Hände geraten könnte (unsichere Kanäle wie Messenger, SMS, durchsichtiger Proxy), lässt sich ein zweiter Schlüssel aus einem vom Ersteller festlegbaren Passworts ableiten:

const salt = crypto.getRandomValues(new Uint8Array(16));
const passKey = await scrypt(password, salt, N=2**15, r=8, p=1, dkLen=32);
const finalKey = xor(key, passKey);

Der Server speichert das Salt. Zum Entschlüsseln muss der Empfänger nun zusätzlich zum vollen Link das eingegebene Passwort kennen. Die Brute-Force-Kosten hängen am scrypt-Parameter N; auf modernen Clients dauert eine Iteration einige zehntel Sekunden, was für einen einzelnen Zugriff in Ordnung ist und für Brute-Force massiv teuer. Sie werden in dem Fall merken, dass die Entschlüsselung einer Notiz nicht unmittelbar erfolgt. Das ist keine langsame Serververbindung sondern Arbeit, die auf Ihrem lokalen Rechner verrichtet wird. Auf mobilen Endgeräten kann es tatsächlich auch ein paar Sekunden dauern.

Die Lösch-Garantie

Zero-Knowledge schützt den Inhalt; bei der Einmalkeits-Garantie ist die verlässliche Löschung aber ebenso wichtig. Derniums Umsetzung:

  • One-View-Notes: atomarer DB-Delete ... WHERE id = $1 AND consumed_at IS NULL RETURNING *. Nur eine parallele Leseanfrage gewinnt; alle anderen bekommen NotFound.
  • Zeitfenster-Notes: expires_at-Spalte, Cleanup-Job löscht abgelaufene Zeilen. Bis dahin Zugriff möglich, danach physisch gelöscht.
  • Keine Backups der Notes-Tabelle. Das ist wichtig und unbequem: ein Restore würde eine vermeintlich gelöschte Notiz wieder zugänglich machen. Die Tabelle wird explizit aus dem Backup-Scope herausgenommen. Schade, aber sinnvoll.
  • Kein Support-Override. Es gibt keine Möglichkeit, einen verlorenen Schlüssel wiederherzustellen. Das ist Produktmerkmal und bewusst per Design, kein Defekt und auch wir haben keine Möglichkeit das zu umgehen. Dadurch können wir auch nicht z.B. gezwungen werden Inhalte von Notizen zu verraten: wir können beweisen (und das kann auch unabhängig von Extern verifiziert werden), dass weder Inhalte noch Textlängen jemals selbst lesen können. Damit können wir sie auch niemandem zur Verfügung stellen.

Abgelehnte Alternativen und Mythen

"Wir speichern den Schlüssel kurz am Server und werfen ihn danach weg -> "Ende-zu-Ende-Verschlüsselung" als Schutzversprechen" Der Schlüssel war am Server. Eine Speicher-Forensik oder ein Insider mit Zugriff auf den Prozess können den kurzlebigen Schlüssel abgreifen. Zero-Knowledge schliesst das strukturell aus da das Geheimnis niemals an einem Ort liegt von wo er abgegriffen werden könnte.

"Wir nutzen PGP." Funktional eine sehr starke Lösung, aber operativ komplex: jeder Empfänger braucht einen Schlüssel, Key-Management ist nicht gelöst, Messenger und Webmail-Clients unterstützen PGP unterschiedlich. Zero-Knowledge-Kurztext-Dienste sind deutlich niederschwelligere Alternativen und quanten-sicher (durch AES und sichere einmalig symmetrische Schlüssel).

"Ein S3-Bucket mit Client-seitigem Krypto." Funktioniert, aber die Lösch-Garantie muss separat gebaut werden. One-View-Semantik im Client durchzusetzen ist fragil (der Client kann mehrfach aufrufen); zuverlässig ist sie nur serverseitig wo sie ohne Einfluss des Endnutzers erzwungen werden kann.

"Das Fragment lässt sich per JavaScript abfangen." Nur, wenn auf der Seite selbst feindlicher JavaScript-Code läuft. Deshalb gehören zu Zero-Knowledge-Diensten eine scharfe CSP ohne inline-Scripts (siehe kommender Beitrag dieser Serie) und ein minimal gehaltener Bundle. Kein Drittanbieter-JavaScript, kein CDN, kein Analytics.

Wie Dernium hier hilft

Dernium Note implementiert den im Beitrag beschriebenen Flow direkt: Schlüssel-Erzeugung im Browser mit crypto.getRandomValues(), AES-256-GCM-Verschlüsselung, Schlüssel-Transport im URL-Fragment, atomarer One-View-Delete, kein Support-Override. Optional scrypt-basiertes Passwort-Hardening als zweite Schicht on-top, konfigurierbare TTL für Zeitfenster-Notes, keine Backups der Notes-Tabelle.

Verifikation

  • RFC 3986 (URI-Syntax), Abschnitt 3.5 zum Fragment.
  • Web Crypto API: MDN-Dokumentation.
  • Offene Vergleichs-Implementationen: PrivateBin, OneTimeSecret, Bitwarden Send. Jeder Code ist einsehbar, jeder Prüfer kann nachvollziehen, wo der Schlüssel entsteht und wohin er geht.
  • Tests: HTTP-Logs auf dem Server auf Suche nach dem Fragment-Wert, er darf nie auftauchen. DevTools-Netzwerk-Tab beim Abruf: nur die id, nicht der Schlüssel im Request.

Offene Punkte

Browser-Verlauf. Das Fragment steht in der URL des Browser-Verlaufs. Wer Einmal-Notizen verwendet, sollte das dem Empfänger bewusst machen, der Verlauf ist eine Kopie des Schlüssels für die Dauer der Browser-Historie. Informationen lassen sich hieraus keine mehr ziehen - außer, dass Dernium Note verwendet wurde. Bei mehrfach (zeitlich begrenzt) abrufbaren Notizen ist hierüber ein Zugriff möglich. Optimal schützt man sich hier mit einem Passwort als zweite Verteidigungslinie.

Mail-Preview. Manche Mail-Dienste (Outlook, ...) holen URLs im Link vor, um Previews zu bauen. Wenn der Preview-Crawler den Link öffnet, wird eine One-View-Note vor dem eigentlichen Empfänger konsumiert. Das ist ein reales Problem und lässt sich nur abmildern (nicht heilen): Dernium implementiert hier einem Klick-Warte-Schritt, bei dem der Empfänger aktiv bestätigt, bevor die Note freigegeben wird.