Zum Inhalt springen
ecdsa.com

HS256 vs. RS256: symmetrische oder asymmetrische JWTs

HS256 ist ein HMAC: Jeder Verifizierer kann auch Tokens ausstellen. RS256 ist eine Signatur: Verifizierer besitzen nur einen öffentlichen Schlüssel. Die meisten Designfehler bei JWTs beginnen damit, dass dieser Unterschied übersehen wird.
Im VergleichHS256RS256

HS256 vs. RS256 ist die erste Entscheidung, wenn du JWTs ausstellst. Die beiden Werte für alg sehen ähnlich aus, aber HS256 authentifiziert Tokens mit einem gemeinsamen Geheimnis (HMAC-SHA256), während RS256 sie mit einem privaten RSA-Schlüssel signiert (RSASSA-PKCS1-v1_5 mit SHA-256). Davon hängt ab, wer Tokens erzeugen kann, wie Schlüssel verteilt werden und was passiert, wenn ein Schlüssel durchsickert.

Auf einen Blick

HS256RS256
AlgorithmusHMAC mit SHA-256 (RFC 7518 §3.2)RSASSA-PKCS1-v1_5 mit SHA-256 (§3.3)
SchlüsselEin gemeinsames Geheimnis, mindestens 256 BitRSA-Schlüsselpaar, mindestens 2048 Bit
Wer prüfen kannInhaber des GeheimnissesJeder mit dem öffentlichen Schlüssel
Wer Tokens ausstellen kannJeder Inhaber des Geheimnisses, auch VerifiziererNur der Inhaber des privaten Schlüssels
SchlüsselverteilungVertrauliche Kopie an jeden VerifiziererÖffentliches JWKS (jwks_uri)
Signaturteil32 Byte, 43 Zeichen in base64url256 Byte, 342 Zeichen (RSA-2048)
Beispiel-Token (unten gemessen)187 Zeichen508 Zeichen
Geschwindigkeit, ein Kern eines Apple M4 MaxMillionen HMACs pro Sekundeetwa 3.000 Signaturen und 114.000 Prüfungen pro Sekunde
Anforderungsstufe in JWARequiredRecommended
Typischer FehlerfallSchwaches oder durchgesickertes Geheimnis: Jeder kann Tokens ausstellenAlgorithm Confusion bei nachlässigen Verifizierern; Umgang mit dem Schlüssel

Das Vertrauensmodell

Bei HS256 sind Prüfschlüssel und Signaturschlüssel dieselben Bytes. Jeder Dienst, der Tokens prüft, kann auch gültige Tokens für jeden Benutzer, jeden Scope und jede Audience erzeugen, und ein Geheimnis, das aus einem einzigen dieser Dienste durchsickert (eine Umgebungsvariable in einem Log, ein Frontend-Bundle, eine mobile App), erlaubt jedem, Tokens auszustellen. Für einen Monolithen, der seine eigenen Session-Tokens ausstellt und prüft, ist das akzeptabel; wenn ein Token-Aussteller viele APIs oder Teams bedient, ist es eine ernste Schwäche.

Bei RS256 behält der Aussteller den privaten Schlüssel, idealerweise in einem HSM oder KMS, und veröffentlicht den öffentlichen Schlüssel in einem JWKS unter einer URL, die in seinen Metadaten steht. Verifizierer rufen den Schlüsselsatz ab, wählen Schlüssel anhand von kid aus und lassen sich hinzufügen oder entfernen, ohne dass Geheimnisse geteilt werden. Ein kompromittierter Verifizierer kann Tokens lesen, aber nicht fälschen. Deshalb signieren Anbieter von OpenID Connect die ID-Tokens standardmäßig mit RS256, und deshalb empfiehlt der RFC zu Best Practices für JWTs (RFC 8725) für Einsätze mit mehreren Parteien asymmetrische Algorithmen.

OpenID Connect erlaubt durchaus HS256-ID-Tokens für vertrauliche Clients, mit dem Client-Secret als Schlüssel; das funktioniert, weil jeder Client nur Tokens prüft, die für ihn selbst ausgestellt wurden.

Stärke des Geheimnisses und Offline-Knacken

Ein HS256-Token ist ein perfektes Orakel für Offline-Tests: Wer ein einziges Token sieht, kann Kandidaten für das Geheimnis so schnell durchprobieren, wie seine Hardware HMAC-SHA256 berechnet, ohne mit deinem Server zu kommunizieren. Kurze, von Menschen gewählte oder voreingestellte Geheimnisse („secret“, ein Projektname, das Beispiel aus einem Tutorial) werden mit gängigen Tools zum Knacken von Passwörtern in Sekunden ermittelt. RFC 7518 verlangt einen Schlüssel, der mindestens so lang ist wie die Ausgabe der Hashfunktion, also 256 Bit für HS256, und laut RFC 8725 §3.5 müssen Schlüssel genügend Entropie haben. Erzeuge das Geheimnis mit einem CSPRNG (openssl rand -base64 32) und leite es nie aus einem Passwort ab.

Bei RS256 gibt es keinen vergleichbaren Offline-Angriff auf den privaten Schlüssel, aber RFC 7518 lässt keine RSA-Schlüssel unter 2048 Bit zu, und die Schlüsseldatei muss wie jeder private Schlüssel geschützt werden.

Algorithm Confusion zwischen beiden

