Warum ein URL-Fragment hinter der Raute ein passender Ort für einen Schlüssel ist – und wann dieser Schutz wegfällt

Fragezeichen und Raute stehen in der Adresszeile nur ein Zeichen auseinander. Die Query hinter ? gehört zur HTTP-Anfragezeile; Server, Reverse-Proxy und Zugriffsprotokoll können sie lesen. Das Fragment hinter # bleibt nach der Spezifikation im aktuellen Tab und fährt mit diesem Dokumentabruf nicht mit. Ein Entschlüsselungsschlüssel hinter der Raute hält den Host vom Schlüssel fern. Er hält niemanden davon ab, den ganzen Link weiterzuleiten.

Merken Sie sich zuerst eine Prüfung, die Sie in einem Tab abschließen können. Öffnen Sie DevTools Network und legen Sie die volle Adresszeile neben die Anfragezeile des Dokumentabrufs. In der Adresszeile darf der Schlüssel hinter # stehen. In der Anfragezeile darf dieselbe Zeichenkette nicht vorkommen. Tut sie es doch, ist das kein Standardverhalten des Browsers: Die Seite hat das Fragment in eine Query geschrieben, oder ein Skript hat es gelesen und in eine Anfrage gesteckt.

Fragezeichen und Raute sind nicht dieselbe Schutzschicht

Einmalige Chiffretext-Links werden oft mit dem Satz erklärt: „Der Schlüssel steht in der URL.“ Der Satz ist zu grob. Er legt zwei verschiedene Tatsachen übereinander. Eine URL kann Query und Fragment gleichzeitig tragen: ? öffnet die Query, # öffnet das Fragment. Beide stehen in der Adresszeile, und wer kopiert, nimmt meist die ganze Zeichenkette mit. HTTP behandelt die beiden Teile nicht gleich.

Eine Query wird Teil des Anfrageziels. Öffnen Sie s.html?id=abc123, sehen Server, Reverse-Proxy, CDN und Access-Log absichtlich id=abc123. Ein Fragment gehört nicht zu diesem Dokumentabruf. Öffnen Sie s.html?id=abc123#der-schlüssel, fordert der Browser die Seite mit der id an. Alles hinter der Raute bleibt im aktuellen Tab, damit Seitenskripte es über location.hash lesen können.

„Der Schlüssel steht in der URL“ beantwortet also nur, ob ein Mensch die ganze Zeichenkette sehen kann. Es beantwortet nicht, ob der Server den Schlüssel sehen kann. Der passende Ort für den Schlüssel ist der Teil hinter der Raute – sofern die Umsetzung ihn wirklich dort lässt und nicht der Einfachheit halber in die Query schreibt.

Was die Spezifikationen sagen: Das Fragment bleibt beim Client

Das ist keine private Produktregel. RFC 3986, Abschnitt 3.5 definiert das Fragment als Kennung einer „sekundären Ressource“: Nach der Raute folgt der Rest der URI. Wie das Fragment gelesen wird, hängt vom geholten Dokumenttyp ab. Der Client wertet es aus. Das URI-Schema schreibt es nicht um.

RFC 9110, Abschnitt 7.1 hängt dieselbe Regel an HTTP: Die Ziel-URI, die der Browser ableitet, enthält kein Fragment, weil Fragmentbezeichner für den Client gedacht sind. Beim Dokumentabruf dürfen Raute und alles danach nicht in der Anfragezeile stehen.

Referer folgt demselben Schnitt. RFC 9110 verlangt: Ein User-Agent, der Referer erzeugt, darf weder Fragment noch Userinfo mitschicken. Die MDN-Seite zu Referer sagt dasselbe: Der Header darf Ursprung, Pfad und Query tragen, kein #-Fragment. Die W3C Referrer Policy leert das Fragment zuerst, wenn sie aus einer URL einen Referrer macht, und entscheidet erst danach, ob Pfad und Query bleiben.

Diese drei Texte belegen eine Sache: Ein spezifikationskonformer Browser schreibt den Schlüssel hinter der Raute weder in die HTTP-Anfragezeile noch in Referer. Sie belegen nicht, dass Seitenskripte, Erweiterungen oder der Text, den Sie woanders einfügen, denselben Teil von selbst streichen.

