← All posts

Die drei Leben meines JWT-Signaturschlüssels

Authagonal·July 17, 2026
authjwtkey-managementkmsvaultecdsaoidc

Ein einziger privater Schlüssel signiert jedes Token, das unser Auth-Server ausstellt. Geht er verloren, prägt ein Angreifer für jeden beliebigen Nutzer ein gültiges Token – ohne Passwort –, und in Ihren Logs erscheint ein sauberer Login. Er ist das mit Abstand wertvollste Geheimnis im System, und in seinem Leben ist er zweimal umgezogen: Geboren wurde er im Anwendungsprozess selbst, dann wurde er in einen Tresor verbannt, den er nicht mehr verlassen kann, und schließlich als eine völlig andere Art von Schlüssel wiedergeboren. Jeder Umzug fand bei einem laufenden Issuer statt, während bereits Tokens unterwegs waren und Validatoren Zustände zwischenspeicherten, die wir nicht kontrollieren. Hier steht, was solche Umzüge wirklich verlangen – und am meisten gelehrt hat uns derjenige, den wir nicht angekündigt haben.

Erstes Leben: der Schlüssel, den der Server in der Tasche behielt

Am Anfang lebte der Schlüssel dort, wo er auch benutzt wurde. Der Server erzeugte einen RSA-2048-Schlüssel, hielt ihn im Arbeitsspeicher und signierte seine Tokens mit RS256. Das ist fast überall die Voreinstellung, und es geht auch gut – bis zu dem Moment, in dem man das Wort Verwahrung laut ausspricht. Der private Schlüssel war für alles erreichbar, was den Speicher des Prozesses, seine Konfiguration oder ein Backup von beidem lesen konnte. Wir wollten unseren Kunden sagen können, dass eine geleakte Datenbank nichts Brauchbares preisgibt – und ein Signaturschlüssel, der nur einen Prozess von dieser Datenbank entfernt lag, machte diese Aussage zur Lüge. Der Schlüssel musste das Gebäude verlassen.

Zweites Leben: der Schlüssel, der in einen Tresor zog, den er nicht verlassen kann

Wir haben eine Nahtstelle eingezogen – einen ISignatureProvider, den der Server immer dann aufruft, wenn er eine Signatur braucht – und ein KMS dahintergesetzt. Der Schlüssel wird nun innerhalb der Transit-Engine von Vault erzeugt und niemals exportiert. Die Anwendung besitzt ihn nicht; sie schickt die zu signierenden Bytes an den Tresor und bekommt eine Signatur zurück. Sie kann den Schlüssel benutzen, aber sie kann ihn nicht herausschleusen.

Genau auf diesen Unterschied kommt es an. Ein Speicherabzug, eine verschüttete Konfigurationsdatei, ein geleaktes Backup – keines davon enthält den Schlüssel noch, denn der Schlüssel war an keinem dieser Orte jemals vorhanden. Wer die Anwendung kompromittiert, erkauft sich damit jetzt nur noch die Möglichkeit, den Tresor um eine Signatur zu bitten – und die können wir drosseln, protokollieren und widerrufen. Er erkauft sich nicht mehr den Schlüssel selbst, gegen dessen Verlust wir nichts hätten tun können, sobald er einmal weg war.

Und hier kommt der Teil, der nicht in den Release Notes stand. Derselbe Commit, der den Schlüssel in den Tresor verschob, halbierte klammheimlich auch unsere PBKDF2-Iterationszahl, von 100.000 auf 50.000. Ein einziger Diff, zwei Sicherheitsänderungen, die in entgegengesetzte Richtungen zeigten: Die Schlagzeile lautete „im KMS signieren“, und darunter fuhr ein halbierter Arbeitsfaktor für das Passwort-Hashing mit – in einer Zeile, die niemand ansah, weil es in der Geschichte um etwas anderes ging. Beides ist Sicherheitscode, also wurde beides als eine einzige „sicherere“ Änderung geprüft und auf die Stärke der guten Hälfte hin durchgewunken.

Der Fix war eine Zeile. Die Lehre war es nicht. Ein Diff mit dem Etikett sicherer ist immer noch ein Diff, und die sicherheitsrelevanten Konstanten darin – Iterationszahlen, Schlüsselgrößen, Timeouts, Algorithmusnamen – erben keinen Heiligenschein von der Commit-Nachricht. Wir behandeln eine Arbeitsfaktor-Zahl heute genauso wie die Wahl eines Algorithmus: als etwas, das man behauptet und überprüft – nicht als etwas, dem man vertraut, weil die umgebende Änderung eine gute Idee war.

