{"id":86,"date":"2026-06-12T16:15:43","date_gmt":"2026-06-12T16:15:43","guid":{"rendered":"https:\/\/shattered.io\/de\/2026\/06\/12\/ecdsa-nodejs-signaturen\/"},"modified":"2026-06-12T20:08:46","modified_gmt":"2026-06-12T20:08:46","slug":"ecdsa-nodejs-signaturen","status":"publish","type":"post","link":"https:\/\/shattered.io\/de\/2026\/06\/12\/ecdsa-nodejs-signaturen\/","title":{"rendered":"ECDSA in Node.js: Signaturen in 11 Schritten [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Digitale Signaturen halten das halbe Internet zusammen. Jedes TLS-Zertifikat, jedes signierte JSON Web Token, jede Bitcoin-Transaktion und jeder SSH-Login st\u00fctzt sich auf dieselbe Idee: Ein privater Schl\u00fcssel erzeugt eine Signatur, die jeder mit dem passenden \u00f6ffentlichen Schl\u00fcssel pr\u00fcfen kann, ohne den geheimen Teil je zu sehen. Der mit Abstand am h\u00e4ufigsten eingesetzte Algorithmus daf\u00fcr hei\u00dft <strong>ECDSA<\/strong> (Elliptic Curve Digital Signature Algorithm).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In diesem Tutorial bauen Sie ECDSA von Grund auf in Node.js, ausschlie\u00dflich mit dem eingebauten <code>crypto<\/code>-Modul, ohne externe Bibliotheken. Sie erzeugen Schl\u00fcsselpaare, signieren und verifizieren Nachrichten, exportieren Schl\u00fcssel als PEM, arbeiten mit der Bitcoin-Kurve secp256k1, vermeiden die ber\u00fcchtigte Nonce-Falle und bauen am Ende eine signierte REST-API mit ES256-Tokens. Geplante Zeit: rund 45 Minuten. Stand: 12. Juni 2026.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"was-ist-ecdsa-und-warum-es-2026-noch-zaehlt\">Was ist ECDSA und warum es 2026 noch z\u00e4hlt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA ist die Variante des klassischen Digital Signature Algorithm, die auf elliptischen Kurven rechnet statt auf gro\u00dfen Primzahl-Modulen wie RSA. Der Vorteil ist drastisch kleinere Schl\u00fcssel bei gleicher Sicherheit. Ein P-256-Schl\u00fcssel liefert etwa 128 Bit Sicherheit und entspricht damit grob einem 3072-Bit-RSA-Schl\u00fcssel. Ein P-384-Schl\u00fcssel liegt ungef\u00e4hr auf dem Niveau von 7680-Bit-RSA. Kleinere Schl\u00fcssel bedeuten schnellere Handshakes, kleinere Zertifikate und weniger Bandbreite, weshalb moderne TLS-Verbindungen ECDSA-Zertifikaten den Vorzug geben.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eine <a href=\"\/de\/digitale-signaturen\/\">digitale Signatur<\/a> garantiert drei Dinge gleichzeitig: Authentizit\u00e4t (die Nachricht stammt vom Inhaber des privaten Schl\u00fcssels), Integrit\u00e4t (sie wurde nicht ver\u00e4ndert) und Nichtabstreitbarkeit (der Unterzeichner kann die Signatur nicht glaubhaft leugnen). ECDSA erreicht das, indem es zuerst einen Hash der Nachricht bildet, meist mit SHA-256, und dann diesen Hash mit dem privaten Schl\u00fcssel und einer Zufallszahl signiert. Ohne den Hash w\u00fcrde der Algorithmus nicht funktionieren, weshalb ein solides Verst\u00e4ndnis von <a href=\"\/de\/sha-256\/\">SHA-256<\/a> die Basis f\u00fcr jede ECDSA-Implementierung bildet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wo begegnet Ihnen ECDSA konkret? In TLS-Zertifikatsketten und im Handshake. In JWT mit dem Algorithmus ES256. In SSH als Host- und Nutzerschl\u00fcssel neben Ed25519 und RSA. Und in jeder einzelnen Bitcoin- und Ethereum-Transaktion, dort \u00fcber die spezielle Kurve secp256k1. Wer Backend-Software baut, kommt an ECDSA praktisch nicht vorbei. Genau deshalb lohnt es sich, den Algorithmus einmal mit eigenen H\u00e4nden in Node.js umzusetzen statt ihn als Blackbox zu behandeln.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"voraussetzungen-versionen-und-werkzeuge\">Voraussetzungen: Versionen und Werkzeuge<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dieses Tutorial setzt eine moderne Node.js-Umgebung voraus. Alle Code-Beispiele nutzen ausschlie\u00dflich Bordmittel, sodass Sie keine npm-Pakete installieren m\u00fcssen (bis auf das optionale JWT-Beispiel am Ende). Pr\u00fcfen Sie zuerst Ihre Versionen.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Komponente<\/th><th>Empfohlene Version (2026)<\/th><th>Mindestversion<\/th><th>Pr\u00fcfbefehl<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>24.x LTS (Krypton)<\/td><td>20.x<\/td><td><code>node -v<\/code><\/td><\/tr><tr><td>OpenSSL (in Node geb\u00fcndelt)<\/td><td>3.5<\/td><td>3.0<\/td><td><code>node -p \"process.versions.openssl\"<\/code><\/td><\/tr><tr><td>npm<\/td><td>11.x<\/td><td>10.x<\/td><td><code>npm -v<\/code><\/td><\/tr><tr><td>Editor<\/td><td>VS Code \/ beliebig<\/td><td>&#8211;<\/td><td>&#8211;<\/td><\/tr><tr><td>Terminal<\/td><td>bash \/ zsh \/ PowerShell<\/td><td>&#8211;<\/td><td>&#8211;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js 24 ist 2026 die aktuelle LTS-Linie f\u00fcr neue Projekte und wird bis April 2028 unterst\u00fctzt. Sein <code>crypto<\/code>-Modul basiert auf OpenSSL 3.5 und l\u00e4uft mit dem Standard-Sicherheitslevel 2. Das ist wichtig: Auf Sicherheitslevel 2 verbietet OpenSSL elliptische Kurven mit weniger als 224 Bit sowie RSA-Schl\u00fcssel unter 2048 Bit. Alle Kurven in diesem Tutorial (P-256, P-384, secp256k1) liegen klar \u00fcber dieser Grenze, sodass es keine Probleme gibt. Node.js 22 funktioniert ebenfalls, befindet sich aber im Wartungsmodus.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Falls <code>node -v<\/code> eine Version unter 20 zeigt, aktualisieren Sie zuerst. Auf Linux und macOS ist nvm der bequemste Weg, unter Windows der offizielle Installer von nodejs.org. Legen Sie danach einen Arbeitsordner an und stellen Sie sicher, dass Sie Schreibrechte besitzen. Den vollst\u00e4ndigen Funktionsumfang des Moduls finden Sie in der <a href=\"https:\/\/nodejs.org\/api\/crypto.html\" target=\"_blank\" rel=\"noopener\">offiziellen Node.js crypto-Dokumentation<\/a>, die wir in diesem Tutorial mehrfach als Referenz heranziehen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ecdsa-vs-rsa-vs-ed25519-der-direkte-vergleich\">ECDSA vs. RSA vs. Ed25519: der direkte Vergleich<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bevor Sie Code schreiben, sollten Sie wissen, wann ECDSA die richtige Wahl ist und wann nicht. Drei Signaturverfahren dominieren 2026 die Praxis. Die folgende Tabelle stellt sie gegen\u00fcber.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Eigenschaft<\/th><th>ECDSA (P-256)<\/th><th>RSA-3072<\/th><th>Ed25519 (EdDSA)<\/th><\/tr><\/thead><tbody><tr><td>Sicherheitsniveau<\/td><td>~128 Bit<\/td><td>~128 Bit<\/td><td>~128 Bit<\/td><\/tr><tr><td>\u00d6ffentlicher Schl\u00fcssel<\/td><td>~65 Byte<\/td><td>~398 Byte<\/td><td>32 Byte<\/td><\/tr><tr><td>Signaturgr\u00f6\u00dfe<\/td><td>~64 bis 72 Byte<\/td><td>~384 Byte<\/td><td>64 Byte<\/td><\/tr><tr><td>Signieren (relativ)<\/td><td>schnell<\/td><td>langsam<\/td><td>sehr schnell<\/td><\/tr><tr><td>Verifizieren (relativ)<\/td><td>mittel<\/td><td>sehr schnell<\/td><td>schnell<\/td><\/tr><tr><td>Nonce-empfindlich<\/td><td>ja (kritisch)<\/td><td>nein<\/td><td>nein (deterministisch)<\/td><\/tr><tr><td>JWT-Kennung<\/td><td>ES256 \/ ES384<\/td><td>RS256 \/ PS256<\/td><td>EdDSA<\/td><\/tr><tr><td>Bitcoin \/ Ethereum<\/td><td>ja (secp256k1)<\/td><td>nein<\/td><td>nein<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Die wichtigste Erkenntnis: Ed25519 geh\u00f6rt nicht zu ECDSA, sondern zur Familie EdDSA. Es nutzt einen deterministischen Ansatz und umgeht damit die gef\u00e4hrlichste Schw\u00e4che von ECDSA, die Zufallszahl pro Signatur. Wenn Sie heute ein neues System ohne Altlasten bauen und Ihre Gegenstellen Ed25519 unterst\u00fctzen, ist es oft die robustere Wahl. ECDSA gewinnt dort, wo Sie Kompatibilit\u00e4t brauchen: TLS-Zertifikate von \u00f6ffentlichen CAs, ES256-Tokens in OAuth-\u00d6kosystemen, SSH gegen \u00e4ltere Server und vor allem Blockchain. Ein SSH-Schl\u00fcssel auf Basis von Ed25519 l\u00e4sst sich \u00fcbrigens in wenigen Minuten erzeugen, wie unser Leitfaden zum <a href=\"\/de\/ssh-key-erstellen-ed25519-2026\/\">SSH-Key mit Ed25519<\/a> zeigt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">RSA bleibt relevant, wenn Verifikation extrem h\u00e4ufig und Signierung selten passiert, etwa bei Software-Signaturen, die Millionen Clients pr\u00fcfen. F\u00fcr die meisten Web-Backends ist ECDSA mit P-256 jedoch der pragmatische Mittelweg aus Geschwindigkeit, Kompatibilit\u00e4t und kleinen Schl\u00fcsseln.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-1-das-projekt-einrichten\">Schritt 1: Das Projekt einrichten<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Erstellen Sie einen Projektordner und initialisieren Sie ihn. Wir aktivieren ES-Module, damit wir modernes <code>import<\/code>-Syntax nutzen k\u00f6nnen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir ecdsa-tutorial && cd ecdsa-tutorial\nnpm init -y\nnpm pkg set type=module\nnode -v          # erwartet v24.x oder mindestens v20.x\nnode -p \"process.versions.openssl\"   # erwartet 3.5 oder h\u00f6her<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Befehl <code>npm pkg set type=module<\/code> tr\u00e4gt <code>\"type\": \"module\"<\/code> in die <code>package.json<\/code> ein. Damit behandelt Node jede <code>.js<\/code>-Datei als ES-Modul. Legen Sie nun eine erste Datei an und pr\u00fcfen Sie, dass das crypto-Modul verf\u00fcgbar ist.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ check.js\nimport { getCurves } from 'node:crypto';\n\nconsole.log('Verf\u00fcgbare Kurven (Auszug):');\nconsole.log(getCurves().filter(c =&gt; ['prime256v1', 'secp384r1', 'secp256k1'].includes(c)));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fchren Sie die Datei mit <code>node check.js<\/code> aus. Die erwartete Ausgabe best\u00e4tigt, dass die drei Kurven, die wir nutzen werden, vorhanden sind:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Verf\u00fcgbare Kurven (Auszug):\n[ 'prime256v1', 'secp384r1', 'secp256k1' ]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Wichtig zur Namensgebung: In OpenSSL und damit in Node hei\u00dft die NIST-Kurve P-256 intern <code>prime256v1<\/code>. P-384 hei\u00dft <code>secp384r1<\/code>. Diese internen Namen verwenden Sie \u00fcberall dort, wo Node eine Kurve als Zeichenkette erwartet. Merken Sie sich die Zuordnung, sie ist eine h\u00e4ufige Stolperfalle.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-2-ein-ecdsa-schluesselpaar-erzeugen\">Schritt 2: Ein ECDSA-Schl\u00fcsselpaar erzeugen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Jetzt erzeugen Sie das Herzst\u00fcck: ein Schl\u00fcsselpaar auf der Kurve P-256. Node bietet daf\u00fcr <code>generateKeyPairSync<\/code>, die synchrone Variante, ideal zum Lernen. In Produktion bevorzugen Sie die asynchrone Variante, dazu sp\u00e4ter mehr.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ keys.js\nimport { generateKeyPairSync } from 'node:crypto';\n\nconst { publicKey, privateKey } = generateKeyPairSync('ec', {\n  namedCurve: 'prime256v1',   \/\/ entspricht NIST P-256\n});\n\nconsole.log('Privater Schl\u00fcssel-Typ:', privateKey.asymmetricKeyType);\nconsole.log('Kurve:', privateKey.asymmetricKeyDetails.namedCurve);\n\nexport { publicKey, privateKey };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Ausgabe sieht so aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Privater Schl\u00fcssel-Typ: ec\nKurve: prime256v1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Beachten Sie, dass <code>publicKey<\/code> und <code>privateKey<\/code> hier <code>KeyObject<\/code>-Instanzen sind, keine reinen Zeichenketten. Ein KeyObject ist Nodes interne, sichere Repr\u00e4sentation eines Schl\u00fcssels. Es verhindert versehentliches Loggen des geheimen Materials und l\u00e4sst sich direkt an Signierfunktionen \u00fcbergeben. Erst wenn Sie Schl\u00fcssel speichern oder \u00fcber das Netz schicken, wandeln Sie sie in PEM- oder DER-Format um, das zeigt Schritt 5.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wollen Sie stattdessen die st\u00e4rkere Kurve P-384, \u00e4ndern Sie lediglich <code>namedCurve: 'secp384r1'<\/code>. F\u00fcr Bitcoin-kompatible Schl\u00fcssel verwenden Sie <code>'secp256k1'<\/code>. Der restliche Code bleibt identisch, das ist die Eleganz der KeyObject-Abstraktion.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-3-eine-nachricht-signieren\">Schritt 3: Eine Nachricht signieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Mit dem Schl\u00fcsselpaar in der Hand signieren Sie nun Daten. Node stellt die <code>sign<\/code>-Funktion bereit, die den Hash intern berechnet. Sie geben nur den Hash-Algorithmus an, hier SHA-256, was zusammen mit P-256 dem JWT-Standard ES256 entspricht.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ sign.js\nimport { sign } from 'node:crypto';\nimport { privateKey } from '.\/keys.js';\n\nconst nachricht = Buffer.from('\u00dcberweisung: 250 EUR an Konto DE89...');\n\n\/\/ SHA-256 wird intern gebildet, dann mit dem privaten Schl\u00fcssel signiert\nconst signatur = sign('sha256', nachricht, {\n  key: privateKey,\n  dsaEncoding: 'ieee-p1363',   \/\/ festes 64-Byte-Format, ideal f\u00fcr JWT\/Web\n});\n\nconsole.log('Signaturl\u00e4nge:', signatur.length, 'Byte');\nconsole.log('Signatur (base64url):', signatur.toString('base64url'));\n\nexport { nachricht, signatur };<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Eine typische Ausgabe:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Signaturl\u00e4nge: 64 Byte\nSignatur (base64url): k3Jx9... (gek\u00fcrzt)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Parameter <code>dsaEncoding<\/code> ist entscheidend und wird oft \u00fcbersehen. ECDSA-Signaturen bestehen aus zwei Zahlen, r und s. Es gibt zwei Wege, sie zu kodieren. Der Standard <code>'der'<\/code> verpackt sie in eine ASN.1-Struktur variabler L\u00e4nge (etwa 70 bis 72 Byte). Das Format <code>'ieee-p1363'<\/code> h\u00e4ngt r und s einfach aneinander und liefert konstant 64 Byte bei P-256. Web-Standards wie JWT (ES256) und WebCrypto verlangen P1363. Wenn Sie mit OpenSSL-Kommandozeile oder X.509-Zertifikaten arbeiten, brauchen Sie DER. Verwechseln Sie die beiden, schl\u00e4gt jede Verifikation fehl, obwohl Schl\u00fcssel und Daten korrekt sind.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein zweiter wichtiger Punkt: Jeder Aufruf von <code>sign<\/code> erzeugt eine andere Signatur f\u00fcr dieselbe Nachricht. Das ist kein Fehler, sondern liegt an der internen Zufallszahl k. Solange diese Zufallszahl echt zuf\u00e4llig ist, ist das Verhalten korrekt und sicher.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-4-die-signatur-verifizieren\">Schritt 4: Die Signatur verifizieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die Verifikation nutzt den \u00f6ffentlichen Schl\u00fcssel. Sie liefert einen schlichten Booleschen Wert: <code>true<\/code> bei g\u00fcltiger Signatur, <code>false<\/code> sonst. Wichtig ist, dass Sie exakt dieselbe <code>dsaEncoding<\/code> wie beim Signieren angeben.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ verify.js\nimport { verify } from 'node:crypto';\nimport { publicKey } from '.\/keys.js';\nimport { nachricht, signatur } from '.\/sign.js';\n\nconst gueltig = verify('sha256', nachricht, {\n  key: publicKey,\n  dsaEncoding: 'ieee-p1363',\n}, signatur);\n\nconsole.log('Signatur g\u00fcltig?', gueltig);   \/\/ true\n\n\/\/ Manipulationstest: ein Byte \u00e4ndern\nconst gefaelscht = Buffer.from('\u00dcberweisung: 999 EUR an Konto DE89...');\nconst gueltig2 = verify('sha256', gefaelscht, {\n  key: publicKey,\n  dsaEncoding: 'ieee-p1363',\n}, signatur);\n\nconsole.log('Manipulierte Nachricht g\u00fcltig?', gueltig2);   \/\/ false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Erwartete Ausgabe:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Signatur g\u00fcltig? true\nManipulierte Nachricht g\u00fcltig? false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dieser Manipulationstest demonstriert die Kernaussage jeder Signatur: Schon die \u00c4nderung eines einzigen Zeichens (250 zu 999) f\u00fchrt zu einem v\u00f6llig anderen SHA-256-Hash, und die alte Signatur passt nicht mehr. Genau dieses Prinzip sch\u00fctzt TLS-Verbindungen, Software-Updates und Blockchain-Transaktionen. Wer tiefer verstehen will, warum schon kleinste \u00c4nderungen den Hash komplett umwerfen, findet die Erkl\u00e4rung in unserem Beitrag \u00fcber <a href=\"\/de\/digitale-signaturen\/\">digitale Signaturen<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein praktischer Sicherheitshinweis: Behandeln Sie eine <code>false<\/code>-R\u00fcckgabe immer als Ablehnung der gesamten Anfrage. Loggen Sie den Vorfall, aber verraten Sie dem Aufrufer nie, warum genau die Pr\u00fcfung scheiterte. Detaillierte Fehlermeldungen helfen Angreifern beim systematischen Ausprobieren.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-5-schluessel-als-pem-exportieren-und-laden\">Schritt 5: Schl\u00fcssel als PEM exportieren und laden<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bisher lebten die Schl\u00fcssel nur im Speicher. F\u00fcr echte Anwendungen m\u00fcssen Sie sie persistieren, etwa in einer Datei oder einem Secret-Manager. Das Standardformat daf\u00fcr ist PEM, eine base64-kodierte Textdarstellung mit Kopf- und Fu\u00dfzeile.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ export.js\nimport { generateKeyPairSync, createPrivateKey, createPublicKey } from 'node:crypto';\nimport { writeFileSync, readFileSync } from 'node:fs';\n\nconst { publicKey, privateKey } = generateKeyPairSync('ec', {\n  namedCurve: 'prime256v1',\n});\n\n\/\/ Export als PEM-Text\nconst privPem = privateKey.export({ type: 'pkcs8', format: 'pem' });\nconst pubPem  = publicKey.export({ type: 'spki', format: 'pem' });\n\nwriteFileSync('private.pem', privPem);\nwriteFileSync('public.pem', pubPem);\nconsole.log('Schl\u00fcssel geschrieben.');\n\n\/\/ Wieder einlesen\nconst privGeladen = createPrivateKey(readFileSync('private.pem'));\nconst pubGeladen  = createPublicKey(readFileSync('public.pem'));\nconsole.log('Geladene Kurve:', privGeladen.asymmetricKeyDetails.namedCurve);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Ausgabe best\u00e4tigt das Schreiben und Laden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Schl\u00fcssel geschrieben.\nGeladene Kurve: prime256v1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Zur Terminologie: <code>pkcs8<\/code> ist das moderne Containerformat f\u00fcr private Schl\u00fcssel, <code>spki<\/code> das f\u00fcr \u00f6ffentliche. Diese Kombination ist 2026 die empfohlene Wahl und mit nahezu jeder anderen Krypto-Bibliothek kompatibel. Die erzeugte <code>private.pem<\/code> beginnt mit <code>-----BEGIN PRIVATE KEY-----<\/code>, die <code>public.pem<\/code> mit <code>-----BEGIN PUBLIC KEY-----<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"private-schluessel-niemals-im-klartext-ablegen\">Private Schl\u00fcssel niemals im Klartext ablegen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Eine unverschl\u00fcsselte <code>private.pem<\/code> auf der Festplatte ist ein Risiko. In Produktion verschl\u00fcsseln Sie den privaten Schl\u00fcssel mit einer Passphrase oder lagern ihn in einem Secret-Manager wie HashiCorp Vault, AWS KMS oder einem Hardware-Sicherheitsmodul aus. F\u00fcr die passphrasen-gesch\u00fctzte Variante erg\u00e4nzen Sie beim Export einfach eine <code>cipher<\/code>&#8211; und <code>passphrase<\/code>-Option. Wer einen verschl\u00fcsselten Datentr\u00e4ger als zus\u00e4tzliche Schutzschicht m\u00f6chte, findet praktische Schritte in unserem Tutorial zu <a href=\"\/de\/veracrypt-festplatte-verschluesseln\/\">VeraCrypt<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-6-mit-secp256k1-arbeiten-der-bitcoin-kurve\">Schritt 6: Mit secp256k1 arbeiten, der Bitcoin-Kurve<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bitcoin und Ethereum nutzen nicht P-256, sondern die Kurve secp256k1. Sie bietet ebenfalls rund 128 Bit Sicherheit, wurde aber mit besonders nachvollziehbaren Parametern entworfen, was Misstrauen gegen\u00fcber m\u00f6glichen Hintert\u00fcren reduziert. In Node ist sie ein einzeiliger Tausch.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ bitcoin-curve.js\nimport { generateKeyPairSync, sign, verify } from 'node:crypto';\n\nconst { publicKey, privateKey } = generateKeyPairSync('ec', {\n  namedCurve: 'secp256k1',   \/\/ die Bitcoin- und Ethereum-Kurve\n});\n\nconst tx = Buffer.from('send 0.05 BTC to bc1q...');\nconst sig = sign('sha256', tx, { key: privateKey, dsaEncoding: 'ieee-p1363' });\nconst ok  = verify('sha256', tx, { key: publicKey, dsaEncoding: 'ieee-p1363' }, sig);\n\nconsole.log('secp256k1-Signatur g\u00fcltig?', ok);   \/\/ true\nconsole.log('Signaturl\u00e4nge:', sig.length, 'Byte'); \/\/ 64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die Mechanik ist identisch zu P-256, nur die Kurve wechselt. In echten Wallets kommen jedoch zus\u00e4tzliche Konventionen hinzu: Bitcoin verlangt sogenannte Low-S-Signaturen (BIP 62), um Transaktions-Malleability zu verhindern, und nutzt double-SHA256 statt einfachem SHA-256. Das eingebaute crypto-Modul deckt die Kryptografie ab, die Blockchain-spezifischen Regeln implementieren spezialisierte Bibliotheken. Eine technische Referenz zur Kurve selbst bietet das <a href=\"https:\/\/en.bitcoin.it\/wiki\/Secp256k1\" target=\"_blank\" rel=\"noopener\">Bitcoin-Wiki zu secp256k1<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wer den privaten Schl\u00fcssel einer Kryptow\u00e4hrung verwaltet, sollte ihn niemals auf einem Server im Klartext halten. Hardware-Wallets isolieren das Schl\u00fcsselmaterial physisch. Der Unterschied zwischen den g\u00e4ngigen Ger\u00e4ten ist Thema unseres Vergleichs <a href=\"\/de\/ledger-vs-trezor\/\">Ledger vs. Trezor<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-7-das-nonce-problem-und-deterministische-signaturen\">Schritt 7: Das Nonce-Problem und deterministische Signaturen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Jetzt zur gef\u00e4hrlichsten Eigenschaft von ECDSA. Bei jeder Signatur w\u00e4hlt der Algorithmus eine geheime Zufallszahl k, die Nonce. Diese Zahl darf niemals zweimal mit demselben privaten Schl\u00fcssel verwendet werden, und sie darf nicht vorhersagbar sein. Versto\u00dfen Sie dagegen, l\u00e4sst sich der private Schl\u00fcssel aus zwei Signaturen mathematisch zur\u00fcckrechnen. Das ist kein theoretisches Risiko.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das bekannteste Beispiel ist die Sony PlayStation 3 aus dem Jahr 2010. Sony verwendete eine feste, konstante Nonce f\u00fcr alle ECDSA-Signaturen der Firmware. Angreifer extrahierten daraus den privaten Signierschl\u00fcssel und konnten beliebigen Code als offiziell signiert ausgeben. Der gesamte Schutzmechanismus der Konsole brach an einem einzigen Implementierungsfehler zusammen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die gute Nachricht: Node.js und OpenSSL erzeugen die Nonce mit einem kryptografisch sicheren Zufallsgenerator. Solange Sie das eingebaute <code>sign<\/code> nutzen und nicht selbst an der Nonce schrauben, sind Sie gesch\u00fctzt. Sie k\u00f6nnen die Robustheit zus\u00e4tzlich erh\u00f6hen, indem Sie auf deterministische ECDSA-Signaturen nach RFC 6979 setzen, die k aus dem Schl\u00fcssel und der Nachricht ableiten statt aus einem Zufallsgenerator.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Hinweis: Das eingebaute crypto-Modul nutzt zuf\u00e4llige Nonces.\n\/\/ F\u00fcr deterministische Signaturen nach RFC 6979 nutzen Sie\n\/\/ eine spezialisierte Bibliothek oder wechseln zu Ed25519,\n\/\/ das von Haus aus deterministisch arbeitet.\n\nimport { generateKeyPairSync, sign } from 'node:crypto';\n\nconst { privateKey } = generateKeyPairSync('ed25519');   \/\/ EdDSA, deterministisch\nconst msg = Buffer.from('keine Nonce-Sorgen');\nconst sig1 = sign(null, msg, privateKey);   \/\/ Hash-Algorithmus = null bei Ed25519\nconst sig2 = sign(null, msg, privateKey);\n\nconsole.log('Ed25519 deterministisch?', sig1.equals(sig2)); \/\/ true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Beachten Sie zwei Details im Ed25519-Beispiel: Der Hash-Parameter ist <code>null<\/code>, weil Ed25519 das Hashing selbst \u00fcbernimmt, und zwei Signaturen derselben Nachricht sind identisch. Diese Determiniertheit ist genau der Grund, warum viele neue Protokolle Ed25519 bevorzugen. Die formale Grundlage f\u00fcr deterministisches ECDSA beschreibt <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc6979\" target=\"_blank\" rel=\"noopener\">RFC 6979<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-8-ecdsa-in-json-web-tokens-es256\">Schritt 8: ECDSA in JSON Web Tokens (ES256)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die h\u00e4ufigste Begegnung mit ECDSA im Web ist der Algorithmus ES256: ECDSA mit P-256 und SHA-256, der genau das P1363-Format aus Schritt 3 nutzt. Wir bauen ein minimales, signiertes Token von Hand, um zu zeigen, dass kein Zauber dahintersteckt.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ es256.js\nimport { generateKeyPairSync, sign, verify } from 'node:crypto';\n\nconst { publicKey, privateKey } = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\n\nconst b64 = (obj) =&gt; Buffer.from(JSON.stringify(obj)).toString('base64url');\n\nconst header  = b64({ alg: 'ES256', typ: 'JWT' });\nconst payload = b64({ sub: 'user-42', role: 'admin', exp: 1781000000 });\nconst signingInput = `${header}.${payload}`;\n\nconst signature = sign('sha256', Buffer.from(signingInput), {\n  key: privateKey, dsaEncoding: 'ieee-p1363',\n}).toString('base64url');\n\nconst jwt = `${signingInput}.${signature}`;\nconsole.log('JWT:', jwt);\n\n\/\/ Verifikation\nconst [h, p, s] = jwt.split('.');\nconst ok = verify('sha256', Buffer.from(`${h}.${p}`), {\n  key: publicKey, dsaEncoding: 'ieee-p1363',\n}, Buffer.from(s, 'base64url'));\nconsole.log('Token g\u00fcltig?', ok);   \/\/ true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dieses Beispiel zeigt die Anatomie eines JWT: Header, Payload und Signatur, jeweils base64url-kodiert und mit Punkten getrennt. Der entscheidende Punkt ist <code>dsaEncoding: 'ieee-p1363'<\/code>. Mit dem Standard-DER-Format w\u00fcrde jede konforme JWT-Bibliothek das Token ablehnen, weil der ES256-Standard das feste 64-Byte-Format vorschreibt. Die formale Definition steht in <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc7518\" target=\"_blank\" rel=\"noopener\">RFC 7518 (JSON Web Algorithms)<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In der Praxis schreiben Sie JWTs nat\u00fcrlich nicht von Hand, sondern nutzen eine gepr\u00fcfte Bibliothek, die auch Ablauf, Audience und weitere Claims validiert. Das Handbeispiel dient dem Verst\u00e4ndnis. So sehen Sie, dass ein ES256-Token nichts weiter ist als eine ECDSA-Signatur \u00fcber zwei base64url-Strings.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-9-komplettprojekt-eine-signierte-rest-api\">Schritt 9: Komplettprojekt, eine signierte REST-API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Jetzt f\u00fcgen wir alles zu einem lauff\u00e4higen Mini-Server zusammen. Er stellt zwei Endpunkte bereit: <code>\/sign<\/code> erzeugt f\u00fcr eine Nachricht eine Signatur, <code>\/verify<\/code> pr\u00fcft sie. Wir nutzen nur den eingebauten HTTP-Server, also weiterhin keine Abh\u00e4ngigkeiten.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js\nimport { createServer } from 'node:http';\nimport { generateKeyPairSync, sign, verify } from 'node:crypto';\n\n\/\/ Schl\u00fcsselpaar einmalig beim Start (in Produktion aus Secret-Manager laden)\nconst { publicKey, privateKey } = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\nconst ENC = 'ieee-p1363';\n\nfunction readBody(req) {\n  return new Promise((resolve) =&gt; {\n    let data = '';\n    req.on('data', (c) =&gt; (data += c));\n    req.on('end', () =&gt; resolve(data));\n  });\n}\n\nconst server = createServer(async (req, res) =&gt; {\n  res.setHeader('Content-Type', 'application\/json');\n  try {\n    if (req.method === 'POST' &amp;&amp; req.url === '\/sign') {\n      const { message } = JSON.parse(await readBody(req));\n      if (typeof message !== 'string') throw new Error('message fehlt');\n      const sig = sign('sha256', Buffer.from(message), { key: privateKey, dsaEncoding: ENC });\n      res.end(JSON.stringify({ message, signature: sig.toString('base64url') }));\n      return;\n    }\n    if (req.method === 'POST' &amp;&amp; req.url === '\/verify') {\n      const { message, signature } = JSON.parse(await readBody(req));\n      const ok = verify('sha256', Buffer.from(message), { key: publicKey, dsaEncoding: ENC },\n                        Buffer.from(signature, 'base64url'));\n      res.statusCode = ok ? 200 : 401;\n      res.end(JSON.stringify({ valid: ok }));\n      return;\n    }\n    res.statusCode = 404;\n    res.end(JSON.stringify({ error: 'not found' }));\n  } catch (err) {\n    res.statusCode = 400;\n    res.end(JSON.stringify({ error: 'bad request' }));\n  }\n});\n\nserver.listen(3000, () =&gt; console.log('ECDSA-API l\u00e4uft auf http:\/\/localhost:3000'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Starten Sie den Server mit <code>node server.js<\/code> und testen Sie ihn in einem zweiten Terminal mit curl:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Signieren\ncurl -s -X POST http:\/\/localhost:3000\/sign \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\"message\":\"Hallo ECDSA\"}'\n# Antwort: {\"message\":\"Hallo ECDSA\",\"signature\":\"q8Fa...gekuerzt...\"}\n\n# Verifizieren (Signatur aus der Antwort oben einsetzen)\ncurl -s -X POST http:\/\/localhost:3000\/verify \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\"message\":\"Hallo ECDSA\",\"signature\":\"q8Fa...gekuerzt...\"}'\n# Antwort: {\"valid\":true}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Damit haben Sie eine vollst\u00e4ndige, lauff\u00e4hige ECDSA-Anwendung. Der Server h\u00e4lt den privaten Schl\u00fcssel, Clients erhalten nur Signaturen und pr\u00fcfen Nachrichten. Beachten Sie die Fehlerbehandlung: Jeder Ausnahmefall endet in einem generischen 400 oder einem 401 ohne Detailinformation. Diese Zur\u00fcckhaltung ist Absicht und geh\u00f6rt zu sicherer API-Gestaltung.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"den-oeffentlichen-schluessel-verteilen\">Den \u00f6ffentlichen Schl\u00fcssel verteilen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In einer echten Architektur w\u00fcrden Clients den \u00f6ffentlichen Schl\u00fcssel kennen, um Signaturen selbst zu pr\u00fcfen, ohne dem Server zu vertrauen. Erg\u00e4nzen Sie daf\u00fcr einen <code>GET \/pubkey<\/code>-Endpunkt, der <code>publicKey.export({ type: 'spki', format: 'pem' })<\/code> ausliefert. So verifiziert jeder Client lokal, was die Skalierbarkeit erh\u00f6ht und die Vertrauensbasis verkleinert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-10-automatisierte-tests-schreiben\">Schritt 10: Automatisierte Tests schreiben<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Krypto-Code ohne Tests ist riskant, weil Fehler oft still bleiben: Eine falsche Kodierung f\u00fchrt nicht zum Absturz, sondern liefert einfach immer <code>false<\/code>. Node 24 bringt einen eingebauten Test-Runner mit, sodass Sie keine Test-Bibliothek installieren m\u00fcssen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ test\/ecdsa.test.js\nimport { test } from 'node:test';\nimport assert from 'node:assert\/strict';\nimport { generateKeyPairSync, sign, verify } from 'node:crypto';\n\nconst ENC = 'ieee-p1363';\nconst keys = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\n\ntest('g\u00fcltige Signatur verifiziert als true', () =&gt; {\n  const msg = Buffer.from('test');\n  const sig = sign('sha256', msg, { key: keys.privateKey, dsaEncoding: ENC });\n  assert.equal(verify('sha256', msg, { key: keys.publicKey, dsaEncoding: ENC }, sig), true);\n});\n\ntest('manipulierte Nachricht verifiziert als false', () =&gt; {\n  const sig = sign('sha256', Buffer.from('test'), { key: keys.privateKey, dsaEncoding: ENC });\n  assert.equal(verify('sha256', Buffer.from('test!'), { key: keys.publicKey, dsaEncoding: ENC }, sig), false);\n});\n\ntest('falsches Encoding schl\u00e4gt fehl', () =&gt; {\n  const sig = sign('sha256', Buffer.from('test'), { key: keys.privateKey, dsaEncoding: 'der' });\n  assert.equal(verify('sha256', Buffer.from('test'), { key: keys.publicKey, dsaEncoding: ENC }, sig), false);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fchren Sie die Tests mit <code>node --test<\/code> aus. Die erwartete Ausgabe:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2714 g\u00fcltige Signatur verifiziert als true\n\u2714 manipulierte Nachricht verifiziert als false\n\u2714 falsches Encoding schl\u00e4gt fehl\n\u2139 tests 3\n\u2139 pass 3\n\u2139 fail 0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der dritte Test ist besonders lehrreich: Er beweist, dass eine DER-Signatur gegen einen P1363-Verifizierer scheitert, obwohl Schl\u00fcssel und Nachricht stimmen. Genau dieser Fall verursacht in der Praxis die meisten ratlosen Debug-Stunden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"fuenf-haeufige-fehler-und-wie-sie-sie-vermeiden\">F\u00fcnf h\u00e4ufige Fehler und wie Sie sie vermeiden<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Diese Stolperfallen tauchen immer wieder auf, wenn Entwickler ECDSA das erste Mal einsetzen. Wer sie kennt, spart sich Stunden.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Encoding verwechseln.<\/strong> DER beim Signieren, P1363 beim Verifizieren, oder umgekehrt. Das Ergebnis ist immer <code>false<\/code> ohne Fehlermeldung. Legen Sie die Kodierung als Konstante fest und nutzen Sie sie an beiden Stellen.<\/li><li><strong>Kurvennamen falsch schreiben.<\/strong> P-256 hei\u00dft in Node <code>prime256v1<\/code>, nicht <code>p-256<\/code> oder <code>secp256r1<\/code>. Ein falscher Name wirft sofort einen Fehler, was immerhin schnell auff\u00e4llt.<\/li><li><strong>Den privaten Schl\u00fcssel im Klartext committen.<\/strong> Eine <code>private.pem<\/code> im Git-Repository ist ein klassischer Vorfall. Tragen Sie sie in <code>.gitignore<\/code> ein und nutzen Sie Secret-Manager.<\/li><li><strong>Signatur und Nachricht entkoppeln.<\/strong> Wer eine Signatur pr\u00fcft, aber die zugeh\u00f6rige Nachricht aus einer anderen Quelle nimmt, \u00f6ffnet die T\u00fcr f\u00fcr Verwechslungsangriffe. Halten Sie beide untrennbar zusammen.<\/li><li><strong>Selbst an der Nonce schrauben.<\/strong> Jeder Versuch, k manuell zu setzen oder einen schwachen Zufallsgenerator zu verwenden, kann den privaten Schl\u00fcssel preisgeben. \u00dcberlassen Sie die Nonce immer dem crypto-Modul.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"troubleshooting-acht-typische-probleme\">Troubleshooting: acht typische Probleme<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Wenn etwas nicht funktioniert, hilft diese \u00dcbersicht der h\u00e4ufigsten Fehlermeldungen und ihrer Ursachen.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Symptom<\/th><th>Wahrscheinliche Ursache<\/th><th>L\u00f6sung<\/th><\/tr><\/thead><tbody><tr><td><code>verify<\/code> liefert immer <code>false<\/code><\/td><td>Unterschiedliche <code>dsaEncoding<\/code> bei Sign und Verify<\/td><td>Beide auf denselben Wert setzen (meist <code>ieee-p1363<\/code>)<\/td><\/tr><tr><td><code>Invalid EC curve name<\/code><\/td><td>Falscher Kurvenname<\/td><td><code>prime256v1<\/code>, <code>secp384r1<\/code> oder <code>secp256k1<\/code> verwenden<\/td><\/tr><tr><td><code>EXPECTING: PRIVATE KEY<\/code><\/td><td>PEM-Datei besch\u00e4digt oder falscher Typ<\/td><td>Datei auf vollst\u00e4ndige BEGIN\/END-Zeilen pr\u00fcfen<\/td><\/tr><tr><td>JWT wird von Bibliothek abgelehnt<\/td><td>DER- statt P1363-Format<\/td><td><code>dsaEncoding: 'ieee-p1363'<\/code> setzen<\/td><\/tr><tr><td><code>ERR_OSSL_UNSUPPORTED<\/code><\/td><td>Kurve unter Sicherheitslevel 2 verboten<\/td><td>Kurve mit mindestens 224 Bit w\u00e4hlen<\/td><\/tr><tr><td><code>Cannot use import statement<\/code><\/td><td><code>type: module<\/code> fehlt<\/td><td><code>npm pkg set type=module<\/code> ausf\u00fchren<\/td><\/tr><tr><td>Signatur mal 64, mal 72 Byte<\/td><td>Gemischte Encodings im Code<\/td><td>Eine einheitliche Konstante nutzen<\/td><\/tr><tr><td>Ed25519-Signatur scheitert<\/td><td>Hash-Algorithmus angegeben statt <code>null<\/code><\/td><td>Bei Ed25519 immer <code>sign(null, ...)<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Bei hartn\u00e4ckigen Problemen pr\u00fcfen Sie zuerst die OpenSSL-Version mit <code>node -p \"process.versions.openssl\"<\/code>. Verhalten und Fehlermeldungen unterscheiden sich teils zwischen OpenSSL 1.1 (alte Node-Versionen) und 3.x. Die <a href=\"https:\/\/www.openssl.org\/\" target=\"_blank\" rel=\"noopener\">offizielle OpenSSL-Seite<\/a> dokumentiert die Unterschiede der Sicherheitslevel im Detail.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"fortgeschrittene-tipps-fuer-den-produktivbetrieb\">Fortgeschrittene Tipps f\u00fcr den Produktivbetrieb<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Sobald Ihr ECDSA-Code die Lernphase verl\u00e4sst, kommen zus\u00e4tzliche Anforderungen ins Spiel. Diese Praktiken trennen ein Tutorial-Beispiel von produktionsreifer Software.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Asynchron signieren.<\/strong> Unter Last blockiert <code>generateKeyPairSync<\/code> die Event-Loop. Nutzen Sie <code>generateKeyPair<\/code> (callback oder Promise), damit der Server w\u00e4hrend der Schl\u00fcsselerzeugung weiter Anfragen bedient.<\/li><li><strong>Schl\u00fcsselrotation einplanen.<\/strong> Versehen Sie jeden Schl\u00fcssel mit einer Kennung (kid) und halten Sie alte \u00f6ffentliche Schl\u00fcssel f\u00fcr eine \u00dcbergangszeit g\u00fcltig, damit bereits ausgestellte Signaturen pr\u00fcfbar bleiben.<\/li><li><strong>Konstante-Zeit-Vergleiche.<\/strong> Wenn Sie selbst Bytes vergleichen, etwa Token-Bestandteile, nutzen Sie <code>crypto.timingSafeEqual<\/code> statt <code>===<\/code>, um Timing-Angriffe zu verhindern.<\/li><li><strong>Hardware-Schutz.<\/strong> F\u00fcr hochwertige Schl\u00fcssel lagern Sie das private Material in ein HSM oder einen Cloud-KMS aus. Der Server signiert dann \u00fcber eine API, sieht den geheimen Schl\u00fcssel aber nie.<\/li><li><strong>Eingaben begrenzen.<\/strong> Setzen Sie eine maximale Nachrichtengr\u00f6\u00dfe, bevor Sie signieren oder hashen, um Speicher-Ersch\u00f6pfung durch riesige Anfragen zu vermeiden.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Erg\u00e4nzend lohnt ein Blick auf die formalen Standards. Die aktuelle US-Norm f\u00fcr digitale Signaturen ist <a href=\"https:\/\/csrc.nist.gov\/pubs\/fips\/186-5\/final\" target=\"_blank\" rel=\"noopener\">FIPS 186-5<\/a>, die ECDSA und EdDSA gemeinsam spezifiziert. Sie ist die ma\u00dfgebliche Referenz, wenn Sie Compliance-Anforderungen erf\u00fcllen m\u00fcssen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ecdsa-und-die-post-quanten-frage\">ECDSA und die Post-Quanten-Frage<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Eine ehrliche Einordnung f\u00fcr 2026 geh\u00f6rt dazu: ECDSA ist nicht quantensicher. Ein ausreichend gro\u00dfer Quantencomputer k\u00f6nnte mit dem Shor-Algorithmus den privaten Schl\u00fcssel aus dem \u00f6ffentlichen berechnen und damit jede ECDSA-Signatur f\u00e4lschen. Solche Maschinen existieren heute nicht in der n\u00f6tigen Gr\u00f6\u00dfe, doch die Migration l\u00e4uft bereits an.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die NIST hat 2024 die ersten Post-Quanten-Standards finalisiert, darunter ML-DSA (basierend auf dem Verfahren Dilithium) als quantenresistenten Signaturalgorithmus. Der realistische Pfad f\u00fcr die n\u00e4chsten Jahre sind hybride Signaturen, die ECDSA und einen Post-Quanten-Algorithmus kombinieren. So bleibt die Verbindung sicher, selbst wenn eines der beiden Verfahren sp\u00e4ter f\u00e4llt. Einen breiteren Einstieg in das Thema bietet unsere <a href=\"\/de\/cryptography-hub\/\">\u00dcbersicht zu Hashing und Kryptographie<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr die Praxis 2026 hei\u00dft das: ECDSA ist f\u00fcr neue Projekte weiterhin sicher und Standard, aber bauen Sie Ihre Software so, dass sich der Signaturalgorithmus austauschen l\u00e4sst. Eine klar gekapselte Sign- und Verify-Schicht, wie das Komplettprojekt in Schritt 9 sie andeutet, macht den sp\u00e4teren Wechsel zu hybriden oder rein quantensicheren Verfahren zur \u00fcberschaubaren Aufgabe statt zum Gro\u00dfprojekt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-11-durchsatz-und-performance-messen\">Schritt 11: Durchsatz und Performance messen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bevor Sie ECDSA in einen Hochlast-Dienst einbauen, sollten Sie wissen, wie viele Operationen Ihre Hardware schafft. Ein einfacher Mikro-Benchmark mit der eingebauten <code>performance<\/code>-API liefert belastbare Zahlen f\u00fcr Ihre konkrete Maschine, statt sich auf fremde Messwerte zu verlassen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ bench.js\nimport { generateKeyPairSync, sign, verify } from 'node:crypto';\nimport { performance } from 'node:perf_hooks';\n\nconst { publicKey, privateKey } = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\nconst ENC = 'ieee-p1363';\nconst msg = Buffer.from('benchmark-nachricht');\nconst N = 10000;\n\n\/\/ Signieren messen\nlet sig;\nlet t0 = performance.now();\nfor (let i = 0; i &lt; N; i++) {\n  sig = sign('sha256', msg, { key: privateKey, dsaEncoding: ENC });\n}\nlet signMs = performance.now() - t0;\n\n\/\/ Verifizieren messen\nt0 = performance.now();\nfor (let i = 0; i &lt; N; i++) {\n  verify('sha256', msg, { key: publicKey, dsaEncoding: ENC }, sig);\n}\nlet verifyMs = performance.now() - t0;\n\nconsole.log(`Signieren:    ${Math.round(N \/ (signMs \/ 1000))} Ops\/s`);\nconsole.log(`Verifizieren: ${Math.round(N \/ (verifyMs \/ 1000))} Ops\/s`);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Die absoluten Zahlen h\u00e4ngen stark von CPU, Node-Version und OpenSSL-Build ab, weshalb Sie den Test auf Ihrer Zielhardware ausf\u00fchren sollten. Als grobe Orientierung gilt: Auf einer modernen Server-CPU liegen ECDSA-Signaturen mit P-256 typischerweise im Bereich von mehreren zehntausend Operationen pro Sekunde pro Kern. Das Verifizieren ist bei ECDSA etwas langsamer als das Signieren, anders als bei RSA, wo die Verifikation deutlich schneller l\u00e4uft als die Signatur. Wenn Ihr Lastprofil also von extrem h\u00e4ufiger Verifikation dominiert wird, kann RSA trotz gr\u00f6\u00dferer Schl\u00fcssel im Vorteil sein. F\u00fcr ausgewogene Sign- und Verify-Last bleibt ECDSA die effizientere Wahl.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ein praktischer Tipp f\u00fcr die Messung: Lassen Sie den Benchmark mehrere Sekunden laufen und verwerfen Sie die erste Runde. Die JIT-Optimierung von V8 braucht ein paar Iterationen, bis sie den hei\u00dfen Pfad kompiliert hat. Messen Sie zu fr\u00fch, erhalten Sie zu pessimistische Werte. Vergleichen Sie au\u00dferdem P-256 gegen secp256k1 und Ed25519, indem Sie nur die Schl\u00fcsselerzeugung tauschen. In den meisten Setups signiert Ed25519 sp\u00fcrbar schneller, was es f\u00fcr Systeme mit sehr hohem Signaturdurchsatz attraktiv macht.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"wie-ecdsa-den-tls-handshake-beschleunigt\">Wie ECDSA den TLS-Handshake beschleunigt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der h\u00e4ufigste produktive Einsatz von ECDSA spielt sich unsichtbar im TLS-Handshake ab, jedes Mal, wenn ein Browser eine HTTPS-Verbindung aufbaut. Der Server pr\u00e4sentiert sein Zertifikat und beweist mit einer ECDSA-Signatur, dass er den zugeh\u00f6rigen privaten Schl\u00fcssel besitzt. Weil ein ECDSA-Zertifikat mit P-256 nur einen Bruchteil der Gr\u00f6\u00dfe eines gleichwertigen RSA-3072-Zertifikats hat, sinkt die \u00fcbertragene Datenmenge im Handshake, und der rechenintensive Signaturschritt l\u00e4uft schneller. Auf mobilen Verbindungen mit hoher Latenz macht das einen messbaren Unterschied bei der Zeit bis zum ersten sichtbaren Inhalt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Moderne TLS-1.3-Verbindungen kombinieren ECDSA zur Authentifizierung mit ECDH zum Schl\u00fcsselaustausch. Beide rechnen auf elliptischen Kurven, nutzen aber unterschiedliche Schl\u00fcssel und Zwecke, was den in der FAQ erw\u00e4hnten Grundsatz der Zwecktrennung unterstreicht. Wer genauer verstehen will, was beim Verbindungsaufbau zwischen Browser und Server passiert und wie das Schloss-Symbol zustande kommt, findet die Details in unserem Beitrag zu <a href=\"\/de\/https-und-tls\/\">HTTPS und TLS<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr Betreiber bedeutet das eine konkrete Empfehlung: Stellen Sie nach M\u00f6glichkeit ECDSA-Zertifikate aus, idealerweise mit P-256, und halten Sie ein RSA-Zertifikat nur f\u00fcr sehr alte Clients als Fallback bereit. Viele \u00f6ffentliche Zertifizierungsstellen unterst\u00fctzen das parallele Ausstellen beider Typen. Der Server w\u00e4hlt dann je nach F\u00e4higkeiten des Clients automatisch das passende Zertifikat, sodass Sie die Geschwindigkeitsvorteile von ECDSA nutzen, ohne Kompatibilit\u00e4t zu opfern.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"haeufig-gestellte-fragen-zu-ecdsa-in-node-js\">H\u00e4ufig gestellte Fragen zu ECDSA in Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"brauche-ich-fuer-ecdsa-eine-externe-bibliothek\">Brauche ich f\u00fcr ECDSA eine externe Bibliothek?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Nein. Das eingebaute <code>node:crypto<\/code>-Modul deckt Schl\u00fcsselerzeugung, Signieren und Verifizieren f\u00fcr alle g\u00e4ngigen Kurven vollst\u00e4ndig ab. Externe Pakete brauchen Sie erst f\u00fcr spezialisierte Aufgaben wie deterministische RFC-6979-Signaturen, Bitcoin-spezifische Konventionen oder komfortable JWT-Verwaltung.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"welche-kurve-soll-ich-waehlen-p-256-oder-secp256k1\">Welche Kurve soll ich w\u00e4hlen, P-256 oder secp256k1?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr Web, TLS, JWT und allgemeine Backends nehmen Sie P-256 (<code>prime256v1<\/code>), den De-facto-Standard. secp256k1 brauchen Sie nur, wenn Sie mit Bitcoin, Ethereum oder anderen Blockchains interagieren, die genau diese Kurve verlangen. Beide bieten rund 128 Bit Sicherheit.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"warum-sind-zwei-signaturen-derselben-nachricht-unterschiedlich\">Warum sind zwei Signaturen derselben Nachricht unterschiedlich?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA verwendet pro Signatur eine geheime Zufallszahl (Nonce). Dadurch unterscheiden sich die Signaturen, obwohl beide g\u00fcltig sind. Das ist korrektes Verhalten. Wollen Sie identische Signaturen f\u00fcr identische Eingaben, nutzen Sie deterministisches ECDSA nach RFC 6979 oder wechseln zu Ed25519.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ist-ecdsa-sicherer-als-rsa\">Ist ECDSA sicherer als RSA?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Bei gleichem Sicherheitsniveau bietet ECDSA deutlich kleinere Schl\u00fcssel und schnellere Signaturen. Ein P-256-Schl\u00fcssel entspricht grob RSA-3072. RSA ist nicht unsicherer, nur ineffizienter bei vergleichbarer St\u00e4rke. Beide sind anf\u00e4llig f\u00fcr Quantencomputer und werden langfristig durch Post-Quanten-Verfahren erg\u00e4nzt.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"was-bedeutet-es256-in-einem-jwt\">Was bedeutet ES256 in einem JWT?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">ES256 bezeichnet ECDSA mit der Kurve P-256 und dem Hash SHA-256 im festen 64-Byte-Format (IEEE P1363). Es ist die ECDSA-Variante f\u00fcr JSON Web Tokens. In Node setzen Sie sie um, indem Sie <code>sign('sha256', ..., { dsaEncoding: 'ieee-p1363' })<\/code> mit einem prime256v1-Schl\u00fcssel verwenden.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kann-ich-denselben-schluessel-zum-verschluesseln-und-signieren-nutzen\">Kann ich denselben Schl\u00fcssel zum Verschl\u00fcsseln und Signieren nutzen?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Nein, und Sie sollten es auch nicht. ECDSA ist ein reines Signaturverfahren und kann nicht verschl\u00fcsseln. F\u00fcr Verschl\u00fcsselung auf elliptischen Kurven nutzen Sie ECDH zum Schl\u00fcsselaustausch und anschlie\u00dfend ein symmetrisches Verfahren. Trennen Sie Schl\u00fcssel grunds\u00e4tzlich nach Zweck.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"wie-schuetze-ich-den-privaten-schluessel-auf-dem-server\">Wie sch\u00fctze ich den privaten Schl\u00fcssel auf dem Server?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Speichern Sie ihn nie im Klartext neben dem Code. Nutzen Sie einen passphrasen-verschl\u00fcsselten PEM, besser noch einen Secret-Manager (Vault, AWS KMS) oder ein Hardware-Sicherheitsmodul, das nur Signaturen \u00fcber eine API ausgibt und das Schl\u00fcsselmaterial nie herausr\u00fcckt.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"related-coverage\">Related Coverage<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/de\/digitale-signaturen\/\">Digitale Signaturen erkl\u00e4rt: So funktionieren sie<\/a><\/li><li><a href=\"\/de\/ssh-key-erstellen-ed25519-2026\/\">SSH-Key erstellen: Ed25519 in 8 Schritten<\/a><\/li><li><a href=\"\/de\/sha-256\/\">SHA-256 erkl\u00e4rt: So funktioniert es<\/a><\/li><li><a href=\"\/de\/https-und-tls\/\">HTTPS und TLS: Wie das Schloss im Browser Sie sch\u00fctzt<\/a><\/li><li><a href=\"\/de\/gpg-verschluesselung-gnupg\/\">GPG-Verschl\u00fcsselung mit GnuPG in 12 Schritten<\/a><\/li><li><a href=\"\/de\/ledger-vs-trezor\/\">Ledger vs. Trezor: Hardware-Wallets im Vergleich<\/a><\/li><li><a href=\"\/de\/cryptography-hub\/\">Hashing und Kryptographie verst\u00e4ndlich erkl\u00e4rt<\/a><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Letzte Aktualisierung: 12. Juni 2026. Alle Code-Beispiele wurden mit Node.js 24 LTS und OpenSSL 3.5 konzipiert. Die genannten Algorithmen und Standards entsprechen dem Stand von 2026.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Digitale Signaturen halten das halbe Internet zusammen. Jedes TLS-Zertifikat, jedes signierte JSON Web Token, jede Bitcoin-Transaktion und jeder SSH-Login st\u00fctzt sich auf dieselbe Idee: Ein privater Schl\u00fcssel erzeugt eine Signatur,\u2026<\/p>\n","protected":false},"author":2,"featured_media":87,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-86","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cryptography"],"_links":{"self":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts\/86","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/comments?post=86"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts\/86\/revisions"}],"predecessor-version":[{"id":88,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/posts\/86\/revisions\/88"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/media\/87"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/media?parent=86"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/categories?post=86"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/de\/wp-json\/wp\/v2\/tags?post=86"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}