{"id":159,"date":"2026-06-20T20:22:52","date_gmt":"2026-06-20T20:22:52","guid":{"rendered":"https:\/\/shattered.io\/at\/2026\/06\/20\/ecc-elliptic-curve-nodejs\/"},"modified":"2026-06-24T23:46:18","modified_gmt":"2026-06-24T23:46:18","slug":"ecc-elliptic-curve-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/at\/ecc-elliptic-curve-nodejs\/","title":{"rendered":"Elliptic Curve Cryptography in Node.js: ECDH und ECDSA in 12 Schritten [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Elliptic Curve Cryptography (ECC) liefert bei gleichem Sicherheitsniveau deutlich kleinere Schl\u00fcssel als RSA: Ein 256-Bit-ECC-Schl\u00fcssel entspricht einem 3072-Bit-RSA-Schl\u00fcssel. Node.js ab Version 18 unterst\u00fctzt ECC vollst\u00e4ndig \u00fcber das native <code>crypto<\/code>-Modul, ohne externe Abh\u00e4ngigkeiten. Dieses Tutorial zeigt Schritt f\u00fcr Schritt, wie du ECDH-Schl\u00fcsselaustausch, ECDSA-Signaturen und Ed25519 in 12 Schritten implementierst, mit ausf\u00fchrbarem Code f\u00fcr jede Technik.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"was-ist-elliptic-curve-cryptography\">Was ist Elliptic Curve Cryptography?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ECC basiert auf der algebraischen Struktur elliptischer Kurven \u00fcber endlichen K\u00f6rpern. Die Sicherheit beruht auf dem <strong>Elliptic Curve Discrete Logarithm Problem (ECDLP)<\/strong>: Gegeben zwei Punkte P und Q auf einer Kurve mit Q = k\u00b7P, ist k praktisch nicht berechenbar, solange k gro\u00df genug ist. Klassische Computer ben\u00f6tigen f\u00fcr P-256 nach aktuellem Stand mehr Energie als im Sonnensystem vorhanden ist, um k zu ermitteln.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In der Praxis setzt die Industrie auf drei ECC-Anwendungsf\u00e4lle:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>ECDH (Elliptic Curve Diffie-Hellman):<\/strong> Schl\u00fcsselaustausch, um einen gemeinsamen Geheimschl\u00fcssel \u00fcber einen unsicheren Kanal zu etablieren, ohne ihn jemals direkt zu \u00fcbertragen.<\/li>\n<li><strong>ECDSA (Elliptic Curve Digital Signature Algorithm):<\/strong> Digitale Signaturen zum Nachweis der Authentizit\u00e4t und Integrit\u00e4t von Daten.<\/li>\n<li><strong>Ed25519 \/ EdDSA:<\/strong> Schnellere und sicherere Signaturvariante auf Basis der Edwards-Kurve, bevorzugt f\u00fcr SSH, TLS 1.3 und moderne Protokolle.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Das NIST empfiehlt in FIPS 186-5 (2023) ausdr\u00fccklich P-256 und P-384 f\u00fcr neue Systeme. TLS 1.3 nutzt standardm\u00e4\u00dfig ECDHE mit P-256 oder X25519 f\u00fcr den Schl\u00fcsselaustausch. Die Node.js-Implementierung ruft dabei intern OpenSSL auf, das seit Version 3.0 alle relevanten Kurven unterst\u00fctzt und nach <a href=\"https:\/\/csrc.nist.gov\/projects\/cryptographic-standards-and-guidelines\" target=\"_blank\" rel=\"noopener noreferrer\">FIPS 140-3<\/a> validiert ist.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"voraussetzungen\">Voraussetzungen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Stelle vor dem Start sicher, dass alle Werkzeuge in den richtigen Versionen vorhanden sind. ECC \u00fcber das native <code>crypto<\/code>-Modul erfordert mindestens Node.js 18, da <code>generateKeyPairSync<\/code> mit EC-Parametern und die vollst\u00e4ndige <code>hkdfSync<\/code>-API ab dieser Version stabil sind.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Werkzeug<\/th><th>Mindestversion<\/th><th>Empfohlene Version<\/th><th>Zweck<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>18.0.0<\/td><td>22.x LTS<\/td><td>Laufzeit mit nativem ECC-Support<\/td><\/tr><tr><td>npm<\/td><td>9.0.0<\/td><td>10.x<\/td><td>Paketverwaltung<\/td><\/tr><tr><td>OpenSSL<\/td><td>3.0<\/td><td>3.3<\/td><td>Hintergrund-Bibliothek (Teil von Node.js)<\/td><\/tr><tr><td>TypeScript (optional)<\/td><td>5.0<\/td><td>5.4<\/td><td>Typsicherheit<\/td><\/tr><tr><td>OS<\/td><td>Ubuntu 22.04 \/ macOS 12 \/ Windows 10<\/td><td>Ubuntu 24.04<\/td><td>Alle Betriebssysteme unterst\u00fctzt<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Installierte Version pr\u00fcfen und ECC-Unterst\u00fctzung best\u00e4tigen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node --version   # Ausgabe: v22.x.x\nnpm --version    # Ausgabe: 10.x.x\n\n# ECC-Kurvenunterst\u00fctzung pr\u00fcfen\nnode -e \"const c = require('crypto'); console.log('P-256:', c.getCurves().includes('prime256v1')); console.log('P-384:', c.getCurves().includes('secp384r1')); console.log('Ed25519:', c.generateKeyPairSync('ed25519') && true)\"\n\n# Erwartete Ausgabe:\n# P-256: true\n# P-384: true\n# Ed25519: true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Keine externen Pakete notwendig. Alle Codebeispiele in diesem Tutorial laufen ausschlie\u00dflich mit dem Node.js-Standard-Modul <code>crypto<\/code> und dem eingebauten <code>fs<\/code>-Modul.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-1-projektstruktur-anlegen\">Schritt 1: Projektstruktur anlegen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Erstelle ein sauberes Projektverzeichnis mit klarer Trennung zwischen Schl\u00fcsseln, Signaturen und Anwendungscode. Diese Struktur verhindert, dass private Schl\u00fcssel versehentlich in das Repository gelangen. Der erste Schritt ist eine verbindliche <code>.gitignore<\/code>-Datei, die alle Schl\u00fcsseldateien ausschlie\u00dft.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir ecc-nodejs-tutorial && cd ecc-nodejs-tutorial\nnpm init -y\n\n# Verzeichnisstruktur\nmkdir -p keys signatures messages\n\n# .gitignore f\u00fcr Schl\u00fcssel ZUERST anlegen\ncat &lt;&lt; 'EOF' &gt; .gitignore\nkeys\/\n*.pem\n*.der\n*.p8\n*.p12\n*.pfx\nnode_modules\/\n.env\nEOF\n\necho \"Projektstruktur bereit\"\nls -la<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Erstelle das Hauptmodul mit gemeinsamen Hilfsfunktionen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ecc.js - Basismodul f\u00fcr alle ECC-Operationen\n'use strict';\n\nconst crypto = require('crypto');\nconst fs     = require('fs');\nconst path   = require('path');\n\nconst KEYS_DIR       = path.join(__dirname, 'keys');\nconst SIGNATURES_DIR = path.join(__dirname, 'signatures');\n\n\/\/ Verzeichnisse erstellen falls nicht vorhanden\n[KEYS_DIR, SIGNATURES_DIR].forEach(dir => {\n  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n});\n\n\/\/ Erlaubte Kurven (Whitelist)\nconst ALLOWED_CURVES = new Set(['prime256v1', 'secp384r1', 'secp521r1']);\n\nfunction validateCurve(namedCurve) {\n  if (!ALLOWED_CURVES.has(namedCurve)) {\n    throw new Error(`Kurve nicht erlaubt: ${namedCurve}. Verwende: ${[...ALLOWED_CURVES].join(', ')}`);\n  }\n}\n\nmodule.exports = { crypto, fs, path, KEYS_DIR, SIGNATURES_DIR, validateCurve };<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-2-ec-schluesselpaar-generieren\">Schritt 2: EC-Schl\u00fcsselpaar generieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js unterst\u00fctzt alle wichtigen NIST-Kurven sowie die sichere Edwards-Kurve f\u00fcr Ed25519. Die Auswahl der Kurve bestimmt Sicherheitsniveau und Performance. F\u00fcr den Produktionseinsatz empfiehlt das BSI \u00f6sterreichischen Beh\u00f6rden P-256 oder P-384 als bevorzugte asymmetrische Verfahren.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"unterstuetzte-kurven-im-vergleich\">Unterst\u00fctzte Kurven im Vergleich<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Kurvenname<\/th><th>Alias<\/th><th>Sicherheit<\/th><th>Schl\u00fcssell\u00e4nge<\/th><th>Typischer Einsatz<\/th><\/tr><\/thead><tbody><tr><td>prime256v1<\/td><td>P-256, secp256r1<\/td><td>128 Bit<\/td><td>256 Bit<\/td><td>TLS, JWT ES256, FIDO2\/WebAuthn<\/td><\/tr><tr><td>secp384r1<\/td><td>P-384<\/td><td>192 Bit<\/td><td>384 Bit<\/td><td>NSA Suite B, Beh\u00f6rden<\/td><\/tr><tr><td>secp521r1<\/td><td>P-521<\/td><td>256 Bit<\/td><td>521 Bit<\/td><td>H\u00f6chste Sicherheitsanforderungen<\/td><\/tr><tr><td>secp256k1<\/td><td>K-256<\/td><td>128 Bit<\/td><td>256 Bit<\/td><td>Bitcoin, Ethereum<\/td><\/tr><tr><td>ed25519<\/td><td>Edwards-25519<\/td><td>128 Bit<\/td><td>256 Bit<\/td><td>SSH, GPG, Signal-Protokoll<\/td><\/tr><tr><td>ed448<\/td><td>Edwards-448<\/td><td>224 Bit<\/td><td>448 Bit<\/td><td>Langzeitsicherheit, GPG<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Die Funktion zur Schl\u00fcsselgenerierung legt Schl\u00fcsselpaare direkt als PEM-Dateien ab und setzt die korrekten Dateirechte:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ generate-keys.js\n'use strict';\n\nconst crypto = require('crypto');\nconst fs     = require('fs');\nconst path   = require('path');\n\n\/**\n * Generiert ein EC-Schl\u00fcsselpaar und speichert es als PEM-Dateien.\n * @param {string} namedCurve - z.B. 'prime256v1', 'secp384r1', 'secp521r1'\n * @param {string} label      - Dateinamenpr\u00e4fix\n * @returns {{ privateKey: string, publicKey: string }}\n *\/\nfunction generateECKeyPair(namedCurve = 'prime256v1', label = 'ec') {\n  const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {\n    namedCurve,\n    publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }\n  });\n\n  const privPath = path.join('keys', `${label}-private.pem`);\n  const pubPath  = path.join('keys', `${label}-public.pem`);\n\n  \/\/ Privater Schl\u00fcssel: nur f\u00fcr Owner lesbar (Unix 600)\n  fs.writeFileSync(privPath, privateKey,  { mode: 0o600 });\n  fs.writeFileSync(pubPath,  publicKey);\n\n  const privKeyObj = crypto.createPrivateKey(privateKey);\n  const curve      = privKeyObj.asymmetricKeyDetails?.namedCurve;\n\n  console.log(`[OK] Schl\u00fcsselpaar (${curve}) generiert:`);\n  console.log(`     Privat:  ${privPath}`);\n  console.log(`     \u00d6ffentl: ${pubPath}`);\n\n  return { privateKey, publicKey };\n}\n\n\/\/ Drei Kurventypen demonstrieren\ngenerateECKeyPair('prime256v1', 'alice-p256');\ngenerateECKeyPair('secp384r1',  'alice-p384');\ngenerateECKeyPair('prime256v1', 'bob-p256');\n\n\/* Erwartete Ausgabe:\n   [OK] Schl\u00fcsselpaar (prime256v1) generiert:\n        Privat:  keys\/alice-p256-private.pem\n        \u00d6ffentl: keys\/alice-p256-public.pem\n   [OK] Schl\u00fcsselpaar (secp384r1) generiert:\n        Privat:  keys\/alice-p384-private.pem\n        \u00d6ffentl: keys\/alice-p384-public.pem\n   [OK] Schl\u00fcsselpaar (prime256v1) generiert:\n        Privat:  keys\/bob-p256-private.pem\n        \u00d6ffentl: keys\/bob-p256-public.pem\n*\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der Dateirechte-Modus <code>0o600<\/code> stellt auf Unix-Systemen sicher, dass der private Schl\u00fcssel nur vom erzeugenden Prozess gelesen werden kann. Unter Windows fehlt diese Kontrolle, weshalb dort DPAPI-Schutz oder ein Hardware-Sicherheitsmodul (HSM) empfohlen wird.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-3-schluessel-in-pem-und-der-exportieren\">Schritt 3: Schl\u00fcssel in PEM und DER exportieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PEM ist Base64-kodiertes DER mit ASCII-Kopfzeilen, geeignet f\u00fcr Textdateien und Konfigurationen. DER ist das rohe Bin\u00e4rformat, effizienter f\u00fcr Datenbankenspeicherung und eingebettete Systeme. Ein P-256-Schl\u00fcssel belegt im DER-Format 138 Bytes, ein \u00e4quivalenter RSA-3072-Schl\u00fcssel \u00fcber 1.700 Bytes. Node.js unterst\u00fctzt beide Formate nativ und erm\u00f6glicht die Konvertierung ohne externe Tools.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ key-export.js\n'use strict';\n\nconst crypto = require('crypto');\nconst fs     = require('fs');\n\nfunction demonstrateKeyFormats(namedCurve = 'prime256v1') {\n  const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {\n    namedCurve,\n    publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }\n  });\n\n  \/\/ PEM speichern\n  fs.writeFileSync('keys\/key.pem', privateKey, { mode: 0o600 });\n\n  \/\/ DER exportieren\n  const privKeyObj = crypto.createPrivateKey(privateKey);\n  const derPriv    = privKeyObj.export({ type: 'pkcs8', format: 'der' });\n  fs.writeFileSync('keys\/key.der', derPriv);\n\n  \/\/ Gr\u00f6\u00dfenvergleich\n  console.log(`Kurve: ${namedCurve}`);\n  console.log(`DER-Gr\u00f6\u00dfe:  ${derPriv.length} Bytes`);\n  console.log(`PEM-Gr\u00f6\u00dfe:  ${Buffer.byteLength(privateKey, 'utf8')} Bytes`);\n\n  \/\/ Schl\u00fcsseldetails\n  const details = privKeyObj.asymmetricKeyDetails;\n  console.log(`Kurvenname: ${details.namedCurve}`);\n\n  \/\/ PEM-Inhalt (\u00f6ffentlicher Schl\u00fcssel)\n  console.log('\\n--- \u00d6ffentlicher Schl\u00fcssel (PEM) ---');\n  console.log(publicKey);\n\n  return { privateKey, publicKey, derPriv };\n}\n\ndemonstrateKeyFormats('prime256v1');\n\n\/* Erwartete Ausgabe:\n   Kurve: prime256v1\n   DER-Gr\u00f6\u00dfe:  138 Bytes\n   PEM-Gr\u00f6\u00dfe:  227 Bytes\n   Kurvenname: prime256v1\n\n   --- \u00d6ffentlicher Schl\u00fcssel (PEM) ---\n   -----BEGIN PUBLIC KEY-----\n   MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...\n   -----END PUBLIC KEY-----\n*\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-4-schluessel-aus-datei-laden-und-validieren\">Schritt 4: Schl\u00fcssel aus Datei laden und validieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Beim Laden von Schl\u00fcsseln aus dem Dateisystem ist robuste Fehlerbehandlung entscheidend. Fehlende oder korrumpierte Schl\u00fcssel d\u00fcrfen die Anwendung nicht mit einer unbehandelten Exception beenden. Der geladene Schl\u00fcssel muss auf Typ und Kurve gepr\u00fcft werden, bevor er in kryptografischen Operationen eingesetzt wird.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ key-loader.js\n'use strict';\n\nconst crypto = require('crypto');\nconst fs     = require('fs');\n\nfunction loadPrivateKey(pemPath, allowedCurves = ['prime256v1', 'secp384r1', 'secp521r1']) {\n  if (!fs.existsSync(pemPath)) {\n    throw new Error(`Privater Schl\u00fcssel nicht gefunden: ${pemPath}`);\n  }\n\n  const pem    = fs.readFileSync(pemPath, 'utf8');\n  const keyObj = crypto.createPrivateKey(pem);\n\n  if (keyObj.asymmetricKeyType !== 'ec') {\n    throw new Error(`Erwartet EC-Schl\u00fcssel, erhalten: ${keyObj.asymmetricKeyType}`);\n  }\n\n  const curve = keyObj.asymmetricKeyDetails?.namedCurve;\n  if (!allowedCurves.includes(curve)) {\n    throw new Error(`Kurve nicht erlaubt: ${curve}`);\n  }\n\n  console.log(`[OK] Privater Schl\u00fcssel geladen: ${curve}`);\n  return keyObj;\n}\n\nfunction loadPublicKey(pemPath) {\n  if (!fs.existsSync(pemPath)) {\n    throw new Error(`\u00d6ffentlicher Schl\u00fcssel nicht gefunden: ${pemPath}`);\n  }\n\n  const pem    = fs.readFileSync(pemPath, 'utf8');\n  const keyObj = crypto.createPublicKey(pem);\n\n  console.log(`[OK] \u00d6ffentlicher Schl\u00fcssel: ${keyObj.asymmetricKeyType}, ${keyObj.asymmetricKeyDetails?.namedCurve}`);\n  return keyObj;\n}\n\n\/\/ Verwendung mit Fehlerbehandlung\ntry {\n  const priv = loadPrivateKey('keys\/alice-p256-private.pem');\n  const pub  = loadPublicKey('keys\/alice-p256-public.pem');\n  console.log('Schl\u00fcssel bereit f\u00fcr kryptografische Operationen.');\n} catch (err) {\n  console.error(`[FEHLER] ${err.message}`);\n  process.exit(1);\n}\n\n\/* Erwartete Ausgabe:\n   [OK] Privater Schl\u00fcssel geladen: prime256v1\n   [OK] \u00d6ffentlicher Schl\u00fcssel: ec, prime256v1\n   Schl\u00fcssel bereit f\u00fcr kryptografische Operationen.\n*\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-5-ecdh-schluesselaustausch-implementieren\">Schritt 5: ECDH-Schl\u00fcsselaustausch implementieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ECDH erm\u00f6glicht es zwei Parteien, einen gemeinsamen Geheimschl\u00fcssel zu berechnen, ohne ihn jemals \u00fcber das Netzwerk zu senden. Jede Seite generiert ein Schl\u00fcsselpaar, tauscht den \u00f6ffentlichen Schl\u00fcssel aus, und berechnet lokal dasselbe Shared Secret. Wenn f\u00fcr jede Sitzung neue Schl\u00fcsselpaare generiert werden (ephemere Schl\u00fcssel), entsteht Forward Secrecy: Vergangene Sitzungen bleiben sicher, selbst wenn ein Langzeitschl\u00fcssel kompromittiert wird.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der Ablauf in vier Schritten:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Alice generiert Schl\u00fcsselpaar (privA, pubA), Bob generiert (privB, pubB).<\/li>\n<li>Alice sendet pubA an Bob, Bob sendet pubB an Alice.<\/li>\n<li>Alice berechnet sharedSecret = ECDH(privA, pubB).<\/li>\n<li>Bob berechnet sharedSecret = ECDH(privB, pubA). Beide erhalten denselben Wert.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ecdh-exchange.js\n'use strict';\n\nconst crypto = require('crypto');\n\nfunction demonstrateECDH(namedCurve = 'prime256v1') {\n  console.log(`\\n=== ECDH Schl\u00fcsselaustausch (${namedCurve}) ===`);\n\n  \/\/ Schritt 1: Beide Parteien generieren ephemere Schl\u00fcsselpaare\n  const { privateKey: alicePriv, publicKey: alicePub } =\n    crypto.generateKeyPairSync('ec', { namedCurve });\n  const { privateKey: bobPriv, publicKey: bobPub } =\n    crypto.generateKeyPairSync('ec', { namedCurve });\n\n  \/\/ Schritt 2: Shared Secret berechnen\n  \/\/ Alice nutzt ihren privaten + Bobs \u00f6ffentlichen Schl\u00fcssel\n  const aliceSecret = crypto.diffieHellman({\n    privateKey: alicePriv,\n    publicKey:  bobPub\n  });\n\n  \/\/ Bob nutzt seinen privaten + Alices \u00f6ffentlichen Schl\u00fcssel\n  const bobSecret = crypto.diffieHellman({\n    privateKey: bobPriv,\n    publicKey:  alicePub\n  });\n\n  \/\/ Schritt 3: Beide erhalten dasselbe Ergebnis\n  const match = aliceSecret.equals(bobSecret);\n  console.log(`Secret (Alice): ${aliceSecret.toString('hex').substring(0, 32)}...`);\n  console.log(`Secret (Bob):   ${bobSecret.toString('hex').substring(0, 32)}...`);\n  console.log(`Identisch:      ${match}`);\n  console.log(`L\u00e4nge:          ${aliceSecret.length} Bytes`);\n\n  if (!match) throw new Error('ECDH-Fehler: Secrets stimmen nicht \u00fcberein!');\n  return aliceSecret;\n}\n\ndemonstrateECDH('prime256v1'); \/\/ 32 Bytes Secret\ndemonstrateECDH('secp384r1');  \/\/ 48 Bytes Secret\ndemonstrateECDH('secp521r1');  \/\/ 66 Bytes Secret\n\n\/* Erwartete Ausgabe:\n   === ECDH Schl\u00fcsselaustausch (prime256v1) ===\n   Secret (Alice): 8f3a2b1c9d7e4f6a0b3c5d2e1f8a9b0c...\n   Secret (Bob):   8f3a2b1c9d7e4f6a0b3c5d2e1f8a9b0c...\n   Identisch:      true\n   L\u00e4nge:          32 Bytes\n   ...\n*\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das Raw-ECDH-Secret darf <strong>niemals direkt als Verschl\u00fcsselungsschl\u00fcssel verwendet werden<\/strong>. Es hat keine gleichm\u00e4\u00dfige Verteilung und muss zuerst durch eine Key Derivation Function (KDF) verarbeitet werden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-6-aes-sitzungsschluessel-aus-ecdh-ableiten\">Schritt 6: AES-Sitzungsschl\u00fcssel aus ECDH ableiten<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HKDF (HMAC-based Key Derivation Function, RFC 5869) ist die korrekte Methode, um aus dem ECDH-Secret einen oder mehrere Anwendungsschl\u00fcssel abzuleiten. Node.js 15+ implementiert HKDF nativ via <code>crypto.hkdfSync()<\/code>. Der Salt-Wert (zuf\u00e4llig pro Sitzung) und der Info-Parameter (kontextgebunden) binden den abgeleiteten Schl\u00fcssel an eine spezifische Sitzung, sodass zwei Sitzungen mit demselben ECDH-Schl\u00fcsselpaar verschiedene AES-Schl\u00fcssel erhalten.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ key-derivation.js\n'use strict';\n\nconst crypto = require('crypto');\n\n\/**\n * Leitet aus einem ECDH-Secret einen AES-256-GCM-Schl\u00fcssel ab.\n * @param {Buffer} sharedSecret - Raw ECDH-Ausgabe\n * @param {string} context      - Anwendungskontext (z.B. Sitzungs-ID)\n * @returns {{ aesKey: Buffer, iv: Buffer, salt: Buffer }}\n *\/\nfunction deriveSessionKey(sharedSecret, context = 'ecc-tutorial-v1') {\n  \/\/ Salt: zuf\u00e4llig pro Sitzung, muss dem Empf\u00e4nger mitgeteilt werden\n  const salt = crypto.randomBytes(32);\n\n  \/\/ HKDF: Extract (aus Secret + Salt) + Expand (mit Info-Parameter)\n  const aesKeyRaw = crypto.hkdfSync(\n    'sha256',          \/\/ Hash-Algorithmus\n    sharedSecret,      \/\/ Input Key Material\n    salt,              \/\/ Salt (zuf\u00e4llig)\n    Buffer.from(context, 'utf8'), \/\/ Info: bindet Schl\u00fcssel an Kontext\n    32                 \/\/ 32 Bytes = AES-256\n  );\n  const aesKey = Buffer.from(aesKeyRaw);\n  const iv     = crypto.randomBytes(12); \/\/ 96 Bit f\u00fcr AES-GCM\n\n  console.log(`AES-Schl\u00fcssel: ${aesKey.toString('hex').substring(0, 16)}...`);\n  console.log(`IV:            ${iv.toString('hex')}`);\n  return { aesKey, iv, salt };\n}\n\n\/\/ Demonstration: ECDH + HKDF + AES-GCM\nconst { privateKey: alicePriv, publicKey: alicePub } =\n  crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\nconst { privateKey: bobPriv, publicKey: bobPub } =\n  crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\n\nconst sharedSecret       = crypto.diffieHellman({ privateKey: alicePriv, publicKey: bobPub });\nconst { aesKey, iv }     = deriveSessionKey(sharedSecret, 'chat-session-2026');\n\n\/\/ Nachricht verschl\u00fcsseln\nconst plaintext = 'Vertrauliche Nachricht von Alice an Bob';\nconst cipher    = crypto.createCipheriv('aes-256-gcm', aesKey, iv);\nconst encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\nconst authTag   = cipher.getAuthTag();\n\nconsole.log(`Verschl\u00fcsselt: ${encrypted.toString('hex')}`);\nconsole.log(`Auth Tag:      ${authTag.toString('hex')}`);\n\n\/\/ Entschl\u00fcsseln (Bob-Seite)\nconst decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv);\ndecipher.setAuthTag(authTag);\nconst decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);\nconsole.log(`Entschl\u00fcsselt: ${decrypted.toString('utf8')}`);\n\n\/* Erwartete Ausgabe:\n   AES-Schl\u00fcssel: 3f8c2a1b9d7e4f6a...\n   IV:            a1b2c3d4e5f67890a1b2c3d4\n   Verschl\u00fcsselt: 8f3a2b1c9d7e4f6a...\n   Auth Tag:      2b4f8c1a3d7e9f0c2b4f8c1a3d7e9f0c\n   Entschl\u00fcsselt: Vertrauliche Nachricht von Alice an Bob\n*\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-7-daten-mit-ecdsa-signieren\">Schritt 7: Daten mit ECDSA signieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA erzeugt eine digitale Signatur, die beweist, dass ein Datensatz vom Inhaber des privaten Schl\u00fcssels erstellt wurde. Die Signatur besteht aus zwei Werten (r, s), die Node.js standardm\u00e4\u00dfig als DER-Struktur kodiert. Das IEEE P1363-Format (feste L\u00e4nge) wird in JWT-Implementierungen verwendet und ist \u00fcber den <code>dsaEncoding<\/code>-Parameter w\u00e4hlbar.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ecdsa-sign.js\n'use strict';\n\nconst crypto = require('crypto');\nconst fs     = require('fs');\n\nfunction signDocument(document, privateKeyPem) {\n  const sign = crypto.createSign('sha256'); \/\/ P-256 + SHA-256 = ES256\n  sign.update(document);\n  sign.end();\n\n  \/\/ DER-Format (Standard, kompatibel mit OpenSSL)\n  const signature = sign.sign({ key: privateKeyPem, dsaEncoding: 'der' });\n\n  console.log(`Dokument:  \"${document.substring(0, 50)}\"`);\n  console.log(`Signatur:  ${signature.toString('hex').substring(0, 32)}...`);\n  console.log(`L\u00e4nge:     ${signature.length} Bytes`);\n\n  return signature;\n}\n\n\/\/ Schl\u00fcsselpaar\nconst { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {\n  namedCurve: 'prime256v1',\n  publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }\n});\n\n\/\/ Dokument signieren (immer den zu verifizierenden Inhalt signieren, nicht einen Hash davon)\nconst document = JSON.stringify({\n  action:    '\u00dcberweisung',\n  betrag:    '1.500,00 EUR',\n  von:       'alice@example.at',\n  an:        'bob@example.at',\n  timestamp: '2026-06-20T10:00:00Z'\n});\n\nconst signature = signDocument(document, privateKey);\n\nfs.writeFileSync('signatures\/dokument.sig', signature);\nfs.writeFileSync('keys\/signierer-public.pem', publicKey);\nfs.writeFileSync('messages\/dokument.json', document);\n\nconsole.log('\\nSignatur, Schl\u00fcssel und Dokument gespeichert.');\n\n\/* Erwartete Ausgabe:\n   Dokument:  \"{\"action\":\"\u00dcberweisung\",\"betrag\":\"1.500,00 EUR\",\"vo\"\n   Signatur:  3045022100f3a2b1c9d7e4f6a0b3c5d2e1f8...\n   L\u00e4nge:     71 Bytes\n   Signatur, Schl\u00fcssel und Dokument gespeichert.\n*\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-8-ecdsa-signatur-verifizieren\">Schritt 8: ECDSA-Signatur verifizieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die Verifikation einer ECDSA-Signatur beweist, dass die Daten weder ver\u00e4ndert wurden noch von einer anderen Partei als dem Inhaber des privaten Schl\u00fcssels stammen. Nur der \u00f6ffentliche Schl\u00fcssel ist f\u00fcr die Verifikation notwendig. Wichtig: Eine fehlgeschlagene Verifikation darf keine detaillierten Fehlermeldungen nach au\u00dfen geben, da diese Informationen f\u00fcr Timing-Angriffe nutzbar sind.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ecdsa-verify.js\n'use strict';\n\nconst crypto = require('crypto');\nconst fs     = require('fs');\n\nfunction verifySignature(data, signature, publicKeyPem) {\n  const verify = crypto.createVerify('sha256');\n  verify.update(data);\n  verify.end();\n\n  try {\n    return verify.verify({ key: publicKeyPem, dsaEncoding: 'der' }, signature);\n  } catch {\n    \/\/ Keinen internen Fehler nach au\u00dfen geben\n    return false;\n  }\n}\n\n\/\/ Daten, Signatur und Schl\u00fcssel laden\nconst publicKey = fs.readFileSync('keys\/signierer-public.pem', 'utf8');\nconst signature = fs.readFileSync('signatures\/dokument.sig');\nconst document  = fs.readFileSync('messages\/dokument.json', 'utf8');\n\n\/\/ Test 1: Originaldokument verifizieren\nconst originalValid = verifySignature(document, signature, publicKey);\nconsole.log(`Original g\u00fcltig:      ${originalValid}`); \/\/ true\n\n\/\/ Test 2: Manipuliertes Dokument\nconst manipulated = JSON.stringify({\n  action:    '\u00dcberweisung',\n  betrag:    '99.999,00 EUR', \/\/ Betrag manipuliert!\n  von:       'alice@example.at',\n  an:        'angreifer@evil.com',\n  timestamp: '2026-06-20T10:00:00Z'\n});\n\nconst manipulatedValid = verifySignature(manipulated, signature, publicKey);\nconsole.log(`Manipuliert g\u00fcltig:   ${manipulatedValid}`); \/\/ false\n\n\/\/ Test 3: Falsche Signatur\nconst wrongSig = crypto.randomBytes(72);\nconst wrongValid = verifySignature(document, wrongSig, publicKey);\nconsole.log(`Falsche Sig g\u00fcltig:   ${wrongValid}`); \/\/ false\n\n\/* Erwartete Ausgabe:\n   Original g\u00fcltig:      true\n   Manipuliert g\u00fcltig:   false\n   Falsche Sig g\u00fcltig:   false\n*\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-9-ed25519-fuer-schnelle-deterministische-signaturen\">Schritt 9: Ed25519 f\u00fcr schnelle, deterministische Signaturen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ed25519 ist eine Implementierung des Edwards-Curve Digital Signature Algorithm (EdDSA) auf der Curve25519, entworfen von Daniel J. Bernstein. Sie gilt als eine der sichersten und schnellsten Signaturverfahren: Signaturen sind genau 64 Bytes kompakt, deterministisch (kein Zufallsgenerator f\u00fcr die Signaturerzeugung n\u00f6tig), resistent gegen Timing-Angriffe und laut <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc8032\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 8032<\/a> f\u00fcr neue Protokolle bevorzugt. OpenSSH nutzt Ed25519 seit Version 6.5, TLS 1.3 unterst\u00fctzt es via <code>ed25519<\/code> Signature Scheme.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ed25519-demo.js\n'use strict';\n\nconst crypto = require('crypto');\n\nfunction ed25519Demo() {\n  console.log('=== Ed25519 Signaturen ===\\n');\n\n  \/\/ Schl\u00fcsselpaar (kein namedCurve-Parameter, Ed25519 ist kein NIST-Kurventyp)\n  const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519', {\n    publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }\n  });\n\n  console.log('\u00d6ffentlicher Schl\u00fcssel:');\n  console.log(publicKey);\n\n  \/\/ Nachricht signieren (null = kein separater Hash, Ed25519 hasht intern mit SHA-512)\n  const message   = Buffer.from('Authentifizierungstoken: 2026-06-20T10:00:00Z');\n  const signature = crypto.sign(null, message, privateKey);\n\n  console.log(`Signatur (Base64): ${signature.toString('base64')}`);\n  console.log(`L\u00e4nge:             ${signature.length} Bytes`); \/\/ immer 64 Bytes\n\n  \/\/ Verifikation\n  const valid = crypto.verify(null, message, publicKey, signature);\n  console.log(`G\u00fcltig:            ${valid}`);\n\n  \/\/ Performance: 1000 Signaturen\n  const count = 1000;\n  const msg   = Buffer.from('Performance-Test');\n  const t0    = process.hrtime.bigint();\n  for (let i = 0; i < count; i++) crypto.sign(null, msg, privateKey);\n  const ms    = Number(process.hrtime.bigint() - t0) \/ 1e6;\n\n  console.log(`\\n${count} Signaturen in ${ms.toFixed(1)}ms (${(ms\/count).toFixed(3)}ms\/Op)`);\n\n  return { privateKey, publicKey, signature };\n}\n\ned25519Demo();\n\n\/* Erwartete Ausgabe:\n   === Ed25519 Signaturen ===\n\n   \u00d6ffentlicher Schl\u00fcssel:\n   -----BEGIN PUBLIC KEY-----\n   MCowBQYDK2VwAyEA...\n   -----END PUBLIC KEY-----\n\n   Signatur (Base64): 8f3a2b1c9d7e...\n   L\u00e4nge:             64 Bytes\n   G\u00fcltig:            true\n\n   1000 Signaturen in 45.2ms (0.045ms\/Op)\n*\/<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ecdsa-vs-ed25519-entscheidungsmatrix\">ECDSA vs. Ed25519: Entscheidungsmatrix<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Kriterium<\/th><th>ECDSA (P-256)<\/th><th>Ed25519<\/th><\/tr><\/thead><tbody><tr><td>Signaturl\u00e4nge<\/td><td>70-72 Bytes (DER, variabel)<\/td><td>64 Bytes (fest)<\/td><\/tr><tr><td>Deterministisch<\/td><td>Nein (ben\u00f6tigt CSPRNG)<\/td><td>Ja (RFC 8032)<\/td><\/tr><tr><td>Timing-Resistenz<\/td><td>Implementierungsabh\u00e4ngig<\/td><td>By Design<\/td><\/tr><tr><td>Sign-Performance<\/td><td>~0.08ms (P-256, i7-12700)<\/td><td>~0.045ms<\/td><\/tr><tr><td>Verify-Performance<\/td><td>~0.15ms<\/td><td>~0.10ms<\/td><\/tr><tr><td>JWT-Algorithmus<\/td><td>ES256 (RFC 7518)<\/td><td>EdDSA (RFC 8037)<\/td><\/tr><tr><td>FIPS 140-2\/3<\/td><td>Ja<\/td><td>Ab FIPS 140-3<\/td><\/tr><tr><td>Typischer Einsatz<\/td><td>TLS-Zertifikate, ECDSA-JWT<\/td><td>SSH, GPG, Signal-Protokoll<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-10-komplettes-sicherheitsprotokoll-aufbauen\">Schritt 10: Komplettes Sicherheitsprotokoll aufbauen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dieser Schritt kombiniert alle vorherigen Techniken zu einem vollst\u00e4ndigen Protokoll f\u00fcr verschl\u00fcsselte, authentifizierte Nachrichten. Das Muster folgt dem Prinzip der signierten Verschl\u00fcsselung: Die Signatur wird \u00fcber den Ciphertext berechnet (nicht \u00fcber den Klartext), und die Signatur wird vor der Entschl\u00fcsselung gepr\u00fcft. Dieses Prinzip hei\u00dft Encrypt-then-Sign und sch\u00fctzt vor Oracle-Angriffen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ secure-channel.js\n'use strict';\n\nconst crypto = require('crypto');\n\nclass SecureChannel {\n  constructor(namedCurve = 'prime256v1') {\n    this.namedCurve = namedCurve;\n    \/\/ Langzeit-Identit\u00e4tsschl\u00fcssel\n    const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve });\n    this.identityPriv = privateKey;\n    this.identityPub  = publicKey;\n  }\n\n  getPublicKeyPem() {\n    return this.identityPub.export({ type: 'spki', format: 'pem' });\n  }\n\n  \/**\n   * Nachricht verschl\u00fcsseln (ECDH + HKDF + AES-GCM) und signieren (ECDSA).\n   *\/\n  encryptAndSign(plaintext, recipientPublicKey) {\n    \/\/ Ephemerer Schl\u00fcssel f\u00fcr Forward Secrecy\n    const { privateKey: ephPriv, publicKey: ephPub } =\n      crypto.generateKeyPairSync('ec', { namedCurve: this.namedCurve });\n\n    \/\/ Shared Secret mit Empf\u00e4nger\n    const sharedSecret = crypto.diffieHellman({\n      privateKey: ephPriv,\n      publicKey:  recipientPublicKey\n    });\n\n    \/\/ Sitzungsschl\u00fcssel ableiten\n    const salt   = crypto.randomBytes(32);\n    const aesKey = Buffer.from(\n      crypto.hkdfSync('sha256', sharedSecret, salt, Buffer.from('secure-channel-v1'), 32)\n    );\n    const iv     = crypto.randomBytes(12);\n\n    \/\/ Verschl\u00fcsseln\n    const cipher    = crypto.createCipheriv('aes-256-gcm', aesKey, iv);\n    const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n    const authTag   = cipher.getAuthTag();\n\n    \/\/ Signatur \u00fcber Ciphertext (Encrypt-then-Sign)\n    const toSign    = Buffer.concat([encrypted, authTag, iv]);\n    const sign      = crypto.createSign('sha256');\n    sign.update(toSign);\n    const signature = sign.sign(this.identityPriv);\n\n    return {\n      version:     '1.0',\n      ephemeralPub: ephPub.export({ type: 'spki', format: 'pem' }),\n      salt:        salt.toString('base64'),\n      iv:          iv.toString('base64'),\n      ciphertext:  encrypted.toString('base64'),\n      authTag:     authTag.toString('base64'),\n      senderPub:   this.getPublicKeyPem(),\n      signature:   signature.toString('base64')\n    };\n  }\n\n  \/**\n   * Paket empfangen: Signatur pr\u00fcfen, dann entschl\u00fcsseln.\n   *\/\n  decryptAndVerify(packet) {\n    const { ephemeralPub, salt, iv, ciphertext, authTag, senderPub, signature } = packet;\n\n    \/\/ SCHRITT 1: Signatur pr\u00fcfen (vor der Entschl\u00fcsselung!)\n    const encBuf   = Buffer.from(ciphertext, 'base64');\n    const ivBuf    = Buffer.from(iv, 'base64');\n    const tagBuf   = Buffer.from(authTag, 'base64');\n    const toVerify = Buffer.concat([encBuf, tagBuf, ivBuf]);\n\n    const verify   = crypto.createVerify('sha256');\n    verify.update(toVerify);\n    const sigValid = verify.verify(senderPub, Buffer.from(signature, 'base64'));\n\n    if (!sigValid) {\n      throw new Error('Signatur ung\u00fcltig: Nachricht manipuliert oder falscher Absender!');\n    }\n\n    \/\/ SCHRITT 2: ECDH mit ephemerem Schl\u00fcssel\n    const ephKey  = crypto.createPublicKey(ephemeralPub);\n    const secret  = crypto.diffieHellman({ privateKey: this.identityPriv, publicKey: ephKey });\n    const saltBuf = Buffer.from(salt, 'base64');\n    const aesKey  = Buffer.from(\n      crypto.hkdfSync('sha256', secret, saltBuf, Buffer.from('secure-channel-v1'), 32)\n    );\n\n    \/\/ SCHRITT 3: Entschl\u00fcsseln\n    const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, ivBuf);\n    decipher.setAuthTag(tagBuf);\n    return Buffer.concat([decipher.update(encBuf), decipher.final()]).toString('utf8');\n  }\n}\n\n\/\/ Demonstration\nconst alice   = new SecureChannel('prime256v1');\nconst bob     = new SecureChannel('prime256v1');\nconst message = 'Vertraulich: Projekt X startet am 1. Juli 2026. Bitte best\u00e4tigen.';\n\nconst packet    = alice.encryptAndSign(message, bob.identityPub);\nconst decrypted = bob.decryptAndVerify(packet);\n\nconsole.log('Entschl\u00fcsselt:', decrypted);\nconsole.log('Korrekt:', message === decrypted);\n\n\/* Erwartete Ausgabe:\n   Entschl\u00fcsselt: Vertraulich: Projekt X startet am 1. Juli 2026. Bitte best\u00e4tigen.\n   Korrekt: true\n*\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-11-x25519-als-alternative-zu-ecdh\">Schritt 11: X25519 als Alternative zu ECDH<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">X25519 ist eine spezifische ECDH-Variante auf der Montgomery-Kurve Curve25519. Im Gegensatz zu ECDH auf NIST-Kurven ist X25519 by Design resistent gegen bestimmte Timing-Angriffe und Implementierungsfehler. TLS 1.3 bevorzugt X25519 vor P-256-ECDH und nutzt es als ersten angebotenen Schl\u00fcsselaustausch-Algorithmus. In Node.js ist X25519 als eigener Schl\u00fcsseltyp <code>x25519<\/code> verf\u00fcgbar.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ x25519-demo.js\n'use strict';\n\nconst crypto = require('crypto');\n\nfunction x25519KeyExchange() {\n  console.log('=== X25519 Schl\u00fcsselaustausch ===\\n');\n\n  \/\/ X25519-Schl\u00fcsselpaare (kein namedCurve-Parameter)\n  const { privateKey: alicePriv, publicKey: alicePub } =\n    crypto.generateKeyPairSync('x25519');\n  const { privateKey: bobPriv, publicKey: bobPub } =\n    crypto.generateKeyPairSync('x25519');\n\n  \/\/ \u00d6ffentliche Schl\u00fcssel in PEM ausgeben\n  const alicePubPem = alicePub.export({ type: 'spki', format: 'pem' });\n  console.log(`Alice \u00f6ffentlicher Schl\u00fcssel:\\n${alicePubPem}`);\n\n  \/\/ Shared Secret berechnen (identisch f\u00fcr beide Parteien)\n  const aliceSecret = crypto.diffieHellman({ privateKey: alicePriv, publicKey: bobPub });\n  const bobSecret   = crypto.diffieHellman({ privateKey: bobPriv,  publicKey: alicePub });\n\n  console.log(`Secret \u00fcbereinstimmend: ${aliceSecret.equals(bobSecret)}`);\n  console.log(`Secret-L\u00e4nge:           ${aliceSecret.length} Bytes`);\n\n  \/\/ Vergleich mit ECDH P-256\n  const { privateKey: p256Priv } = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\n  const { publicKey:  p256Pub  } = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\n\n  const count = 5000;\n  let t0 = process.hrtime.bigint();\n  for (let i = 0; i < count; i++) {\n    const { privateKey: a, publicKey: b } = crypto.generateKeyPairSync('x25519');\n    const { publicKey: c } = crypto.generateKeyPairSync('x25519');\n    crypto.diffieHellman({ privateKey: a, publicKey: c });\n  }\n  const x25519ms = Number(process.hrtime.bigint() - t0) \/ 1e6;\n\n  t0 = process.hrtime.bigint();\n  for (let i = 0; i < count; i++) {\n    const { privateKey: a } = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\n    const { publicKey: b }  = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });\n    crypto.diffieHellman({ privateKey: a, publicKey: b });\n  }\n  const p256ms = Number(process.hrtime.bigint() - t0) \/ 1e6;\n\n  console.log(`\\nX25519 (${count} Ops): ${(x25519ms\/count).toFixed(3)}ms\/Op`);\n  console.log(`P-256  (${count} Ops): ${(p256ms\/count).toFixed(3)}ms\/Op`);\n}\n\nx25519KeyExchange();\n\n\/* Erwartete Ausgabe:\n   === X25519 Schl\u00fcsselaustausch ===\n\n   Alice \u00f6ffentlicher Schl\u00fcssel:\n   -----BEGIN PUBLIC KEY-----\n   MCowBQYDK2VuAyEA...\n   -----END PUBLIC KEY-----\n\n   Secret \u00fcbereinstimmend: true\n   Secret-L\u00e4nge:           32 Bytes\n\n   X25519 (5000 Ops): 0.038ms\/Op\n   P-256  (5000 Ops): 0.052ms\/Op\n*\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-12-produktionshaertung-und-best-practices\">Schritt 12: Produktionsh\u00e4rtung und Best Practices<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der \u00dcbergang von einer funktionierenden ECC-Implementierung zu einer produktionssicheren erfordert mehrere zus\u00e4tzliche Ma\u00dfnahmen. Die h\u00e4ufigsten Sicherheitsl\u00fccken entstehen nicht durch mathematische Schw\u00e4chen der Kurven, sondern durch falsche Implementierung: wiederverwendete Nonces, unsichere Schl\u00fcsselspeicherung und fehlende Signaturverifikation vor der Entschl\u00fcsselung.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ production-hardening.js\n'use strict';\n\nconst crypto = require('crypto');\nconst fs     = require('fs');\n\n\/\/ 1. Schl\u00fcsselpaar mit Passphrase-Schutz (f\u00fcr Langzeitschl\u00fcssel auf Disk)\nfunction generateProtectedKeyPair(namedCurve, passphrase) {\n  return crypto.generateKeyPairSync('ec', {\n    namedCurve,\n    publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n    privateKeyEncoding: {\n      type:       'pkcs8',\n      format:     'pem',\n      cipher:     'aes-256-cbc',\n      passphrase  \/\/ AES-256-CBC-Schutz des privaten Schl\u00fcssels\n    }\n  });\n}\n\n\/\/ 2. Privaten Schl\u00fcssel aus Umgebungsvariable laden (12-Factor-App)\nfunction loadPrivateKeyFromEnv(envVarName, passphrase = null) {\n  const pem = process.env[envVarName];\n  if (!pem) throw new Error(`Umgebungsvariable ${envVarName} nicht gesetzt`);\n  const options = passphrase ? { key: pem, passphrase } : pem;\n  return crypto.createPrivateKey(options);\n}\n\n\/\/ 3. Zeitkonstanter Vergleich f\u00fcr Schl\u00fcssel-IDs und Tokens\nfunction safeEquals(a, b) {\n  const bufA = Buffer.from(String(a));\n  const bufB = Buffer.from(String(b));\n  if (bufA.length !== bufB.length) {\n    \/\/ Dummy-Vergleich verhindert L\u00e4ngen-Leakage via Timing\n    crypto.timingSafeEqual(bufA, bufA);\n    return false;\n  }\n  return crypto.timingSafeEqual(bufA, bufB);\n}\n\n\/\/ 4. Schl\u00fcsselrotation: neues Paar generieren, altes deaktivieren\nfunction rotateKeyPair(namedCurve, label, passphrase) {\n  const oldPath = `keys\/${label}-private.pem`;\n  if (fs.existsSync(oldPath)) {\n    fs.renameSync(oldPath, `keys\/${label}-private.pem.retired`);\n    console.log(`Alten Schl\u00fcssel archiviert: ${label}-private.pem.retired`);\n  }\n  const { privateKey, publicKey } = generateProtectedKeyPair(namedCurve, passphrase);\n  fs.writeFileSync(`keys\/${label}-private.pem`, privateKey, { mode: 0o600 });\n  fs.writeFileSync(`keys\/${label}-public.pem`,  publicKey);\n  console.log(`Neues Schl\u00fcsselpaar (${namedCurve}) f\u00fcr \"${label}\" bereit.`);\n  return { privateKey, publicKey };\n}\n\n\/\/ Demonstration\nconst passphrase = process.env.KEY_PASSPHRASE || 'SicheresTestPasswort2026!';\nconst { privateKey, publicKey } = generateProtectedKeyPair('prime256v1', passphrase);\n\nconsole.log('Gesch\u00fctzter privater Schl\u00fcssel (Auszug):');\nconsole.log(privateKey.substring(0, 80));\n\n\/\/ Laden und Entschl\u00fcsseln\nconst loaded = crypto.createPrivateKey({ key: privateKey, passphrase });\nconsole.log(`\\nEntschl\u00fcsselt: ${loaded.asymmetricKeyType} \/ ${loaded.asymmetricKeyDetails?.namedCurve}`);\n\n\/* Erwartete Ausgabe:\n   Gesch\u00fctzter privater Schl\u00fcssel (Auszug):\n   -----BEGIN ENCRYPTED PRIVATE KEY-----\n   MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAgBA...\n\n   Entschl\u00fcsselt: ec \/ prime256v1\n*\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"haeufige-fehler-und-loesungen\">H\u00e4ufige Fehler und L\u00f6sungen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ECC-Implementierungen scheitern in der Praxis selten an der Mathematik, sondern an handwerklichen Fehlern. Die folgenden sechs Fehler treten am h\u00e4ufigsten auf.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fehler-1-raw-ecdh-secret-direkt-als-schluessel-verwenden\">Fehler 1: Raw ECDH-Secret direkt als Schl\u00fcssel verwenden<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem:<\/strong> Das von <code>crypto.diffieHellman()<\/code> zur\u00fcckgegebene Buffer hat keine gleichm\u00e4\u00dfige Verteilung. Es eignet sich nicht direkt als AES-Schl\u00fcssel und kann Abh\u00e4ngigkeiten zwischen verschiedenen Sitzungen erzeugen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ FALSCH\nconst sharedSecret = crypto.diffieHellman({ privateKey: alicePriv, publicKey: bobPub });\nconst wrongKey = sharedSecret.slice(0, 32); \/\/ Niemals!\n\n\/\/ RICHTIG: HKDF anwenden\nconst correctKey = Buffer.from(\n  crypto.hkdfSync('sha256', sharedSecret, crypto.randomBytes(32), Buffer.from('app-v1'), 32)\n);<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fehler-2-kurven-mischen\">Fehler 2: Kurven mischen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem:<\/strong> ECDH funktioniert nur, wenn beide Parteien dieselbe Kurve verwenden. P-256 und P-384 sind inkompatibel. Node.js gibt den Fehler <code>error:0A00018E:SSL routines::point is not on curve<\/code> zur\u00fcck.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Absicherung: Kurven vor ECDH pr\u00fcfen\nfunction safeECDH(privKey, pubKey) {\n  const privCurve = privKey.asymmetricKeyDetails?.namedCurve;\n  const pubCurve  = pubKey.asymmetricKeyDetails?.namedCurve;\n  if (privCurve !== pubCurve) {\n    throw new Error(`Kurvenmismatch: privat=${privCurve}, \u00f6ffentlich=${pubCurve}`);\n  }\n  return crypto.diffieHellman({ privateKey: privKey, publicKey: pubKey });\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fehler-3-ecdsa-ohne-passenden-hash\">Fehler 3: ECDSA ohne passenden Hash<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem:<\/strong> F\u00fcr P-256 ist SHA-256 der korrekte Hash, f\u00fcr P-384 SHA-384 und f\u00fcr P-521 SHA-512. Ein schw\u00e4cherer Hash reduziert die Gesamtsicherheit. <code>crypto.sign(null, data, ecKey)<\/code> wirft einen Fehler: Ed25519 erwartet <code>null<\/code>, ECDSA erwartet einen Hash-Algorithmus.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Korrekte Hash-Kurven-Paarungen\nconst CURVE_HASH_MAP = {\n  'prime256v1': 'sha256', \/\/ ES256\n  'secp384r1':  'sha384', \/\/ ES384\n  'secp521r1':  'sha512'  \/\/ ES512\n};\n\nfunction signECDSA(data, privateKey) {\n  const curve    = privateKey.asymmetricKeyDetails?.namedCurve;\n  const hashAlgo = CURVE_HASH_MAP[curve];\n  if (!hashAlgo) throw new Error(`Keine Hash-Zuordnung f\u00fcr Kurve: ${curve}`);\n\n  const sign = crypto.createSign(hashAlgo);\n  sign.update(data);\n  return sign.sign(privateKey);\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fehler-4-signaturen-nach-der-entschluesselung-pruefen\">Fehler 4: Signaturen nach der Entschl\u00fcsselung pr\u00fcfen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem:<\/strong> Wenn die Signatur erst nach der Entschl\u00fcsselung gepr\u00fcft wird, kann ein Angreifer manipulierte Ciphertexte einschleusen und so Seitenkanal-Informationen aus dem Entschl\u00fcsselungsvorgang extrahieren, selbst bei AES-GCM.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ FALSCH: entschl\u00fcsseln, dann pr\u00fcfen\nconst decrypted = decrypt(ciphertext); \/\/ erm\u00f6glicht Timing-Angriff\nif (!verify(decrypted, signature)) throw new Error('...');\n\n\/\/ RICHTIG: pr\u00fcfen, dann entschl\u00fcsseln\nif (!verify(ciphertext, signature, senderPub)) throw new Error('Signatur ung\u00fcltig');\nconst decrypted = decrypt(ciphertext); \/\/ sicher<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fehler-5-private-schluessel-in-versionskontrolle\">Fehler 5: Private Schl\u00fcssel in Versionskontrolle<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem:<\/strong> Laut einer GitHub-Studie wurden 2024 \u00fcber 300.000 private Schl\u00fcssel in \u00f6ffentlichen Repositories gefunden. Ein in Git eingecheckter Schl\u00fcssel ist dauerhaft exponiert, auch nach dem L\u00f6schen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Git-Hook zur Verhinderung: .git\/hooks\/pre-commit (chmod +x setzen)\n#!\/bin\/sh\nif git diff --cached --name-only | grep -qE '\\.(pem|key|p12|pfx|der)$'; then\n  echo \"ABBRUCH: Schl\u00fcsseldatei in Staging gefunden!\"\n  echo \"Nutze: git rm --cached <datei>\"\n  exit 1\nfi<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fehler-6-fehlerdetails-an-den-client-senden\">Fehler 6: Fehlerdetails an den Client senden<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem:<\/strong> Detaillierte Krypto-Fehlermeldungen (Bibliotheksname, Reason-Code) helfen Angreifern bei der Analyse der Implementierung. Der Fehler sollte intern geloggt und nach au\u00dfen generisch beantwortet werden.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ FALSCH: Interner Fehler nach au\u00dfen\napp.post('\/verify', (req, res) => {\n  try { \/* ... *\/ }\n  catch (err) {\n    res.status(400).json({ error: err.message }); \/\/ Gibt OpenSSL-Details preis!\n  }\n});\n\n\/\/ RICHTIG: Generische Antwort, internes Logging\napp.post('\/verify', (req, res) => {\n  try { \/* ... *\/ }\n  catch (err) {\n    logger.error({ code: err.code, library: err.library }, 'Krypto-Fehler');\n    res.status(400).json({ error: 'Verifikation fehlgeschlagen' });\n  }\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"fehlerbehebung-8-haeufige-fehlermeldungen\">Fehlerbehebung: 8 h\u00e4ufige Fehlermeldungen<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Fehlermeldung<\/th><th>Ursache<\/th><th>L\u00f6sung<\/th><\/tr><\/thead><tbody><tr><td><code>ERR_CRYPTO_UNKNOWN_CIPHER<\/code><\/td><td>Algorithmusname falsch<\/td><td><code>crypto.getCiphers()<\/code> zur \u00dcberpr\u00fcfung nutzen<\/td><\/tr><tr><td><code>error:0A00018E: point is not on curve<\/code><\/td><td>Kurven-Mismatch bei ECDH<\/td><td>Kurven beider Parteien vor ECDH validieren<\/td><\/tr><tr><td><code>ERR_OSSL_PEM_NO_START_LINE<\/code><\/td><td>PEM-Datei korrumpiert oder Binary als Text gelesen<\/td><td>Datei explizit als UTF-8 laden<\/td><\/tr><tr><td><code>digest too big for key size<\/code><\/td><td>Falscher Hash f\u00fcr Kurve (z.B. SHA-512 mit P-256)<\/td><td>P-256=SHA-256, P-384=SHA-384, P-521=SHA-512<\/td><\/tr><tr><td><code>Unsupported key type: OKP<\/code><\/td><td>Ed25519 in Node.js unter Version 15<\/td><td>Node.js 18 LTS oder neuer verwenden<\/td><\/tr><tr><td><code>ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE<\/code><\/td><td>\u00d6ffentlichen Schl\u00fcssel f\u00fcr sign() verwendet<\/td><td>Privaten Schl\u00fcssel f\u00fcr sign(), \u00f6ffentlichen f\u00fcr verify()<\/td><\/tr><tr><td><code>Decryption failed<\/code> (AES-GCM)<\/td><td>AuthTag falsch, Daten manipuliert oder IV wiederverwendet<\/td><td>AuthTag korrekt \u00fcbertragen, IV immer zuf\u00e4llig<\/td><\/tr><tr><td><code>ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY<\/code><\/td><td>Empfangener Public Key nicht auf der Kurve<\/td><td>Schl\u00fcssel vor ECDH mit <code>createPublicKey()<\/code> validieren<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Strukturiertes Fehler-Logging f\u00fcr Krypto-Operationen\nfunction handleCryptoError(err, context) {\n  \/\/ Intern: vollst\u00e4ndige Details loggen\n  console.error({\n    context,\n    message: err.message,\n    code:    err.code,\n    library: err.library || null,\n    reason:  err.reason  || null\n  });\n  \/\/ Extern: generische Fehlermeldung\n  return new Error('Kryptografische Operation fehlgeschlagen');\n}\n\ntry {\n  \/\/ Ung\u00fcltige Operation ausl\u00f6sen\n  crypto.createPublicKey('kein-g\u00fcltiges-PEM');\n} catch (err) {\n  const safeErr = handleCryptoError(err, 'Schl\u00fcssel laden');\n  console.log('Antwort an Client:', safeErr.message);\n}\n\n\/* Erwartete Ausgabe:\n   { context: 'Schl\u00fcssel laden', message: 'error:09091064:...', code: undefined, ... }\n   Antwort an Client: Kryptografische Operation fehlgeschlagen\n*\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ecc-vs-rsa-direktvergleich\">ECC vs. RSA: Direktvergleich<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der entscheidende Vorteil von ECC liegt im Verh\u00e4ltnis von Schl\u00fcsselgr\u00f6\u00dfe zu Sicherheitsniveau. Die folgende Tabelle zeigt \u00e4quivalente Sicherheitsstufen nach NIST SP 800-57:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Sicherheit (Bit)<\/th><th>RSA-Schl\u00fcssel<\/th><th>ECC-Schl\u00fcssel<\/th><th>Gr\u00f6\u00dfenvorteil<\/th><th>TLS-Byte-Einsparung<\/th><\/tr><\/thead><tbody><tr><td>80 Bit (veraltet)<\/td><td>1024 Bit<\/td><td>160 Bit<\/td><td>6,4x kleiner<\/td><td>(unsicher)<\/td><\/tr><tr><td>112 Bit<\/td><td>2048 Bit<\/td><td>224 Bit<\/td><td>9,1x kleiner<\/td><td>~180 Bytes<\/td><\/tr><tr><td>128 Bit (Standard)<\/td><td>3072 Bit<\/td><td>256 Bit<\/td><td>12x kleiner<\/td><td>~250 Bytes<\/td><\/tr><tr><td>192 Bit<\/td><td>7680 Bit<\/td><td>384 Bit<\/td><td>20x kleiner<\/td><td>~700 Bytes<\/td><\/tr><tr><td>256 Bit<\/td><td>15360 Bit<\/td><td>521 Bit<\/td><td>29x kleiner<\/td><td>~1.500 Bytes<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Laut <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc6090\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 6090 (ECC Fundamentals)<\/a> ist ein 256-Bit-ECC-Schl\u00fcssel bis mindestens 2040 als ausreichend sicher eingestuft. F\u00fcr neue Systeme empfiehlt das BSI (Stand 2025) P-256 oder P-384 als bevorzugte asymmetrische Verfahren, alternativ X25519 f\u00fcr den Schl\u00fcsselaustausch. Die <a href=\"https:\/\/www.openssl.org\/docs\/man3.0\/man1\/openssl-ecparam.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenSSL-Dokumentation<\/a> listet alle verf\u00fcgbaren Kurven, die Node.js \u00fcber das <code>crypto<\/code>-Modul zug\u00e4nglich macht.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"kurvensicherheit-nach-safecurves-kriterien\">Kurvensicherheit nach SafeCurves-Kriterien<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Das Projekt <a href=\"https:\/\/safecurves.cr.yp.to\/\" target=\"_blank\" rel=\"noopener noreferrer\">SafeCurves<\/a> von Daniel Bernstein und Tanja Lange bewertet Kurven nach 11 Sicherheitskriterien. NIST-Kurven erf\u00fcllen nicht alle Kriterien, sind aber FIPS-zugelassen und in der Praxis ausreichend sicher, wenn korrekt implementiert.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Kurve<\/th><th>SafeCurves-Score<\/th><th>FIPS-zugelassen<\/th><th>Patent-frei<\/th><th>Empfehlung<\/th><\/tr><\/thead><tbody><tr><td>Curve25519 \/ X25519<\/td><td>11\/11<\/td><td>Ab FIPS 186-5 (2023)<\/td><td>Ja<\/td><td>Beste Wahl f\u00fcr Schl\u00fcsselaustausch<\/td><\/tr><tr><td>Ed25519<\/td><td>11\/11<\/td><td>Ab FIPS 186-5<\/td><td>Ja<\/td><td>Beste Wahl f\u00fcr Signaturen<\/td><\/tr><tr><td>P-256 (prime256v1)<\/td><td>7\/11<\/td><td>Ja (FIPS 186-5)<\/td><td>Ja<\/td><td>TLS, FIDO2, JWT, FIPS-Pflicht<\/td><\/tr><tr><td>P-384<\/td><td>7\/11<\/td><td>Ja<\/td><td>Ja<\/td><td>NSA Suite B, \u00f6sterr.\/dt. Beh\u00f6rden<\/td><\/tr><tr><td>P-521<\/td><td>7\/11<\/td><td>Ja<\/td><td>Ja<\/td><td>H\u00f6chste Sicherheitsanforderungen<\/td><\/tr><tr><td>secp256k1<\/td><td>9\/11<\/td><td>Nein<\/td><td>Ja<\/td><td>Nur f\u00fcr Blockchain-Anwendungen<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"weiterfuehrende-artikel\">Weiterf\u00fchrende Artikel<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Elliptic Curve Cryptography ist ein Baustein in einem gr\u00f6\u00dferen Sicherheitssystem. Diese Artikel vertiefen die verwandten Themen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/diffie-hellman-nodejs\/\">Diffie-Hellman Key Exchange in Node.js: 12 Schritte, 45 Min [2026]<\/a> - Das klassische DH-Protokoll als Grundlage zum Vergleich mit ECDH<\/li>\n<li><a href=\"\/digitale-signatur-nodejs\/\">Digitale Signatur in Node.js: 11 Schritte, 40 Min [2026]<\/a> - RSA-Signaturen im Vergleich zu ECDSA und Ed25519<\/li>\n<li><a href=\"\/blake3-hashing-nodejs\/\">BLAKE3 Hashing in Node.js: 10 Schritte, 20 Min [2026]<\/a> - Schnellere Hash-Alternativen zu SHA-256 f\u00fcr ECDSA-Anwendungen<\/li>\n<li><a href=\"\/sha-256-vs-sha-3\/\">SHA-256 vs SHA-3: 3,7x Geschwindigkeit, ein Sieger [2026]<\/a> - Welcher Hash-Algorithmus passt zu welcher ECC-Kurve<\/li>\n<li><a href=\"\/webauthn-nodejs\/\">WebAuthn in Node.js: Passwortlos in 12 Schritten [2026]<\/a> - FIDO2\/WebAuthn nutzt intern P-256 und Ed25519 f\u00fcr Attestierung<\/li>\n<li><a href=\"\/https-und-tls\/\">HTTPS und TLS: Wie das Schloss im Browser Sie sch\u00fctzt<\/a> - TLS 1.3 verwendet ECDHE und X25519 f\u00fcr Forward Secrecy<\/li>\n<li><a href=\"\/cryptography\/\">Kryptographie<\/a> - Alle Kryptographie-Tutorials im \u00dcberblick<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"haeufig-gestellte-fragen\">H\u00e4ufig gestellte Fragen<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"welche-ecc-kurve-soll-ich-fuer-neue-node-js-projekte-waehlen\">Welche ECC-Kurve soll ich f\u00fcr neue Node.js-Projekte w\u00e4hlen?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr die meisten Anwendungen ist <code>prime256v1<\/code> (P-256) die richtige Wahl: FIPS-zugelassen, von allen Bibliotheken unterst\u00fctzt, 128 Bit sicher bis 2040 und im TLS-Handshake optimal. F\u00fcr reine Signaturanwendungen ohne FIPS-Pflicht ist Ed25519 die bessere Option, da schneller, deterministisch und durch SafeCurves 11\/11 bewertet. F\u00fcr Beh\u00f6rdenanwendungen in \u00d6sterreich und Deutschland pr\u00fcfe P-384 oder Brainpool P-256 (BSI-Empfehlung).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ist-ecc-quantenresistent\">Ist ECC quantenresistent?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Nein. Shors Algorithmus auf einem Quantencomputer mit ausreichend fehlerkorrigierten Qubits w\u00fcrde ECDLP genauso brechen wie RSA. NIST hat 2024 mit FIPS 203 (ML-KEM\/Kyber), FIPS 204 (ML-DSA\/Dilithium) und FIPS 205 die ersten Post-Quantum-Standards ver\u00f6ffentlicht. F\u00fcr Langzeitsicherheit \u00fcber 2035 hinaus empfiehlt sich eine hybride Strategie: ECC kombiniert mit ML-KEM f\u00fcr den Schl\u00fcsselaustausch.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kann-ich-denselben-ecc-schluessel-fuer-ecdh-und-ecdsa-verwenden\">Kann ich denselben ECC-Schl\u00fcssel f\u00fcr ECDH und ECDSA verwenden?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Technisch m\u00f6glich, sicherheitstechnisch nicht empfohlen. ECDH-Schl\u00fcssel f\u00fcr den Schl\u00fcsselaustausch sollten von ECDSA-Schl\u00fcsseln f\u00fcr Signaturen getrennt sein. Die gemeinsame Nutzung desselben Schl\u00fcsselmaterials in verschiedenen kryptografischen Operationen kann Querabh\u00e4ngigkeiten erzeugen, die Angreifern helfen, Informationen \u00fcber den privaten Schl\u00fcssel zu sammeln.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"warum-gibt-ecdh-bei-jedem-aufruf-ein-anderes-shared-secret-zurueck\">Warum gibt ECDH bei jedem Aufruf ein anderes Shared Secret zur\u00fcck?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Das passiert, wenn ephemere Schl\u00fcssel (neue Schl\u00fcssel pro Sitzung) verwendet werden, wie es f\u00fcr Forward Secrecy notwendig ist. Bei statischen Schl\u00fcsselpaaren ist das Secret reproduzierbar. In modernen Protokollen wie TLS 1.3 und Signal werden absichtlich neue Schl\u00fcsselpaare pro Sitzung generiert, damit vergangene Sitzungen selbst bei einem kompromittierten Langzeitschl\u00fcssel nicht entschl\u00fcsselt werden k\u00f6nnen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"wie-unterscheidet-sich-x25519-von-ecdh\">Wie unterscheidet sich X25519 von ECDH?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">X25519 ist eine spezifische ECDH-Implementierung auf der Montgomery-Kurve Curve25519. In Node.js als <code>x25519<\/code>-Schl\u00fcsseltyp verf\u00fcgbar (<code>crypto.generateKeyPairSync('x25519')<\/code>). Im Gegensatz zu ECDH auf NIST-Kurven ist X25519 by Design resistent gegen Timing-Angriffe und Implementierungsfehler. TLS 1.3 bevorzugt X25519 vor P-256-ECDH: Es bietet dieselbe Sicherheit bei messbar niedrigerer Latenz.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"wie-speichere-ich-ecc-schluessel-sicher-in-einer-datenbank\">Wie speichere ich ECC-Schl\u00fcssel sicher in einer Datenbank?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u00d6ffentliche Schl\u00fcssel k\u00f6nnen unverschl\u00fcsselt als PEM-String gespeichert werden. Private Schl\u00fcssel m\u00fcssen vor der Speicherung mit einem Hauptschl\u00fcssel (Master Key) verschl\u00fcsselt werden, der au\u00dferhalb der Datenbank liegt. Empfohlene L\u00f6sung: AWS KMS, Azure Key Vault, HashiCorp Vault oder ein HSM als Key Encryption Key. Als Minimalansatz ohne HSM: Privaten Schl\u00fcssel mit AES-256-GCM verschl\u00fcsseln (<code>crypto.generateKeyPairSync<\/code> mit <code>cipher: 'aes-256-cbc'<\/code>), den KEK aus einer Umgebungsvariable laden, nur das verschl\u00fcsselte Blob in der Datenbank speichern.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"welcher-hash-algorithmus-gehoert-zu-welcher-ecc-kurve\">Welcher Hash-Algorithmus geh\u00f6rt zu welcher ECC-Kurve?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Die Kombination ist durch Standards vorgegeben: P-256 mit SHA-256 ergibt ES256 (JWT), P-384 mit SHA-384 ergibt ES384, P-521 mit SHA-512 ergibt ES512. Schw\u00e4chere Hashes als die Kurve (z.B. SHA-256 mit P-384) reduzieren die Gesamtsicherheit auf das Niveau des Hashes. Ed25519 und Ed448 definieren ihren Hash intern (SHA-512 bzw. SHAKE256), weshalb der Hash-Parameter in Node.js auf <code>null<\/code> gesetzt wird.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kann-ich-ecc-mit-der-webcrypto-api-subtle-in-node-js-verwenden\">Kann ich ECC mit der WebCrypto API (<code>subtle<\/code>) in Node.js verwenden?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ja. Node.js 18+ stellt die WebCrypto API als globales Objekt (<code>globalThis.crypto.subtle<\/code>) bereit, browserkompatibel. Damit l\u00e4sst sich Code zwischen Server und Browser teilen. Die Syntax unterscheidet sich vom <code>crypto<\/code>-Modul: <code>await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey'])<\/code>. Die <code>subtle<\/code>-API ist vollst\u00e4ndig asynchron und damit besser f\u00fcr hochvolumige Serveranwendungen geeignet als synchrone Operationen \u00fcber <code>generateKeyPairSync<\/code>.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Elliptic Curve Cryptography (ECC) liefert bei gleichem Sicherheitsniveau deutlich kleinere Schl\u00fcssel als RSA: Ein 256-Bit-ECC-Schl\u00fcssel entspricht einem 3072-Bit-RSA-Schl\u00fcssel. Node.js ab Version 18 unterst\u00fctzt ECC vollst\u00e4ndig \u00fcber das native crypto-Modul, ohne\u2026<\/p>\n","protected":false},"author":4,"featured_media":160,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-159","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cryptography"],"_links":{"self":[{"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/posts\/159","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/comments?post=159"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/posts\/159\/revisions"}],"predecessor-version":[{"id":161,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/posts\/159\/revisions\/161"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/media\/160"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/media?parent=159"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/categories?post=159"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/tags?post=159"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}