Die bekannteste JWT-Schwachstelle vermischt die beiden. Ein Dienst prüft RS256-Tokens, indem er eine Bibliothek mit dem öffentlichen RSA-Schlüssel im PEM-Format aufruft. Übernimmt die Bibliothek den Algorithmus aus dem Header des Tokens und unterstützt sie auch HS256, schickt ein Angreifer ein Token mit "alg": "HS256", dessen HMAC mit den PEM-Bytes des öffentlichen Schlüssels als Geheimnis berechnet wurde; die Bibliothek verwendet dieselben Bytes, und die Fälschung besteht die Prüfung. Tim McLean beschrieb das 2015 zusammen mit "alg": "none", und RFC 8725 §2.1 und §3.1 schreiben die Abhilfe fest: Der Verifizierer, nicht das Token, entscheidet, mit welchem Algorithmus jeder Schlüssel verwendet werden darf.

Moderne Bibliotheken machen das schwer: jsonwebtoken weigert sich, einen öffentlichen Schlüssel als HMAC-Geheimnis zu verwenden (der Fehler secretOrPublicKey must be an asymmetric key erscheint, wenn Konfigurationen die beiden verwechseln), PyJWT verlangt ein Argument algorithms, und jose bindet Algorithmen an Schlüsseltypen. Übergib trotzdem immer eine explizite Allowlist mit genau dem Algorithmus, den du erwartest.

Größe und Geschwindigkeit

Das folgende Skript, ausgeführt mit Node.js 26.10, baut aus denselben Claims nur mit node:crypto ein HS256- und ein RS256-Token.

Node.js: HS256- und RS256-Tokens für dieselbe Payload
import { createHmac, generateKeyPairSync, randomBytes, sign } from "node:crypto";

const b64url = (value) => Buffer.from(value).toString("base64url");
const payload = b64url(JSON.stringify({ iss: "https://auth.example.com", sub: "user-42", aud: "api", exp: 1792500000 }));

// HS256: HMAC-SHA256 with a shared 256-bit secret.
const secret = randomBytes(32);
const hsInput = `${b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }))}.${payload}`;
const hs256 = `${hsInput}.${b64url(createHmac("sha256", secret).update(hsInput).digest())}`;

// RS256: RSASSA-PKCS1-v1_5 with SHA-256 and a 2048-bit key.
const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
const rsInput = `${b64url(JSON.stringify({ alg: "RS256", typ: "JWT", kid: "2026-10" }))}.${payload}`;
const rs256 = `${rsInput}.${b64url(sign("sha256", Buffer.from(rsInput), privateKey))}`;

console.log("HS256 token:", hs256.length, "characters, signature part:", hs256.split(".")[2].length);
console.log("RS256 token:", rs256.length, "characters, signature part:", rs256.split(".")[2].length);
Ausgabe
HS256 token: 187 characters, signature part: 43
RS256 token: 508 characters, signature part: 342

Der RS256-Header enthält außerdem einen kid, den Verifizierer brauchen, um den Schlüssel aus einem JWKS auszuwählen. Verwende in Produktion eine gepflegte JWT-Bibliothek statt selbst gebauter Tokens; hier geht es um die Größe des Signaturteils. Einen Vergleich derselben Abwägung mit ECDSA findest du unter ES256 vs. RS256.

Wann welches Verfahren passt

  • Eine einzelne Anwendung, die ihre eigenen Session-Tokens ausstellt und prüft: HS256 mit einem zufälligen 32-Byte-Geheimnis, rotiert mit kurzer Überlappung.
  • Ein Autorisierungsserver, dessen Tokens von mehreren APIs geprüft werden: RS256, PS256 oder ES256 mit in einem JWKS veröffentlichten Schlüsseln.
  • Tokens, die von Dritten, mobilen Apps oder Browsern geprüft werden: nur asymmetrisch; ein an einen Client ausgeliefertes Geheimnis ist öffentlich.
  • ID-Tokens von OpenID Connect für vertrauliche Clients: Beides funktioniert; asymmetrisch ist der Standard und einfacher zu betreiben.
  • Webhooks an einzelne Kunden: HMAC-artige Verfahren sind verbreitet, weil jeder Kunde sein eigenes Geheimnis hat.
  • Schlüsselverwahrung nach Compliance-Vorgaben (HSM, KMS): asymmetrische Schlüssel, da Dienste prüfen können, ohne Zugriff auf das Schlüsselmaterial zum Signieren zu haben.

Hinweise zur Migration

Umstieg von HS256 auf RS256 oder ES256 ohne harten Stichtag:

  1. Erzeuge das Schlüsselpaar in einem KMS oder HSM und veröffentliche den öffentlichen Schlüssel in einem JWKS mit neuem kid und alg.
  2. Bring den Verifizierern beides bei: Wähle den Schlüssel anhand von kid aus und binde jeden Schlüssel an genau einen Algorithmus (das gemeinsame Geheimnis an HS256, den neuen Schlüssel an RS256). Lass nie zu, dass ein einzelner Aufruf zur Prüfung für dasselbe Schlüsselmaterial beide Algorithmen akzeptiert.
  3. Beginne, asymmetrische Tokens auszustellen, mit dem neuen kid; vorhandene HS256-Tokens lassen sich weiter prüfen, bis sie ablaufen.
  4. Entferne das gemeinsame Geheimnis von jedem Verifizierer, sobald die längste Laufzeit eines HS256-Tokens verstrichen ist. Dieser Schritt ist der eigentliche Sicherheitsgewinn.
  5. Rotiere das alte Geheimnis trotzdem, wenn es auch nur möglicherweise durchgesickert ist, denn ein Angreifer, der es besitzt, könnte weiterhin HS256-Tokens ausstellen, bis die Verifizierer sie nicht mehr akzeptieren.

Quellen

Alle Vergleiche