Drittes Leben: der Schlüssel, der kleiner wurde, um schneller zu werden

Wenn das Signieren hinter einem KMS liegt, ist jede Signatur ein Netzwerk-Roundtrip, und das Signieren mit RSA gehört zur teuren Sorte. Also wechselte der Schlüssel die Spezies: Aus RS256 über RSA-2048 wurde ES256 über der Kurve P-256. Das Signieren mit elliptischen Kurven ist rund eine Größenordnung günstiger als RSA, eine P-256-Signatur ist 64 Bytes groß, wo die von RSA-2048 256 misst, und der öffentliche Schlüsselsatz, den Validatoren herunterladen, schrumpft entsprechend. Kleinerer Schlüssel, kleinere Signatur, kleineres JWKS, schnellere Tokens – alles nur, weil wir eine bessere Kurve gewählt haben.

Der Haken: Man kann den Signaturalgorithmus bei einem laufenden Issuer nicht einfach im Handumdrehen austauschen. Jedes bereits ausgestellte Token wurde mit RS256 signiert und muss weiter gültig bleiben, bis es abläuft. Jeder Validator hat Ihren alten öffentlichen Schlüssel zwischengespeichert. Ändern Sie den Algorithmus in einem einzigen Zug, machen Sie im selben Augenblick jedes unterwegs befindliche Token und jeden zwischengespeicherten Schlüsselsatz ungültig – ein selbst verschuldeter Ausfall, der als Upgrade verkleidet daherkommt.

Was funktioniert hat, war, nicht länger so zu tun, als hätte es je nur einen Schlüssel gegeben. Der Schlüsselspeicher wurde algorithmusagnostisch: Er hält den RSA-Schlüssel und den EC-Schlüssel gleichzeitig und veröffentlicht beide im JWKS, jeden unter seiner eigenen Key-ID und seinem eigenen Algorithmus. Ein separater Auswähler für den aktiven Schlüssel entscheidet, welcher Schlüssel neue Tokens signiert, und er wechselt in dem Moment vom alten RSA-Schlüssel weg, in dem ein EC-Schlüssel existiert – kein Konfigurationsschalter, kein Deploy, dessen Zeitpunkt auf eine Rotation abgestimmt sein müsste. Während der Überlappung gibt Discovery beide öffentlichen Schlüssel bekannt, sodass mit beiden signierte Tokens weiterhin gültig bleiben. Sobald das letzte RS256-Token abgelaufen ist, gibt Discovery nur noch ES256 bekannt, und die Validierung legt ValidAlgorithms = ES256 fest – was zugleich Algorithm-Confusion-Angriffen die Tür vor der Nase zuschlägt, die einem Validator das falsche Verfahren unterjubeln wollen. Die Migration ist eine Überblendung, die der Issuer an sich selbst vollzieht, kein Schalter, den irgendjemand umlegt.

Die Lehre, herausgezogen

Ein Signaturschlüssel sieht aus wie eine Konstante: ein Geheimnis, einmal gesetzt, für immer referenziert. Unserer war es nicht. In seinem Leben wechselte er die Verwahrung, vom Prozess zu einem KMS, und er wechselte die Spezies, von RSA zur elliptischen Kurve – und beide Wechsel mussten vollzogen werden, während Tokens unterwegs waren und Validatoren Dinge zwischenspeicherten, die uns nicht gehören. Die Eigenschaft, die jeden Wechsel überlebbar machte, war beide Male dieselbe: Das System nahm nie an, es gäbe genau einen Schlüssel, einen Algorithmus oder ein Zuhause. Bauen Sie den Schlüsselspeicher so, dass er mehrere Schlüssel hält und ehrlich bekannt gibt, welche er hält, dann hört die Rotation – ob des Orts, an dem der Schlüssel lebt, oder der Art des Schlüssels – auf, ein Ausfall zu sein, den man einplant, und wird zu einer Eigenschaft, die der Issuer von selbst verwaltet.

Wenn Sie Auth betreiben, sollte das, was Ihre Tokens signiert, das langweiligste, am besten prüfbare, am wenigsten trickreiche Objekt sein, das Sie besitzen. Unseres brauchte drei Leben, um dort anzukommen. Jedes einzelne war es wert.