EnglishGitHub
Prototyppackages/signature/src

Signature

@ralphschuler/signature

Signiert JSON-Umschläge mit RSA-SHA256 und verschlüsselt Schlüsselexporte per AES-GCM.

WebCryptoRSA-PSSkey rotation

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.

Im Repository

Signiert JSON-Umschläge mit RSA-SHA256 und verschlüsselt Schlüsselexporte per AES-GCM. Schlüssel gehören lokal zum Signer. Produktiver Vertrauensaufbau, kanonische Serialisierung und Rotation benötigen ein Anwendungsdesign.

03 · Kontrollfluss

Was passiert in welcher Reihenfolge?

  1. Keypair über eine explizite async Factory erzeugen oder laden.
  2. Versionierten Header und Payload kanonisch in Bytes wandeln.
  3. Bytes mit dem privaten Signaturschlüssel signieren.
  4. Key-ID auflösen, Timestamp-Policy prüfen und dieselben Bytes verifizieren.
  5. Private Keys versioniert und authentifiziert verschlüsselt persistieren.

04 · Bauteile

Die entscheidenden Verträge

Signature.signData / verifySignatureErzeugt und prüft einen JSON-Signaturumschlag.
KeyPairEncryptionVerschlüsselt exportierte private Schlüssel per scrypt und AES-256-GCM.
PayloadSignerSigniert und prüft exakte serialisierte Bytes.

05 · Selbst bauen

Selbst implementieren

Beginne mit einer awaitbaren Factory und einem festen Signaturalgorithmus; Persistenz ist ein separater Schritt.

  1. Keypair vollständig erzeugen, bevor eine Signer-Instanz zurückgegeben wird.
  2. Header und Payload in ein kanonisches, versioniertes Byteformat bringen.
  3. Private-Key-Export mit eigener KDF-Salt und AES-GCM-IV als Base64url speichern.
Konzeptskizze · kann ausgelassene Helfer verwenden
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 solltest du testen?

  • 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.

Selbst ausprobieren

Formuliere die erste Prüfaussage als fehlschlagenden Test, implementiere die kleinste Korrektur und ergänze einen Grenzfall.

Originalpayload verifiziert; veränderter Header, Payload oder Signatur verifiziert nicht.

Ansatz vergleichen

Dein Test sollte beim gezielten Verletzen der Invariante fehlschlagen und nach der Korrektur wieder bestehen. Halte den Aufbau deterministisch und prüfe ein beobachtbares Ergebnis.

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.
Wichtig

Schlüssel gehören lokal zum Signer. Produktiver Vertrauensaufbau, kanonische Serialisierung und Rotation benötigen ein Anwendungsdesign.

08 · Weiterdenken

Quellcode und verwandte Ideen

yarn tsx packages/signature/examples/basic.ts