Steht der Schlüssel in der Query, steht er im Klartext im Log

Den Schlüssel als ?id=abc123&key=... zu schreiben, ist einfacher zu bauen. Der Server kann mit derselben Query Chiffretext holen und gleich entschlüsseln. Auf „online verschlüsseln“-Seiten, die eine Anfrage für alles wollen, ist das üblich. Gegenüber „der Host darf den Schlüssel nie sehen“ ist es das Gegenteil.

Eine Query erscheint in der Anfragezeile. Sie landet außerdem in den meisten Access-Logs, Reverse-Proxy-Logs und manchen CDN-Berichten. Sitzt der Schlüssel in der Query, ist „wir speichern nur Chiffretext“ auf der Protokollebene bereits falsch. Wer das Access-Log des Tages öffnet, hat die zweite Hälfte, die zum Entschlüsseln fehlt.

Manche schlagen vor, den Schlüssel stattdessen im POST-Body zu schicken. Das kann ihn aus den URL-Logs halten. Der Schlüssel hat den Browser trotzdem verlassen und ist auf der Maschine angekommen, die Sie „Zero-Knowledge“ genannt haben. Der Wert des Fragments liegt genau andersherum: Der Schlüssel muss gar kein HTTP-Feld werden. Die Seite liest location.hash in diesem Tab.

Wohin legen Sie ihn Kann ein Mensch ihn sehen? Kann dieser HTTP-Abruf ihn sehen?
Query hinter ? Ja, in der Adresszeile und im kopierten Text Ja. Anfragezeile, Proxys und Access-Logs zeichnen ihn auf
Fragment hinter # Ja, in der Adresszeile und im kopierten Text Standardmäßig nein. Anfragezeile und Referer lassen ihn laut Spezifikation weg
POST-Feld Nicht in der Adresszeile Ja. Der Request-Body erreicht den Server
Nur im Speicher, nie in einer URL Die Gegenseite sieht ihn nicht, außer Sie öffnen einen zweiten Kanal Nein. Ein Einmal-Link kann die Übergabe dann aber nicht mit einem Öffnen erledigen

Die Trennung, die prüfbar ist: Locator in der Query, Schlüssel hinter der Raute

Ein einmaliger Chiffretext-Link muss zwei Dinge gleichzeitig tun: dem Server sagen, welchen Chiffretext er holen soll, und dem Browser der Empfängerin oder des Empfängers sagen, mit welchem Schlüssel lokal entschlüsselt wird. Diese beiden Aufgaben gehören nicht ins selbe HTTP-Feld.

Die prüfbare Trennung lautet s.html?id={id}#{key}. Die Query trägt nur die Locator-id. Die Raute trägt nur den Schlüssel. Beim Erstellen rechnet der Browser AES-256-GCM über Web Crypto und darf nur Chiffretext senden. Beim Lesen liest das Skript location.hash, holt den Chiffretext vom Server und entschlüsselt in diesem Tab. Der Server speichert den Chiffretext kurz und zerstört ihn nach dem Lesen.

MakePwd Einmal-Link folgt genau dieser Grenze. Erstellungsseite und Leseseite öffnen sich ohne Konto. Die Leseseite ist für Empfängerinnen und Empfänger öffentlich und verlangt keine Anmeldung. Das bleibt eine Umsetzungswahl, kein Satz der Form „eine Raute ist ein Verschlüsselungsprotokoll“. Ein Fragment hält ein Geheimnis nicht geheim. Es verhindert nur, dass der Schlüssel den Tab als HTTP-Feld verlässt.

Testen Sie nicht mit echten Passphrasen, Ausweisnummern oder ungeschwärzten Tabellen. Nutzen Sie einen wegwerfbaren Satz und einen Einmal-Link, den Sie nur für diese Prüfung erzeugt haben. Sie vergleichen Anfragezeile und Adresszeile – Sie verbreiten kein zweites Geheimnis.

Sofort prüfen: Network gegen die Adresszeile

