01 · Problem
Wofür braucht man das?
Empfänger müssen Integrität und Herkunft einer Nachricht prüfen können, ohne Zugriff auf den privaten Schlüssel zu besitzen.
02 · Denkmodell
Das mentale Modell
Header plus Payload werden kanonisch serialisiert und mit Private Key signiert; Key-ID wählt beim Prüfen den passenden Public Key.
Signature, PayloadSigner und KeyPairEncryption werden exportiert, doch CryptoKey/KeyObject und Persistenztypen widersprechen sich. Der Konstruktor startet nicht awaitbare Keygenerierung und AES-CBC-Daten lassen sich nicht korrekt JSON-serialisieren.
03 · Kontrollfluss
Was passiert in welcher Reihenfolge?
- Keypair über eine explizite async Factory erzeugen oder laden.
- Versionierten Header und Payload kanonisch in Bytes wandeln.
- Bytes mit dem privaten Signaturschlüssel signieren.
- Key-ID auflösen, Timestamp-Policy prüfen und dieselben Bytes verifizieren.
- Private Keys versioniert und authentifiziert verschlüsselt persistieren.
04 · Bauteile
Die entscheidenden Verträge
Signature<T>
High-Level-Fassade mit drei rotierenden RSA-Keypairs.
PayloadSigner<T>
Erstellt Header und signiert/verifiziert serialisierte Daten.
KeyPairEncryption
Exportiert Keys und verschlüsselt Private-Key-Material per PBKDF2/AES-CBC.
KeyPairManager (intern)
Erzeugt, rotiert und findet Keys, wird aber nicht am Root exportiert.
05 · Build it yourself
Selbst implementieren
Beginne mit einer awaitbaren Factory und einem festen Signaturalgorithmus; Persistenz ist ein separater Schritt.
- Keypair vollständig erzeugen, bevor eine Signer-Instanz zurückgegeben wird.
- Header und Payload in ein kanonisches, versioniertes Byteformat bringen.
- Private-Key-Export mit eigener KDF-Salt und AES-GCM-IV als Base64url speichern.
async function sign(payload: unknown, privateKey: CryptoKey) {
const bytes = new TextEncoder().encode(canonicalJson(payload));
const signature = await crypto.subtle.sign(
{ name: "RSA-PSS", saltLength: 32 },
privateKey,
bytes,
);
return new Uint8Array(signature);
}
async function verify(payload: unknown, value: BufferSource, publicKey: CryptoKey) {
const bytes = new TextEncoder().encode(canonicalJson(payload));
return crypto.subtle.verify({ name: "RSA-PSS", saltLength: 32 }, publicKey, value, bytes);
}06 · Verifizieren
Was du testen solltest
- Originalpayload verifiziert; veränderter Header, Payload oder Signatur verifiziert nicht.
- Signieren ist vor abgeschlossener Keyinitialisierung durch die API unmöglich.
- Export/Encrypt/Serialize/Parse/Decrypt/Import bildet den privaten Key verlustfrei ab und erkennt Manipulation.
07 · Grenzen
Kompromisse und Stolperfallen
- RSA-PSS ist breit verfügbar, aber Key und Signatur sind groß.
- Kanonische Serialisierung ist zusätzlicher Aufwand und unverzichtbar für reproduzierbare Bytes.
- Keyrotation begrenzt Schäden, erfordert aber Historie, Ablauf und Revocation.
RSA-OAEP ist ein Verschlüsselungs-, kein Signaturalgorithmus. Für persistierte Secrets authentifizierte Verschlüsselung wie AES-GCM verwenden.
08 · Weiterdenken