Bei RSA-PSS vs. PKCS#1 v1.5 geht es darum, wie ein Hash vor der RSA-Operation mit dem privaten Schlüssel kodiert wird. Beide stammen aus PKCS #1 (RFC 8017), beide sind in FIPS 186-5 zugelassen, und beide funktionieren mit demselben RSA-Schlüsselpaar. Sie unterscheiden sich in ihren Sicherheitsargumenten, in ihrem Determinismus und in den Parametern, die bei Signierer und Verifizierer übereinstimmen müssen.
Auf einen Blick
| RSASSA-PSS | RSASSA-PKCS1-v1_5 | |
|---|---|---|
| Spezifikation | RFC 8017 §8.1, Kodierung EMSA-PSS §9.1 | RFC 8017 §8.2, Kodierung EMSA-PKCS1-v1_5 §9.2 |
| Kodierung | Hash mit Salt, maskiert mit MGF1 | 0x00 01 FF…FF 00 ‖ DigestInfo |
| Randomisiert | Ja (außer bei Salt-Länge 0) | Nein: Gleicher Schlüssel und gleiche Nachricht ergeben dieselbe Signatur |
| Sicherheitsbeweis | Enge Reduktion auf RSA im Random-Oracle-Modell | Kein vergleichbarer Beweis; kein praktischer Angriff auf korrekte Verifizierer |
| Zu vereinbarende Parameter | Hash, MGF-Hash, Salt-Länge, Trailer | Nur der Hash |
| Signaturgröße | Größe des Moduls (256 Byte bei RSA-2048) | Gleich |
| Geschwindigkeit | Gleiche RSA-Operation | Gleich |
| JOSE | PS256, PS384, PS512 (Salt = Hashlänge) | RS256, RS384, RS512 |
| TLS 1.3 | Pflicht für CertificateVerify mit RSA (rsa_pss_rsae_sha256 0x0804, rsa_pss_pss_sha256 0x0809) | Nur für Signaturen in Zertifikaten (rsa_pkcs1_sha256 0x0401) |
| X.509 | id-RSASSA-PSS 1.2.840.113549.1.1.10 mit Parametern | sha256WithRSAEncryption 1.2.840.113549.1.1.11 |
| Vorgabe in FIPS 186-5 | Salt-Länge zwischen 0 und der Hashlänge | Prüfungen von DigestInfo in §5.4(h) |
Unterschiede bei der Sicherheit
Das Signatur-Padding nach PKCS#1 v1.5 stammt aus dem Jahr 1991. Es ist deterministisch und hatte nie einen so sauberen Sicherheitsbeweis wie PSS, doch auch nach drei Jahrzehnten gibt es keinen praktischen Fälschungsangriff auf Verifizierer, die den gesamten kodierten Block prüfen. Die Brüche in der Praxis waren Implementierungsfehler: Bleichenbachers Angriff von 2006 fälschte Signaturen für den öffentlichen Exponenten 3 und zielte auf Parser, die überzählige Bytes hinter der Struktur DigestInfo ignorierten, und Varianten dieses Fehlers tauchten in Bibliotheken wie NSS (BERserk, 2014) und anderen wieder auf. RFC 8017 §8.2.2 vermeidet die ganze Fehlerklasse, indem es den erwarteten Block neu kodiert und die Bytes vergleicht, und FIPS 186-5 §5.4(h) schreibt die Prüfungen fest.
RSASSA-PSS (Bellare und Rogaway, standardisiert in PKCS #1 v2.1, 2002) hasht ein zufälliges Salt zusammen mit dem Hash der Nachricht und maskiert das Ergebnis mit MGF1. Seine Sicherheit lässt sich im Random-Oracle-Modell eng auf das RSA-Problem reduzieren, und dank der Randomisierung verrät ein Rechenfehler oder ein Seitenkanal bei einer einzelnen Signatur weniger. Mit einem Salt der Länge null wird PSS deterministisch, behält seinen Beweis aber unter leicht anderen Annahmen.
Verwechsle keines der beiden mit dem Padding von PKCS #1 v1.5 für die Verschlüsselung, das durch Padding-Oracle-Angriffe (Bleichenbacher 1998, ROBOT 2017) tatsächlich gebrochen ist und aus TLS 1.3 entfernt wurde. Signatur-Padding und Verschlüsselungs-Padding sind getrennte Verfahren, die sich den Namen teilen.
Interoperabilität: die Falle mit der Salt-Länge
Bei PSS müssen sich Signierer und Verifizierer auf drei Parameter einigen: den Hash, den MGF1-Hash und die Salt-Länge. JOSE legt alle drei fest (PS256 bedeutet SHA-256, MGF1-SHA-256 und ein 32-Byte-Salt), und TLS 1.3 verlangt, dass die Salt-Länge der Hashlänge entspricht. Überall sonst musst du nachprüfen: OpenSSL 1.1.1 und 3.0 signierten standardmäßig mit dem maximalen Salt (222 Byte bei RSA-2048 mit SHA-256), während OpenSSL 3.1 und neuer standardmäßig auto-digestmax verwenden, was ein Salt in der Größe des Digests ergibt. Verifizierer, die auf eine feste Salt-Länge eingestellt sind, lehnen dann Signaturen der jeweils anderen Generation ab.
Das folgende Skript, ausgeführt mit OpenSSL 3.6.5, zeigt beide Effekte.
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out key.pem 2>/dev/null
openssl pkey -in key.pem -pubout -out pub.pem
printf 'hello' > msg
openssl dgst -sha256 -sign key.pem -out v15a.sig msg
openssl dgst -sha256 -sign key.pem -out v15b.sig msg
cmp -s v15a.sig v15b.sig && echo "PKCS#1 v1.5: identical signatures" || echo "PKCS#1 v1.5: different signatures"
openssl dgst -sha256 -sign key.pem -sigopt rsa_padding_mode:pss -out pssa.sig msg
openssl dgst -sha256 -sign key.pem -sigopt rsa_padding_mode:pss -out pssb.sig msg
cmp -s pssa.sig pssb.sig && echo "PSS: identical signatures" || echo "PSS: different signatures"
for saltlen in digest max; do
printf 'verify PSS with rsa_pss_saltlen:%s -> ' "$saltlen"
openssl dgst -sha256 -verify pub.pem -sigopt rsa_padding_mode:pss \
-sigopt "rsa_pss_saltlen:$saltlen" -signature pssa.sig msg 2>&1 | grep -o -E "Verified OK|salt length check failed"
done
cd / && rm -rf "$dir"
PKCS#1 v1.5: identical signatures
PSS: different signatures
verify PSS with rsa_pss_saltlen:digest -> Verified OK
verify PSS with rsa_pss_saltlen:max -> salt length check failed
Verifizierer, die auto akzeptieren, erkennen die Salt-Länge anhand der Signatur; das ist die kompatibelste Einstellung, wenn der Signierer nicht in deiner Hand liegt.
Schlüssel und Zertifikate
Derselbe RSA-Schlüssel kann beide Arten von Signaturen erzeugen, und ein Schlüssel vom Typ rsaEncryption in einem Zertifikat ist für beide nutzbar (TLS nennt das rsa_pss_rsae). X.509 definiert außerdem Schlüssel vom Typ id-RSASSA-PSS, deren Parameter sie auf PSS beschränken (rsa_pss_pss in TLS); sie verhindern, dass ein Schlüssel für beide Verfahren genutzt wird. Viele Tools und manche Browser kommen aber schlecht mit ihnen zurecht. Die praktische Wahl sind daher Schlüssel vom Typ rsaEncryption, die mit PSS signieren. Zertifikatssignaturen in der WebPKI nutzen fast ausnahmslos PKCS#1 v1.5 oder ECDSA, und schon deshalb müssen Verifizierer v1.5 unterstützen.
Wann welches Verfahren passt
- Handshakes in TLS 1.3: PSS, verpflichtend; deine Bibliothek erledigt das bei jedem RSA-Zertifikat automatisch.
- JWTs:
PS256, wenn alle Empfänger es unterstützen, sonstRS256; noch besser: ZiehES256in Betracht (siehe ES256 vs. RS256). - X.509-Zertifikate und CRLs, die du ausstellst: PKCS#1 v1.5 (
sha256WithRSAEncryption) für maximale Kompatibilität, es sei denn, ein geschlossenes Ökosystem hat sich auf PSS festgelegt. - Neue Signaturen für Dokumente, Firmware oder APIs mit RSA-Schlüsseln: PSS mit einer Salt-Länge, die der Hashlänge entspricht.
- Interoperabilität mit HSMs und Cloud-KMS: Beide werden unterstützt (zum Beispiel
RSASSA_PSS_SHA_256undRSASSA_PKCS1_V1_5_SHA_256in AWS KMS); richte dich nach den Erwartungen des Verifizierers. - Prüfung bei beiden Verfahren: Nutze die High-Level-Prüffunktion einer Bibliothek und dekodiere die kodierte Nachricht nie von Hand.
Hinweise zur Migration
Der Umstieg von v1.5 auf PSS braucht keine neuen Schlüssel und lässt sich deshalb leicht schrittweise umsetzen.
- Bring zuerst den Verifizierern PSS bei: Erlaube in JOSE
PS256für den Schlüssel (weiterhin gilt die Regel „ein Algorithmus pro Schlüssel“, veröffentliche also dasselbe Schlüsselmaterial unter einem neuen Wert fürkidmitPS256alsalg, oder rotiere den Schlüssel). - Lege die Parameter ausdrücklich fest: Hash SHA-256, MGF1 SHA-256, Salt-Länge 32 Byte; vermeide Standardwerte von Bibliotheken, die sich von Version zu Version unterscheiden.
- Stelle die Signierer um und achte auf Fehler bei der Prüfung, die die Salt-Länge oder das Padding erwähnen.
- Akzeptiere v1.5 nicht mehr für diese Schlüssel, sobald alte Signaturen abgelaufen sind, und behalte v1.5 nur dort, wo Zertifikate oder Dritte es verlangen.
- Wenn du die Schlüssel ohnehin rotierst, erwäge stattdessen ECDSA P-256 oder Ed25519: Beide sind kleiner und signieren schneller als RSA mit jedem beliebigen Padding.