Die vorige Notiz zeigte, wie Sie mit einem Canary prüfen, ob Klartext als Geschäftsdaten das Gerät verlassen hat. Diese Notiz verengt die Frage: Ist der Schlüssel in HTTP gelandet? Die Schritte stehen für sich. Sie müssen den anderen Artikel nicht zuerst lesen.

Legen Sie einen harmlosen Testsatz an und kopieren Sie den erzeugten ganzen Link. Finden Sie die Raute: davor steht s.html?id=..., dahinter der Schlüssel. Öffnen Sie einen sauberen Tab, öffnen Sie DevTools, aktivieren Sie Preserve log und fügen Sie den Link ein.

  1. Legen Sie Adresszeile und Anfragezeile des Dokumentabrufs nebeneinander. Die Adresszeile soll # und den Rest behalten. Die Dokument-URL soll nur Pfad und ?id= zeigen – nicht den Schlüssel hinter der Raute.
  2. Prüfen Sie danach die späteren XHR-/Fetch-Aufrufe. Die Chiffretext-API darf die id tragen. Derselbe Schlüssel darf nicht noch einmal in Body oder Query stehen. Der Create-Body soll Chiffretext sein, nicht der Satz, den Sie gerade getippt haben.
  3. Öffnen Sie Analytics-Queries und -Bodies getrennt. Ein Seitenpfad darf vorkommen. Schreibt ein Skript die volle location.href, wandert der Schlüssel von „HTTP hat ihn standardmäßig nicht mitgenommen“ zu „die Seite hat ihn gemeldet“. Das ist ein Umsetzungsfehler, kein Versagen der Spezifikation.

Drei saubere Durchläufe stützen nur eine enge Aussage: In diesem Browser, bei diesem Öffnen, hat der Schlüssel den Tab nicht als beobachtetes HTTP-Feld verlassen. Wechseln Browser, Version oder Seitenskript, müssen Sie die Prüfung wiederholen.

Wann diese Schutzschicht wegfällt

Ein Fragment blockiert den Satz „dieser HTTP-Abruf hat dem Host den Schlüssel übergeben“. In den Fällen darunter hat der Schlüssel nie auf dem Standardverhalten von HTTP beruht. Die Spezifikation kann dort nicht helfen.

Erstens: Der ganze Link landet in Chat, E-Mail oder Ticket. Die Gegenseite sieht die Zeichenkette mit bloßem Auge, und der Teil hinter der Raute wird in der Historie dieses Systems gespeichert. Manche Clients werfen den Hash weg und zeigen nur, was vor der Raute steht – dann öffnet die Empfängerin oder der Empfänger eine Seite ohne Schlüssel, und die Leseseite soll das sagen, statt den Server um einen zweiten Schlüssel zu bitten. In beiden Fällen haben Sie einen Inhabernachweis an ein anderes System gegeben.

Zweitens: Seitenskripte und Erweiterungen können location.hash lesen. Genau deshalb funktioniert eine Leseseite. Genau deshalb kann XSS oder eine feindliche Erweiterung den Schlüssel mitnehmen. RFC 9110, Abschnitt 17.11, hält fest: Fragmente gehen nicht in die Anfrage, bleiben aber für User-Agent, Erweiterungen und Skripte sichtbar, die mit der Antwort kamen. Erbt eine Weiterleitung das Fragment der Ausgangs-URL, kann sie das Fragment dieser Site auch auf einen anderen Ursprung tragen.

Drittens: Browser-Historie, Bildschirmfreigabe und Zwischenablage. Die volle Adresszeile landet in der lokalen Historie. Werfen Sie den Tab an die Wand im Besprechungsraum, steht der Teil hinter der Raute auf der Leinwand. Nichts davon läuft über Ihren Server, und „Fragmente gehen nicht über HTTP“ widerlegt das nicht.

Viertens: Weiterleitungsseiten oder Kurz-URL-Dienste, die Adressen umschreiben. Leitet die Zwischenseite nur Pfad und Query weiter, ist der Hash schon weg, wenn die Gegenseite öffnet. Liest sie zuerst die volle href in JavaScript und springt dann, ist der Schlüssel in deren Frontend gelandet. Gerade Kurzlinks brauchen eine Live-Prüfung: Ist die Zeichenkette, die Sie übergeben, noch die Originalkette mit #?

Drei Sätze, die die Sache umdrehen

„Hinter der Raute ist es sicherer, also darf die ganze Zeichenkette frei weitergeleitet werden.“ Nein. Sicherer gegenüber dem Server heißt nicht sicherer gegenüber einem Gruppenchat-Log. Die volle URL ist ein Nachweis. Wer sie hat, kann die Leseseite öffnen und entschlüsseln.

„Referer könnte den Schlüssel an einen ausgehenden Link leaken.“ Nach geltender Spezifikation muss ein Browser, der Referer erzeugt, das Fragment streichen. Worauf Sie wirklich achten: ob die Seite location.href in Analytics, Logs oder ein Dritt-Skript schreibt. Die Prüfung bleibt der Network-Payload, nicht die Annahme „jeder ausgehende Link leakt den Schlüssel“.

„Die Leseseite sollte eine Anmeldung verlangen, sonst kann sie jede Person öffnen.“ Das vermischt „wer die ganze Zeichenkette hält“ mit „wer ein Konto hat“. Die Zugriffskontrolle eines einmaligen Chiffretext-Links ist die Zeichenkette selbst. Ein Login-Tor für Empfängerinnen und Empfänger hält den Schlüssel nicht vom Server fern – der Schlüssel darf den Server nie erreichen. Die MakePwd-Leseseite ist für Empfängerinnen und Empfänger öffentlich. Erstellen und Lesen brauchen kein Konto.

Häufige Fragen

Geht der Teil hinter der Raute an den Server?

Standardmäßig nicht. RFC 9110 schließt das Fragment aus der Ziel-URI aus. In Network darf die Anfragezeile des Dokumentabrufs weder # noch den Schlüssel danach zeigen. Der Server sieht absichtlich nur Pfad und ?id=.

Warum gehört der Schlüssel nicht hinter das Fragezeichen?

Eine Query landet in der HTTP-Anfragezeile. Host und Access-Logs können sie lesen. Sitzt der Schlüssel in der Query, ist „nur Chiffretext“ auf der Protokollebene bereits falsch. Eine Locator-id darf hinter ? stehen. Der Schlüssel gehört hinter #.

Ist der ganze Link im Chat noch sicher?

Gegenüber dem Server bleibt der Schlüssel unsichtbar. Chatverlauf, Ticket und Browser-Historie sehen ihn. Die volle URL ist ein Inhabernachweis. Wenn Sie sie übergeben müssen, prüfen Sie, dass die Gegenseite die Originalkette mit Raute erhalten hat, und behandeln Sie sie als Eintrag in deren Historie.

Muss ich mich anmelden, um die Leseseite zu öffnen?

Nein. Die Leseseite ist für Empfängerinnen und Empfänger öffentlich: Die Query-id holt den Chiffretext, der Schlüssel hinter der Raute entschlüsselt ihn in diesem Tab. MakePwd hat weder Konten noch einen Passworttresor. Erstellen und Lesen sind sofort nutzbar.

Drei Dinge, bevor Sie das nächste Einmal-Geheimnis schicken

Erstens: Auf das Zeichen achten, nicht auf den Slogan „der Schlüssel steht in der URL“. Queries gehen über HTTP. Rauten tun das in der Regel nicht. Zweitens: Beim Erstellen prüfen, dass ausgehende Daten Chiffretext sind; beim Lesen prüfen, dass die Anfragezeile den Teil hinter # nicht enthält. Drittens: Bevor Sie den ganzen Link übergeben, fragen, ob er in einem Chatverlauf, einem Ticket oder einem Kurzlink landet, der den Hash verwirft. Die letzten beiden Fehler haben nichts damit zu tun, ob der Server Zero-Knowledge ist.

Wenn Sie außerdem prüfen wollen, ob Klartext als Geschäftsdaten den Tab verlassen hat, gehört das in die vorige Notiz: Network-Bodies und Analytics mit einem Canary durchsuchen. Dieser Text macht aus „warum ein Schlüssel hinter der Raute stehen darf“ ein Urteil, das Sie an Spezifikation und Anfragezeile legen können.