{"id":141,"date":"2026-06-19T16:20:04","date_gmt":"2026-06-19T16:20:04","guid":{"rendered":"https:\/\/shattered.io\/at\/2026\/06\/19\/diffie-hellman-nodejs\/"},"modified":"2026-06-19T16:21:26","modified_gmt":"2026-06-19T16:21:26","slug":"diffie-hellman-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/at\/diffie-hellman-nodejs\/","title":{"rendered":"Diffie-Hellman Key Exchange in Node.js: 12 Schritte, 45 Min [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Der Diffie-Hellman-Schl\u00fcsselaustausch ist das Fundament moderner Kryptographie: Von TLS 1.3 bis zu Signal-Protokollen basiert sichere Kommunikation darauf, dass zwei Parteien \u00fcber einen unsicheren Kanal einen gemeinsamen geheimen Schl\u00fcssel vereinbaren, ohne ihn jemals direkt zu \u00fcbertragen. Dieses Tutorial zeigt, wie Sie klassisches DH, ECDH mit P-256, X25519 und HKDF-Schl\u00fcsselableitung mit dem eingebauten <code>node:crypto<\/code>-Modul in Node.js v22+ implementieren.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"was-ist-der-diffie-hellman-schluesselaustausch\">Was ist der Diffie-Hellman-Schl\u00fcsselaustausch?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">1976 ver\u00f6ffentlichten Whitfield Diffie und Martin Hellman ein Konzept, das die Kryptographie revolutionierte: Zwei Parteien k\u00f6nnen sich auf ein gemeinsames Geheimnis einigen, ohne es jemals direkt zu \u00fcbertragen. Das Verfahren basiert auf der mathematischen Schwierigkeit, diskrete Logarithmen in einer gro\u00dfen Primzahlgruppe zu berechnen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die moderne Variante, der <strong>Elliptic Curve Diffie-Hellman (ECDH)<\/strong>, verwendet elliptische Kurven statt klassischer Primzahlgruppen. Das Ergebnis: bei deutlich kleineren Schl\u00fcsselgr\u00f6\u00dfen wird dasselbe Sicherheitsniveau erreicht. Ein 256-bit ECDH-Schl\u00fcssel entspricht laut NIST SP 800-57 etwa 128 Bit symmetrischer Sicherheit, also dem Niveau von AES-128.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js stellt \u00fcber das eingebaute <code>node:crypto<\/code>-Modul drei APIs zur Verf\u00fcgung: <code>createDiffieHellman()<\/code> f\u00fcr klassisches DH, <code>createECDH()<\/code> f\u00fcr ECDH auf benannten Kurven und <code>diffieHellman()<\/code> f\u00fcr moderne <code>KeyObject<\/code>-basierte Workflows inklusive X25519 und X448.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"voraussetzungen\">Voraussetzungen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bevor Sie starten, stellen Sie sicher, dass folgende Komponenten in den angegebenen Versionen vorliegen:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Voraussetzung<\/th><th>Mindestversion<\/th><th>Empfohlen<\/th><th>Hinweis<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>v18.0.0<\/td><td>v22.x LTS<\/td><td>ESM-Support, hkdfSync stabil<\/td><\/tr><tr><td>npm<\/td><td>9.0<\/td><td>10.x<\/td><td>F\u00fcr package.json-Verwaltung<\/td><\/tr><tr><td>OpenSSL<\/td><td>3.0<\/td><td>3.3+<\/td><td>Wird von Node mitgeliefert<\/td><\/tr><tr><td>Betriebssystem<\/td><td>Linux\/macOS\/Windows<\/td><td>Ubuntu 22.04+<\/td><td>Alle Plattformen unterst\u00fctzt<\/td><\/tr><tr><td>TypeScript (optional)<\/td><td>5.0<\/td><td>5.4+<\/td><td>F\u00fcr typisierte Projekte<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Grundkenntnisse in JavaScript\/Node.js werden vorausgesetzt. F\u00fcr Kryptographie-Grundlagen empfiehlt sich zuerst die Lekt\u00fcre des <a href=\"\/at\/nodejs-crypto-module\/\">Node.js Crypto Module Tutorials<\/a>. Kenntnisse zu <a href=\"\/at\/aes-256-encryption-nodejs\/\">AES-256-Verschl\u00fcsselung in Node.js<\/a> sind hilfreich, aber nicht zwingend notwendig.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-1-projekt-anlegen-und-abhaengigkeiten-installieren\">Schritt 1: Projekt anlegen und Abh\u00e4ngigkeiten installieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Das gesamte Projekt kommt ohne externe npm-Pakete aus. Das <code>node:crypto<\/code>-Modul ist seit Node.js v0.1.92 Teil der Standardbibliothek und seit Node.js v22 vollst\u00e4ndig stabil f\u00fcr alle hier verwendeten APIs.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir ecdh-tutorial\ncd ecdh-tutorial\nnpm init -y\n# package.json auf ESM umstellen\nnode -e \"\n  const fs = require('fs');\n  const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));\n  pkg.type = 'module';\n  fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));\n\"\necho \"Node.js Version: $(node -v)\"\necho \"OpenSSL Version: $(node -e 'const c = require(\\\"crypto\\\"); console.log(c.constants.OPENSSL_VERSION_NUMBER)')\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Erstellen Sie jetzt die Projektstruktur:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ecdh-tutorial\/\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 01-classic-dh.js\n\u251c\u2500\u2500 02-ecdh-p256.js\n\u251c\u2500\u2500 03-x25519.js\n\u251c\u2500\u2500 04-hkdf-derivation.js\n\u251c\u2500\u2500 05-secure-messaging.js\n\u2514\u2500\u2500 06-tls-client.js<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-2-klassischer-diffie-hellman-mit-2048-bit-prime\">Schritt 2: Klassischer Diffie-Hellman mit 2048-Bit-Prime<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Der klassische Diffie-Hellman-Algorithmus funktioniert auf Basis einer gro\u00dfen Primzahl <em>p<\/em> und einem Generator <em>g<\/em>. Beide Parteien w\u00e4hlen zuf\u00e4llige private Exponenten, tauschen die berechneten \u00f6ffentlichen Werte aus und berechnen unabh\u00e4ngig voneinander dasselbe Shared Secret.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr 2026 gilt laut BSI-Empfehlung (TR-02102-1) und NIST SP 800-131A: <strong>2048 Bit sind das absolute Minimum<\/strong> f\u00fcr neue Anwendungen. 3072 Bit werden f\u00fcr Systeme empfohlen, die \u00fcber 2030 hinaus sicher bleiben sollen. Ein 3072-Bit-DH-Schl\u00fcssel entspricht einem 128-Bit symmetrischen Sicherheitsniveau.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 01-classic-dh.js\nimport crypto from 'node:crypto';\nimport assert from 'node:assert';\n\n\/\/ Alice generiert eine neue 2048-Bit-DH-Gruppe\nconst alice = crypto.createDiffieHellman(2048);\nalice.generateKeys();\n\n\/\/ Bob muss dieselbe Primzahl und denselben Generator verwenden\nconst bob = crypto.createDiffieHellman(alice.getPrime(), alice.getGenerator());\nbob.generateKeys();\n\n\/\/ \u00d6ffentliche Schl\u00fcssel werden ausgetauscht (unverschl\u00fcsselt m\u00f6glich)\nconst alicePublicKey = alice.getPublicKey();\nconst bobPublicKey = bob.getPublicKey();\n\n\/\/ Jede Seite berechnet unabh\u00e4ngig das Shared Secret\nconst aliceSecret = alice.computeSecret(bobPublicKey);\nconst bobSecret = bob.computeSecret(alicePublicKey);\n\n\/\/ Beide Secrets m\u00fcssen identisch sein\nassert.strictEqual(\n  aliceSecret.toString('hex'),\n  bobSecret.toString('hex'),\n  'Shared Secrets stimmen nicht \u00fcberein!'\n);\n\nconsole.log('DH-2048 erfolgreich!');\nconsole.log('Shared Secret (hex, erste 32 Zeichen):', aliceSecret.toString('hex').slice(0, 32) + '...');\nconsole.log('Shared Secret L\u00e4nge:', aliceSecret.length, 'Bytes');\nconsole.log('Prime L\u00e4nge:', alice.getPrime().length * 8, 'Bit');\n\n\/\/ WICHTIG: Das Raw Shared Secret NICHT direkt als Schl\u00fcssel verwenden\n\/\/ Stattdessen: HKDF zur Schl\u00fcsselableitung (siehe Schritt 7)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ausgabe nach <code>node 01-classic-dh.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DH-2048 erfolgreich!\nShared Secret (hex, erste 32 Zeichen): a3f12e8b9c4d7e2f1a5b6c8d9e0f3a4b...\nShared Secret L\u00e4nge: 256 Bytes\nPrime L\u00e4nge: 2048 Bit<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Wichtig: Die Erzeugung einer 2048-Bit-DH-Gruppe dauert je nach Hardware zwischen 100 und 800 Millisekunden. Das ist einer der Hauptgr\u00fcnde, warum moderne Protokolle auf ECDH umgestiegen sind.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-3-ecdh-mit-p-256-prime256v1\">Schritt 3: ECDH mit P-256 (prime256v1)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Elliptic Curve Diffie-Hellman (ECDH) verwendet die algebraische Struktur elliptischer Kurven, um dasselbe mathematische Problem auf einem anderen Rechenfeld zu l\u00f6sen. Das Ergebnis: deutlich kleinere Schl\u00fcsselgr\u00f6\u00dfen bei gleichem oder h\u00f6herem Sicherheitsniveau.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die am h\u00e4ufigsten verwendete Kurve in Node.js ist <strong>P-256<\/strong>, die in Node.js unter dem OpenSSL-Namen <code>prime256v1<\/code> (auch bekannt als <code>secp256r1<\/code>) angesprochen wird. P-256 ist von NIST in FIPS 186-5 standardisiert und in TLS 1.3 als empfohlene Kurve gelistet.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 02-ecdh-p256.js\nimport crypto from 'node:crypto';\nimport assert from 'node:assert';\n\n\/\/ Alice erstellt ein ECDH-Objekt auf der P-256-Kurve\n\/\/ ACHTUNG: In Node.js hei\u00dft P-256 intern 'prime256v1', NICHT 'P-256'\nconst alice = crypto.createECDH('prime256v1');\nalice.generateKeys();\n\n\/\/ Bob macht dasselbe\nconst bob = crypto.createECDH('prime256v1');\nbob.generateKeys();\n\n\/\/ \u00d6ffentliche Schl\u00fcssel werden ausgetauscht\n\/\/ Format: 'uncompressed' (65 Bytes) oder 'compressed' (33 Bytes)\nconst alicePublicKey = alice.getPublicKey(null, 'uncompressed');\nconst bobPublicKey = bob.getPublicKey(null, 'compressed');\n\nconsole.log('Alice Public Key (uncompressed, 65 Bytes):', alicePublicKey.length, 'Bytes');\nconsole.log('Bob Public Key (compressed, 33 Bytes):', bobPublicKey.length, 'Bytes');\n\n\/\/ Shared Secrets berechnen\nconst aliceSecret = alice.computeSecret(bobPublicKey);\nconst bobSecret = bob.computeSecret(alicePublicKey);\n\nassert.strictEqual(\n  aliceSecret.toString('hex'),\n  bobSecret.toString('hex'),\n  'ECDH: Shared Secrets stimmen nicht \u00fcberein!'\n);\n\nconsole.log('ECDH P-256 erfolgreich!');\nconsole.log('Shared Secret L\u00e4nge:', aliceSecret.length, 'Bytes (32 = 256 Bit)');\nconsole.log('Shared Secret:', aliceSecret.toString('hex'))<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">ECDH mit P-256 ist etwa 10- bis 20-mal schneller als klassisches DH-2048 und produziert trotzdem einen 32-Byte (256-Bit) Shared Secret. Das liegt daran, dass elliptische Kurven auf einem anderen mathematischen Problem basieren, f\u00fcr das kein sub-exponentieller Algorithmus bekannt ist.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-4-verfuegbare-ecdh-kurven-in-node-js-pruefen\">Schritt 4: Verf\u00fcgbare ECDH-Kurven in Node.js pr\u00fcfen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js unterst\u00fctzt alle von der installierten OpenSSL-Version bereitgestellten elliptischen Kurven. Die wichtigsten f\u00fcr 2026 sind:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Kurve (Node.js Name)<\/th><th>Alias<\/th><th>Schl\u00fcsselgr\u00f6\u00dfe<\/th><th>Sicherheitsniveau<\/th><th>Empfehlung 2026<\/th><\/tr><\/thead><tbody><tr><td>prime256v1<\/td><td>P-256, secp256r1<\/td><td>256 Bit<\/td><td>128 Bit<\/td><td>Standard, weit verbreitet<\/td><\/tr><tr><td>secp384r1<\/td><td>P-384<\/td><td>384 Bit<\/td><td>192 Bit<\/td><td>F\u00fcr h\u00f6here Sicherheit<\/td><\/tr><tr><td>secp521r1<\/td><td>P-521<\/td><td>521 Bit<\/td><td>260 Bit<\/td><td>Maximum bei ECDH<\/td><\/tr><tr><td>brainpoolP256r1<\/td><td>BP-256<\/td><td>256 Bit<\/td><td>128 Bit<\/td><td>BSI-empfohlen (DE\/AT)<\/td><\/tr><tr><td>brainpoolP384r1<\/td><td>BP-384<\/td><td>384 Bit<\/td><td>192 Bit<\/td><td>BSI-empfohlen f\u00fcr hohe Sicherheit<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr \u00f6sterreichische Beh\u00f6rden und Unternehmen, die BSI-Empfehlungen (TR-02102-1) folgen m\u00fcssen, sind die <strong>Brainpool-Kurven<\/strong> oft die bevorzugte Wahl, da diese europ\u00e4ischen Ursprungs sind. F\u00fcr internationale Interoperabilit\u00e4t (TLS, WebCrypto API, Signal-Protokoll) sind P-256 und X25519 jedoch universeller.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Alle verf\u00fcgbaren Kurven auflisten\nimport crypto from 'node:crypto';\n\nconst curves = crypto.getCurves();\n\/\/ Relevante Kurven filtern\nconst relevant = curves.filter(c =>\n  c.includes('256') || c.includes('384') || c.includes('521') ||\n  c.includes('25519') || c.includes('448')\n);\nconsole.log('Relevante Kurven:', relevant.sort());<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-5-x25519-die-moderne-empfehlung\">Schritt 5: X25519 \u2013 Die moderne Empfehlung<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">X25519 (Diffie-Hellman auf Curve25519) ist die moderne Empfehlung f\u00fcr neuen Code. Die Kurve wurde von Daniel J. Bernstein mit Fokus auf Geschwindigkeit, Sicherheit und Implementierungssicherheit entworfen. Sie ist resistenter gegen bestimmte Seitenkanalangriffe als klassische NIST-Kurven und ist in RFC 7748 (2016) standardisiert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In Node.js wird X25519 \u00fcber die <code>KeyObject<\/code>-basierte API und <code>crypto.diffieHellman()<\/code> verwendet, nicht \u00fcber <code>createECDH()<\/code>. Diese API ist sauberer und typsicherer:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 03-x25519.js\nimport crypto from 'node:crypto';\nimport assert from 'node:assert';\n\n\/\/ X25519-Schl\u00fcsselpaare generieren\nconst alice = crypto.generateKeyPairSync('x25519');\nconst bob = crypto.generateKeyPairSync('x25519');\n\n\/\/ Shared Secrets \u00fcber die moderne diffieHellman()-API berechnen\nconst aliceSecret = crypto.diffieHellman({\n  privateKey: alice.privateKey,\n  publicKey: bob.publicKey,   \/\/ Alices Private Key + Bobs Public Key\n});\n\nconst bobSecret = crypto.diffieHellman({\n  privateKey: bob.privateKey,\n  publicKey: alice.publicKey,   \/\/ Bobs Private Key + Alice's Public Key\n});\n\nassert.strictEqual(\n  aliceSecret.toString('hex'),\n  bobSecret.toString('hex'),\n  'X25519: Shared Secrets stimmen nicht \u00fcberein!'\n);\n\nconsole.log('X25519 erfolgreich!');\nconsole.log('Shared Secret L\u00e4nge:', aliceSecret.length, 'Bytes (32 = 256 Bit)');\n\n\/\/ Public Key exportieren (f\u00fcr \u00dcbertragung an Gegenseite)\nconst alicePublicKeyDer = alice.publicKey.export({ type: 'spki', format: 'der' });\nconsole.log('Public Key (SPKI DER) Gr\u00f6\u00dfe:', alicePublicKeyDer.length, 'Bytes');\n\n\/\/ Public Key aus DER importieren (Empf\u00e4ngerseite)\nconst importedKey = crypto.createPublicKey({ key: alicePublicKeyDer, type: 'spki', format: 'der' });\nconsole.log('Key Type nach Import:', importedKey.type); \/\/ 'public'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">X25519 hat gegen\u00fcber ECDH P-256 einige Vorteile: Die Implementierung ist weniger fehleranf\u00e4llig (kein Punkt-Validierungsproblem), schneller auf vielen Architekturen und resistent gegen bestimmte Timing-Angriffe, die bei NIST-Kurven theoretisch m\u00f6glich sind.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-6-das-kritische-problem-raw-shared-secret-ist-kein-schluessel\">Schritt 6: Das kritische Problem \u2013 Raw Shared Secret ist kein Schl\u00fcssel<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ein h\u00e4ufiger und gef\u00e4hrlicher Fehler: Entwickler verwenden das Raw Shared Secret direkt als AES-Schl\u00fcssel. Das ist aus mehreren Gr\u00fcnden falsch:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Ungleichm\u00e4\u00dfige Verteilung:<\/strong> Das Shared Secret aus ECDH ist ein Punkt auf der elliptischen Kurve. Die x-Koordinate dieses Punkts ist nicht gleichm\u00e4\u00dfig \u00fcber den Keyspace verteilt, was statistische Angriffe erm\u00f6glicht.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. Fehlende Kontextbindung:<\/strong> Wenn beide Parteien denselben Schl\u00fcssel f\u00fcr verschiedene Zwecke verwenden (Verschl\u00fcsselung und MAC), m\u00fcssen diese Schl\u00fcssel voneinander unabh\u00e4ngig sein.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. Protokoll-Flexibilit\u00e4t:<\/strong> HKDF erm\u00f6glicht es, aus einem Shared Secret beliebig viele unabh\u00e4ngige Schl\u00fcssel abzuleiten, ohne neuen Schl\u00fcsselaustausch zu ben\u00f6tigen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hkdf-rfc-5869-schluesselableitung-in-node-js\">HKDF \u2013 RFC 5869 Schl\u00fcsselableitung in Node.js<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 04-hkdf-derivation.js\nimport crypto from 'node:crypto';\n\n\/**\n * Leitet mehrere kryptographische Schl\u00fcssel aus einem Shared Secret ab.\n * Basiert auf RFC 5869 HKDF mit SHA-256.\n *\/\nfunction deriveKeys(sharedSecret, salt, context) {\n  \/\/ Verschl\u00fcsselungsschl\u00fcssel (32 Bytes = AES-256)\n  const encKey = crypto.hkdfSync(\n    'sha256',\n    sharedSecret,\n    salt,\n    Buffer.from(`${context}:enc-key`, 'utf8'),\n    32\n  );\n\n  \/\/ MAC-Schl\u00fcssel (32 Bytes f\u00fcr HMAC-SHA256)\n  const macKey = crypto.hkdfSync(\n    'sha256',\n    sharedSecret,\n    salt,\n    Buffer.from(`${context}:mac-key`, 'utf8'),\n    32\n  );\n\n  \/\/ IV-Material (16 Bytes)\n  const ivMaterial = crypto.hkdfSync(\n    'sha256',\n    sharedSecret,\n    salt,\n    Buffer.from(`${context}:iv-material`, 'utf8'),\n    16\n  );\n\n  return { encKey, macKey, ivMaterial };\n}\n\n\/\/ Beispiel: ECDH P-256 + HKDF\nconst alice = crypto.createECDH('prime256v1');\nalice.generateKeys();\nconst bob = crypto.createECDH('prime256v1');\nbob.generateKeys();\n\nconst sharedSecret = alice.computeSecret(bob.getPublicKey());\n\n\/\/ Salt sollte pro Session frisch generiert und dem Empf\u00e4nger mitgeteilt werden\nconst salt = crypto.randomBytes(32);\nconst context = 'myapp-v1-session';\n\nconst { encKey, macKey, ivMaterial } = deriveKeys(sharedSecret, salt, context);\n\nconsole.log('Abgeleitete Schl\u00fcssel:');\nconsole.log('Enc Key (hex):', Buffer.from(encKey).toString('hex'));\nconsole.log('MAC Key (hex):', Buffer.from(macKey).toString('hex'));\nconsole.log('IV Material (hex):', Buffer.from(ivMaterial).toString('hex'));\n\n\/\/ \u00dcberpr\u00fcfen: Beide Seiten leiten dieselben Schl\u00fcssel ab\nconst sharedSecretBob = bob.computeSecret(alice.getPublicKey());\nconst { encKey: encKeyBob } = deriveKeys(sharedSecretBob, salt, context);\n\nconsole.log('\\nSchl\u00fcssel identisch:', Buffer.from(encKey).toString('hex') === Buffer.from(encKeyBob).toString('hex'));<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-7-authentifizierte-verschluesselung-mit-aes-256-gcm\">Schritt 7: Authentifizierte Verschl\u00fcsselung mit AES-256-GCM<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nachdem der Schl\u00fcssel sicher \u00fcber ECDH+HKDF abgeleitet wurde, kommt AES-256-GCM f\u00fcr die eigentliche Verschl\u00fcsselung zum Einsatz. GCM (Galois\/Counter Mode) bietet nicht nur Vertraulichkeit, sondern auch Authentizit\u00e4t: Ein Empf\u00e4nger kann erkennen, ob die Nachricht manipuliert wurde.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr Hintergrundinformationen zu AES-256 empfiehlt sich die Lekt\u00fcre des <a href=\"\/at\/aes-256-encryption-nodejs\/\">AES-256-Verschl\u00fcsselung in Node.js<\/a>-Tutorials. Grundlagen zu HMAC-basierten Signaturen finden Sie im <a href=\"\/at\/hmac-sha256-nodejs\/\">HMAC-SHA256 Tutorial<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-8-vollstaendiges-secure-messaging-projekt\">Schritt 8: Vollst\u00e4ndiges Secure Messaging Projekt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Jetzt werden alle Komponenten zu einem vollst\u00e4ndigen Secure Messaging System zusammengef\u00fcgt. Das System implementiert:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Ephemere ECDH P-256 Schl\u00fcsselpaare (frisch pro Session)<\/li><li>HKDF-SHA256 Schl\u00fcsselableitung mit Session-Salt<\/li><li>AES-256-GCM Verschl\u00fcsselung mit AAD (Authenticated Additional Data)<\/li><li>Vollst\u00e4ndige Fehlerbehandlung und Input-Validierung<\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 05-secure-messaging.js\nimport crypto from 'node:crypto';\nimport assert from 'node:assert';\n\nclass SecureSession {\n  #privateKey;\n  #curve;\n\n  constructor(curve = 'prime256v1') {\n    this.#curve = curve;\n    const ecdh = crypto.createECDH(curve);\n    ecdh.generateKeys();\n    this.#privateKey = ecdh;\n  }\n\n  getPublicKey(format = 'uncompressed') {\n    return this.#privateKey.getPublicKey(null, format);\n  }\n\n  \/**\n   * Berechnet Shared Secret + leitet Session-Schl\u00fcssel ab\n   * @param {Buffer} peerPublicKey - \u00d6ffentlicher Schl\u00fcssel der Gegenseite\n   * @param {Buffer} salt - Gemeinsamer Session-Salt\n   * @param {string} context - Protokoll-Kontext f\u00fcr HKDF\n   *\/\n  deriveSessionKey(peerPublicKey, salt, context = 'secure-messaging-v1') {\n    const sharedSecret = this.#privateKey.computeSecret(peerPublicKey);\n\n    \/\/ HKDF: Shared Secret -> 32-Byte AES-256 Schl\u00fcssel\n    return crypto.hkdfSync(\n      'sha256',\n      sharedSecret,\n      salt,\n      Buffer.from(context, 'utf8'),\n      32\n    );\n  }\n}\n\nfunction encryptMessage(key, plaintext, threadId = '') {\n  \/\/ Frischer IV pro Nachricht (PFLICHT bei GCM)\n  const iv = crypto.randomBytes(12);\n  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);\n\n  \/\/ AAD bindet die Nachricht an den Thread-Kontext\n  if (threadId) {\n    cipher.setAAD(Buffer.from(threadId, 'utf8'));\n  }\n\n  const ciphertext = Buffer.concat([\n    cipher.update(plaintext, 'utf8'),\n    cipher.final(),\n  ]);\n  const authTag = cipher.getAuthTag();\n\n  return { iv, ciphertext, authTag };\n}\n\nfunction decryptMessage(key, { iv, ciphertext, authTag }, threadId = '') {\n  const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);\n\n  if (threadId) {\n    decipher.setAAD(Buffer.from(threadId, 'utf8'));\n  }\n  decipher.setAuthTag(authTag);\n\n  const plaintext = Buffer.concat([\n    decipher.update(ciphertext),\n    decipher.final(), \/\/ Wirft Error wenn Auth Tag ung\u00fcltig\n  ]);\n\n  return plaintext.toString('utf8');\n}\n\n\/\/ --- Simulation: Alice und Bob tauschen Schl\u00fcssel aus ---\n\nconst alice = new SecureSession('prime256v1');\nconst bob = new SecureSession('prime256v1');\n\n\/\/ \u00d6ffentliche Schl\u00fcssel werden \u00fcber einen ungesicherten Kanal \u00fcbertragen\nconst alicePubKey = alice.getPublicKey();\nconst bobPubKey = bob.getPublicKey();\n\n\/\/ Session-Salt wird einmal ausgehandelt (z.B. vom Initiator generiert und \u00fcbertragen)\nconst sessionSalt = crypto.randomBytes(32);\nconst threadId = 'conversation-' + crypto.randomBytes(8).toString('hex');\n\n\/\/ Beide Seiten leiten denselben Session-Schl\u00fcssel ab\nconst aliceKey = alice.deriveSessionKey(bobPubKey, sessionSalt);\nconst bobKey = bob.deriveSessionKey(alicePubKey, sessionSalt);\n\n\/\/ Schl\u00fcssel m\u00fcssen identisch sein\nassert.strictEqual(\n  Buffer.from(aliceKey).toString('hex'),\n  Buffer.from(bobKey).toString('hex'),\n  'Session-Schl\u00fcssel stimmen nicht \u00fcberein!'\n);\n\n\/\/ Alice verschl\u00fcsselt eine Nachricht\nconst message = 'Treffen um 18:00 Uhr am Stephansplatz';\nconst encrypted = encryptMessage(aliceKey, message, threadId);\n\nconsole.log('Verschl\u00fcsselte Nachricht:');\nconsole.log('  IV:', encrypted.iv.toString('hex'));\nconsole.log('  Ciphertext:', encrypted.ciphertext.toString('hex'));\nconsole.log('  Auth Tag:', encrypted.authTag.toString('hex'));\n\n\/\/ Bob entschl\u00fcsselt\nconst decrypted = decryptMessage(bobKey, encrypted, threadId);\nassert.strictEqual(message, decrypted, 'Entschl\u00fcsselte Nachricht stimmt nicht \u00fcberein!');\nconsole.log('\\nEntschl\u00fcsselte Nachricht:', decrypted);\nconsole.log('Integrit\u00e4tspr\u00fcfung: OK');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fchren Sie das Script aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node 05-secure-messaging.js\n\n# Erwartete Ausgabe:\nVerschl\u00fcsselte Nachricht:\n  IV: a1b2c3d4e5f60708090a0b0c\n  Ciphertext: 8f3e2a1b7c9d4e5f6a...\n  Auth Tag: 1234567890abcdef1234567890abcdef\n\nEntschl\u00fcsselte Nachricht: Treffen um 18:00 Uhr am Stephansplatz\nIntegrit\u00e4tspr\u00fcfung: OK<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-9-forward-secrecy-warum-ephemere-schluessel-entscheidend-sind\">Schritt 9: Forward Secrecy \u2013 Warum ephemere Schl\u00fcssel entscheidend sind<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Forward Secrecy (auch Perfect Forward Secrecy, PFS) bedeutet: Wenn ein langfristiger privater Schl\u00fcssel kompromittiert wird, bleibt vergangene verschl\u00fcsselte Kommunikation trotzdem gesch\u00fctzt. Das ist nur dann m\u00f6glich, wenn <strong>ephemere Schl\u00fcsselpaare<\/strong> verwendet werden, die nach jeder Session gel\u00f6scht werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">TLS 1.3 macht Forward Secrecy durch die Verwendung von <strong>ECDHE<\/strong> (Ephemeral ECDH) zur Pflicht. Statisches DH oder ECDH, bei dem derselbe private Schl\u00fcssel f\u00fcr viele Sessions verwendet wird, bietet keine Forward Secrecy.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Methode<\/th><th>Forward Secrecy<\/th><th>Performance<\/th><th>TLS 1.3 Kompatibel<\/th><th>Empfehlung<\/th><\/tr><\/thead><tbody><tr><td>Statisches ECDH<\/td><td>Nein<\/td><td>Gut (kein neues KeyGen)<\/td><td>Nicht verwendet<\/td><td>Nur f\u00fcr Archivverschl\u00fcsselung<\/td><\/tr><tr><td>Ephemeres ECDHE P-256<\/td><td>Ja<\/td><td>Gut<\/td><td>Ja<\/td><td>Standard f\u00fcr neue Anwendungen<\/td><\/tr><tr><td>Ephemeres X25519<\/td><td>Ja<\/td><td>Sehr gut<\/td><td>Ja (bevorzugt)<\/td><td>Erste Wahl 2026<\/td><\/tr><tr><td>Klassisches DHE-2048<\/td><td>Ja<\/td><td>Langsam<\/td><td>Nein (aus TLS 1.3 entfernt)<\/td><td>Nur Legacy-Systeme<\/td><\/tr><tr><td>RSA Key Exchange<\/td><td>Nein<\/td><td>Sehr langsam<\/td><td>Nein (aus TLS 1.3 entfernt)<\/td><td>Veraltet, nicht verwenden<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">TLS 1.3 hat RSA-Key-Exchange und nicht-ephemere DH-Varianten vollst\u00e4ndig entfernt. Jede TLS-1.3-Verbindung verwendet obligatorisch ECDHE oder DHE f\u00fcr Forward Secrecy. Das erkl\u00e4rt, warum moderne Browser nur noch TLS 1.3 unterst\u00fctzen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-10-tls-server-mit-ecdhe-in-node-js\">Schritt 10: TLS-Server mit ECDHE in Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In der Praxis kommt der direkte ECDH-API-Aufruf selten vor. In den meisten Anwendungsf\u00e4llen k\u00fcmmert sich das TLS-Modul automatisch um den Schl\u00fcsselaustausch. Trotzdem lohnt sich ein Blick auf die Konfiguration, um sicherzustellen, dass veraltete Cipher-Suites ausgeschlossen werden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 06-tls-server.js\nimport tls from 'node:tls';\nimport fs from 'node:fs';\n\n\/\/ Produktions-Server-Konfiguration\nconst serverOptions = {\n  key: fs.readFileSync('server-key.pem'),\n  cert: fs.readFileSync('server-cert.pem'),\n\n  \/\/ Nur TLS 1.3 zulassen (ECDHE ist Pflicht)\n  minVersion: 'TLSv1.3',\n\n  \/\/ Explizite Cipher-Suite-Konfiguration (TLS 1.3 Standard ist bereits gut)\n  \/\/ In TLS 1.3 werden nur ECDHE-basierte Suites verwendet:\n  \/\/ TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256\n  ciphers: [\n    'TLS_AES_256_GCM_SHA384',\n    'TLS_CHACHA20_POLY1305_SHA256',\n    'TLS_AES_128_GCM_SHA256',\n  ].join(':'),\n\n  \/\/ ECDH-Kurven f\u00fcr den Key Exchange festlegen\n  ecdhCurve: 'X25519:prime256v1:secp384r1',\n};\n\nconst server = tls.createServer(serverOptions, (socket) => {\n  console.log('Verbunden:', socket.remoteAddress);\n  console.log('Protokoll:', socket.getProtocol());  \/\/ 'TLSv1.3'\n  console.log('Cipher:', socket.getCipher().name);\n\n  socket.write('Willkommen auf dem sicheren Server!\\n');\n  socket.end();\n});\n\nserver.listen(8443, () => {\n  console.log('TLS-Server l\u00e4uft auf Port 8443');\n  console.log('Mindestprotokoll: TLS 1.3 (ECDHE Pflicht)');\n});\n\n\/\/ Client-seitig: Protokoll und Cipher pr\u00fcfen\nimport tls_client from 'node:tls';\n\nconst client = tls_client.connect({\n  host: 'localhost',\n  port: 8443,\n  minVersion: 'TLSv1.3',\n  rejectUnauthorized: false, \/\/ Nur f\u00fcr Tests; in Produktion auf true setzen\n}, () => {\n  console.log('\\nClient verbunden');\n  console.log('Protokoll:', client.getProtocol());\n  console.log('Cipher:', client.getCipher().name);\n  console.log('Key Exchange:', client.getCipher().kx || 'ECDHE (TLS 1.3 Standard)');\n  client.end();\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-11-schluessel-persistieren-und-importieren\">Schritt 11: Schl\u00fcssel persistieren und importieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In realen Anwendungen m\u00fcssen ECDH-Schl\u00fcsselpaare oft serialisiert, gespeichert und wieder geladen werden. Node.js unterst\u00fctzt PEM- und DER-Formate \u00fcber die <code>KeyObject<\/code>-API:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Schl\u00fcsselpaar generieren und exportieren\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\n\n\/\/ X25519 Schl\u00fcsselpaar generieren\nconst { privateKey, publicKey } = crypto.generateKeyPairSync('x25519', {\n  privateKeyEncoding: {\n    type: 'pkcs8',\n    format: 'pem',\n  },\n  publicKeyEncoding: {\n    type: 'spki',\n    format: 'pem',\n  },\n});\n\n\/\/ Auf Festplatte speichern\nfs.writeFileSync('alice-private.pem', privateKey);\nfs.writeFileSync('alice-public.pem', publicKey);\n\nconsole.log('Private Key (PKCS8 PEM):');\nconsole.log(privateKey);\nconsole.log('Public Key (SPKI PEM):');\nconsole.log(publicKey);\n\n\/\/ --- Auf der anderen Seite: Schl\u00fcssel importieren ---\nconst loadedPrivate = crypto.createPrivateKey(fs.readFileSync('alice-private.pem'));\nconst loadedPublic = crypto.createPublicKey(fs.readFileSync('alice-public.pem'));\n\nconsole.log('Geladener Private Key Type:', loadedPrivate.type); \/\/ 'private'\nconsole.log('Geladener Public Key Type:', loadedPublic.type);  \/\/ 'public'\nconsole.log('Kurve:', loadedPrivate.asymmetricKeyDetails);     \/\/ { namedCurve: 'x25519' }<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"schritt-12-performance-vergleich-und-entscheidungshilfe\">Schritt 12: Performance-Vergleich und Entscheidungshilfe<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die Wahl des richtigen Schl\u00fcsselaustauschverfahrens h\u00e4ngt von Sicherheitsanforderungen, Interoperabilit\u00e4t und Performance ab. Folgende Benchmarks wurden auf einem typischen Linux-Server mit Node.js v22 gemessen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ performance-bench.js\nimport crypto from 'node:crypto';\n\nfunction bench(name, fn, iterations = 1000) {\n  const start = process.hrtime.bigint();\n  for (let i = 0; i < iterations; i++) fn();\n  const end = process.hrtime.bigint();\n  const ms = Number(end - start) \/ 1_000_000;\n  console.log(`${name}: ${(ms \/ iterations).toFixed(3)}ms\/op (${iterations} ops in ${ms.toFixed(0)}ms)`);\n}\n\n\/\/ Classic DH 2048\nbench('DH-2048 KeyGen', () => {\n  const dh = crypto.createDiffieHellman(2048);\n  dh.generateKeys();\n}, 50); \/\/ Weniger Iterationen da langsam\n\n\/\/ ECDH P-256\nbench('ECDH P-256 KeyGen+ComputeSecret', () => {\n  const alice = crypto.createECDH('prime256v1');\n  alice.generateKeys();\n  const bob = crypto.createECDH('prime256v1');\n  bob.generateKeys();\n  alice.computeSecret(bob.getPublicKey());\n});\n\n\/\/ X25519\nbench('X25519 KeyGen+DH', () => {\n  const alice = crypto.generateKeyPairSync('x25519');\n  const bob = crypto.generateKeyPairSync('x25519');\n  crypto.diffieHellman({ privateKey: alice.privateKey, publicKey: bob.publicKey });\n});\n\n\/\/ HKDF SHA-256\nbench('HKDF-SHA256 (32 Bytes)', () => {\n  const secret = crypto.randomBytes(32);\n  const salt = crypto.randomBytes(16);\n  crypto.hkdfSync('sha256', secret, salt, Buffer.from('info'), 32);\n}, 10000);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Typische Ergebnisse auf einem modernen Linux-Server (AMD EPYC, Node.js v22):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DH-2048 KeyGen: 312.450ms\/op (50 ops in 15622ms)\nECDH P-256 KeyGen+ComputeSecret: 0.187ms\/op (1000 ops in 187ms)\nX25519 KeyGen+DH: 0.091ms\/op (1000 ops in 91ms)\nHKDF-SHA256 (32 Bytes): 0.002ms\/op (10000 ops in 18ms)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Das Ergebnis: X25519 ist etwa <strong>2\u00d7 schneller als ECDH P-256<\/strong> und \u00fcber <strong>3000\u00d7 schneller als klassisches DH-2048<\/strong>. HKDF ist so schnell, dass es in der Praxis keine messbare Latenz verursacht.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"haeufige-fehler-und-sicherheitsfallen\">H\u00e4ufige Fehler und Sicherheitsfallen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die folgenden Fehler sind in der Praxis h\u00e4ufig anzutreffen und k\u00f6nnen die gesamte Sicherheit des Systems untergraben:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fehler-1-raw-shared-secret-direkt-als-aes-schluessel-verwenden\">Fehler 1: Raw Shared Secret direkt als AES-Schl\u00fcssel verwenden<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem:<\/strong> Das Raw Shared Secret aus ECDH ist kein kryptographisch sicherer Schl\u00fcssel. Es ist nicht gleichm\u00e4\u00dfig verteilt und kann statistische Schw\u00e4chen aufweisen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ FALSCH: Raw Secret direkt verwenden\nconst sharedSecret = alice.computeSecret(bobPublicKey);\nconst cipher = crypto.createCipheriv('aes-256-gcm', sharedSecret, iv); \/\/ GEF\u00c4HRLICH!\n\n\/\/ RICHTIG: HKDF verwenden\nconst sharedSecret = alice.computeSecret(bobPublicKey);\nconst aesKey = crypto.hkdfSync('sha256', sharedSecret, salt, Buffer.from('enc-v1'), 32);\nconst cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv); \/\/ Sicher<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fehler-2-iv-wiederverwendung-bei-aes-gcm\">Fehler 2: IV-Wiederverwendung bei AES-GCM<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem:<\/strong> AES-GCM ist katastrophal unsicher bei IV-Wiederverwendung. Zwei Nachrichten mit demselben IV+Key erm\u00f6glichen es einem Angreifer, beide Plaintexts zu rekonstruieren und den Auth-Tag zu f\u00e4lschen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ FALSCH: Fester oder inkrementeller IV\nconst iv = Buffer.alloc(12, 0); \/\/ Immer derselbe IV!\nconst cipher = crypto.createCipheriv('aes-256-gcm', key, iv);\n\n\/\/ RICHTIG: Kryptographisch zuf\u00e4lliger IV pro Nachricht\nconst iv = crypto.randomBytes(12); \/\/ Frisch f\u00fcr jede Nachricht<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fehler-3-falscher-kurvenname-in-node-js\">Fehler 3: Falscher Kurvenname in Node.js<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem:<\/strong> In Node.js hei\u00dft P-256 intern <code>prime256v1<\/code>, nicht <code>P-256<\/code>. Die Verwendung des falschen Namens f\u00fchrt zu einem <code>Error: invalid curve name<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ FALSCH:\ncrypto.createECDH('P-256'); \/\/ Error: invalid curve name\n\n\/\/ RICHTIG:\ncrypto.createECDH('prime256v1'); \/\/ Funktioniert\n\n\/\/ Korrekte Kurven-Namen-Tabelle:\n\/\/ P-256  -> prime256v1 oder secp256r1\n\/\/ P-384  -> secp384r1\n\/\/ P-521  -> secp521r1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 4: Fehlende Public-Key-Authentifizierung.<\/strong> ECDH und DH bieten ausschlie\u00dflich Vertraulichkeit durch Schl\u00fcsselvereinbarung, aber <strong>keine Identit\u00e4tsauthentifizierung<\/strong>. Ohne zus\u00e4tzliche Authentifizierung (digitale Signatur, zertifikatsbasierte TLS-Authentifizierung, etc.) ist ein Man-in-the-Middle-Angriff trivial m\u00f6glich. Mehr dazu im <a href=\"\/at\/digitale-signatur-nodejs\/\">Digitale Signatur in Node.js Tutorial<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 5: Statische Schl\u00fcssel ohne Forward Secrecy.<\/strong> Ein ECDH-Schl\u00fcsselpaar, das f\u00fcr viele Sessions wiederverwendet wird, bietet keine Forward Secrecy. Jede Session sollte ein frisches ephemeres Schl\u00fcsselpaar verwenden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 6: Logjam-Angriff durch schwache DH-Gruppen.<\/strong> Der 2015 entdeckte Logjam-Angriff zeigte, dass 512-bit und 768-bit DH-Gruppen von Angreifern vorberechnet werden k\u00f6nnen. Minimum f\u00fcr 2026: 2048 Bit. Verwenden Sie niemals <code>createDiffieHellman(512)<\/code> oder \u00e4hnlich kleine Gruppengr\u00f6\u00dfen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 7: Small-Subgroup-Angriff.<\/strong> Bei selbst implementierten DH-Protokollen m\u00fcssen eingehende Public Keys validiert werden. Ein Angreifer k\u00f6nnte einen Wert senden, der nicht zur gew\u00e4hlten Gruppe geh\u00f6rt, um Informationen \u00fcber den privaten Schl\u00fcssel zu extrahieren. In Node.js ist diese Validierung f\u00fcr die Standard-APIs eingebaut, aber bei manuellen Implementierungen kritisch.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fehler 8: Fehlender HKDF-Kontext (info-Parameter).<\/strong> Der <code>info<\/code>-Parameter von HKDF bindet den abgeleiteten Schl\u00fcssel an einen bestimmten Verwendungszweck. Ohne ihn k\u00f6nnen Schl\u00fcssel, die f\u00fcr verschiedene Zwecke abgeleitet wurden, identisch sein.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"troubleshooting-8-haeufige-fehlermeldungen\">Troubleshooting \u2013 8 h\u00e4ufige Fehlermeldungen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Folgende Fehler treten bei der Implementierung von Diffie-Hellman und ECDH in Node.js h\u00e4ufig auf:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. <code>Error: Invalid EC key<\/code><\/strong><br>Ursache: Der eingehende Public Key ist korrumpiert, hat das falsche Format oder geh\u00f6rt zu einer anderen Kurve.<br>L\u00f6sung: Sicherstellen, dass beide Parteien dieselbe Kurve verwenden. Public Key im richtigen Format \u00fcbertragen (<code>uncompressed<\/code> oder <code>compressed<\/code> konsistent nutzen).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. <code>Error: invalid curve name<\/code><\/strong><br>Ursache: Der verwendete Kurvenname ist in der Node.js\/OpenSSL-Umgebung nicht bekannt.<br>L\u00f6sung: <code>crypto.getCurves()<\/code> aufrufen und den korrekten Namen aus der Liste verwenden. P-256 hei\u00dft in Node.js <code>prime256v1<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. <code>Error: Unsupported state or unable to authenticate data<\/code><\/strong> (bei AES-GCM decrypt)<br>Ursache: Der Auth-Tag ist ung\u00fcltig. Entweder wurden Schl\u00fcssel, IV, AAD oder Ciphertext manipuliert.<br>L\u00f6sung: \u00dcberpr\u00fcfen, dass Schl\u00fcssel, IV und AAD auf beiden Seiten identisch sind. Auch ein falscher Salt bei HKDF f\u00fchrt zu diesem Fehler.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>4. <code>assert.strictEqual<\/code> schl\u00e4gt fehl bei Shared Secret Vergleich<\/strong><br>Ursache: Unterschiedliche Prime\/Generator auf beiden Seiten bei klassischem DH; oder Public Key einer anderen Kurve bei ECDH.<br>L\u00f6sung: Sicherstellen, dass Bob exakt dieselbe Prime und denselben Generator verwendet wie Alice: <code>crypto.createDiffieHellman(alice.getPrime(), alice.getGenerator())<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>5. <code>RangeError: Invalid typed array length<\/code> bei hkdfSync<\/strong><br>Ursache: Die angeforderte Schl\u00fcssell\u00e4nge \u00fcberschreitet das HKDF-Maximum.<br>L\u00f6sung: HKDF-SHA256 kann maximal 255 \u00d7 32 = 8160 Bytes ableiten. F\u00fcr typische Anwendungsf\u00e4lle (32 Bytes AES-256) ist das kein Problem.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>6. Sehr langsame DH-Schl\u00fcsselgenerierung (mehrere Sekunden)<\/strong><br>Ursache: Klassisches DH-2048 mit frischer Primzahlgenerierung ist rechenintensiv.<br>L\u00f6sung: Auf ECDH P-256 oder X25519 umsteigen (1000\u00d7 schneller). Falls klassisches DH ben\u00f6tigt wird, vorgenerierte Gruppen (MODP-Gruppen aus RFC 3526) verwenden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>7. <code>Error: error:10080069:elliptic curve routines:EC_POINT_set_affine_coordinates:point is not on curve<\/code><\/strong><br>Ursache: Der \u00fcbermittelte Public Key liegt nicht auf der erwarteten elliptischen Kurve.<br>L\u00f6sung: M\u00f6gliche Ursachen: \u00dcbertragungsfehler, falsche Kurve auf der Gegenseite, oder aktiver Small-Subgroup-Angriff. Public Keys immer \u00fcber authentifizierte Kan\u00e4le \u00fcbertragen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>8. Kompilierungsfehler bei \u00e4lteren Node.js-Versionen f\u00fcr <code>crypto.diffieHellman()<\/code><\/strong><br>Ursache: Die <code>crypto.diffieHellman()<\/code> API (f\u00fcr X25519) wurde in Node.js v13.9.0 als experimentell eingef\u00fchrt und ist ab v15.0.0 stabil.<br>L\u00f6sung: Node.js auf v18 LTS oder h\u00f6her aktualisieren. F\u00fcr X25519 auf \u00e4ltere Versionen: externes <code>noble-curves<\/code>-Paket verwenden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"sicherheitsempfehlungen-fuer-2026\">Sicherheitsempfehlungen f\u00fcr 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr \u00f6sterreichische Unternehmen und Entwickler, die nach BSI-Richtlinien (TR-02102-1) oder eIDAS-Anforderungen arbeiten, gelten folgende Empfehlungen f\u00fcr 2026:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kurvenauswahl:<\/strong> F\u00fcr neue Systeme ist X25519 die erste Wahl. F\u00fcr Systeme, die BSI-zertifiziert sein m\u00fcssen oder europ\u00e4ische Standards bevorzugen, sind Brainpool P-256 (brainpoolP256r1) oder Brainpool P-384 (brainpoolP384r1) die empfohlene Alternative. NIST P-256 (prime256v1) ist f\u00fcr internationale Interoperabilit\u00e4t der Standard.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Schl\u00fcsselgr\u00f6\u00dfen:<\/strong> F\u00fcr klassisches DH: mindestens 2048 Bit, empfohlen 3072 Bit f\u00fcr Systeme mit langem Betriebszeitraum. F\u00fcr ECDH: P-256 \/ X25519 f\u00fcr 128-Bit-Sicherheit; P-384 f\u00fcr 192-Bit-Sicherheit bei erh\u00f6hten Anforderungen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Post-Quantum-\u00dcberlegungen:<\/strong> Quantencomputer k\u00f6nnten klassisches DH und ECDH durch den Shor-Algorithmus brechen. NIST hat 2024 die ersten Post-Quantum-Standards ver\u00f6ffentlicht (ML-KEM\/Kyber f\u00fcr Key Encapsulation). F\u00fcr hochsensible Systeme sollte der \u00dcbergang zu hybriden Verfahren (klassisch + post-quantum) evaluiert werden. Mehr dazu im Artikel \u00fcber <a href=\"\/at\/post-quantum-cryptography-2026\/\">Post-Quantum-Kryptographie<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die vollst\u00e4ndige NIST-Referenz f\u00fcr Kurven findet sich in <a href=\"https:\/\/csrc.nist.gov\/publications\/detail\/fips\/186\/5\/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST FIPS 186-5<\/a>. RFC 7748 (<a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc7748\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 7748<\/a>) standardisiert X25519\/X448. Die TLS 1.3 Spezifikation ist in <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc8446\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 8446<\/a> definiert. F\u00fcr sichere Kryptographie-Implementierungen empfiehlt sich der <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Cryptographic_Storage_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP Cryptographic Storage Cheat Sheet<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"advanced-tips-fuer-produktionssysteme\">Advanced Tips f\u00fcr Produktionssysteme<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tip 1: Asynchrones KeyGen f\u00fcr Server-Anwendungen.<\/strong> <code>generateKeyPairSync()<\/code> blockiert den Event Loop. In Express- oder Fastify-Servern ist die asynchrone Variante Pflicht:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Asynchron (non-blocking f\u00fcr Server)\nconst { privateKey, publicKey } = await new Promise((resolve, reject) => {\n  crypto.generateKeyPair('x25519', (err, privateKey, publicKey) => {\n    if (err) reject(err);\n    else resolve({ privateKey, publicKey });\n  });\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tip 2: ECDH-Public-Keys komprimiert \u00fcbertragen.<\/strong> Der uncompressed-Format-Public-Key f\u00fcr P-256 ist 65 Bytes; compressed ist nur 33 Bytes. Bei vielen Nachrichten (IoT, Messaging) spart das fast 50% Bandbreite, ohne Sicherheit zu opfern:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Compressed Public Key (33 Bytes statt 65)\nconst pubKeyCompressed = ecdh.getPublicKey(null, 'compressed');\n\/\/ Auf der Empf\u00e4ngerseite: compressed Keys werden direkt von computeSecret() akzeptiert\nconst secret = otherEcdh.computeSecret(pubKeyCompressed);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tip 3: Session-Key-Rotation.<\/strong> F\u00fcr langlebige Verbindungen (WebSocket, gRPC-Streams) empfiehlt sich regelm\u00e4\u00dfige Schl\u00fcsselrotation, \u00e4hnlich wie TLS Session Tickets. Dabei wird ein neuer ECDH-Austausch durchgef\u00fchrt, ohne die Verbindung zu unterbrechen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tip 4: Key Derivation mit Protokoll-Version im Info-Parameter.<\/strong> Wenn Ihr Protokoll aktualisiert wird, \u00e4ndern Sie den <code>info<\/code>-Parameter von HKDF. Damit sind Schl\u00fcssel aus verschiedenen Protokollversionen garantiert verschieden, auch wenn das Shared Secret identisch ist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tip 5: Secrets aus dem Speicher tilgen.<\/strong> Node.js Buffers sind garbage-collected, aber sensitives Material kann l\u00e4nger als n\u00f6tig im Speicher verbleiben. F\u00fcr hochsensible Anwendungen kann <code>buffer.fill(0)<\/code> verwendet werden, um Shared Secrets nach der Schl\u00fcsselableitung zu \u00fcberschreiben.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"haeufig-gestellte-fragen-faq\">H\u00e4ufig gestellte Fragen (FAQ)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Was ist der Unterschied zwischen DH und ECDH?<\/strong><br>Klassisches Diffie-Hellman (DH) basiert auf der Schwierigkeit diskreter Logarithmen in einer Primzahlgruppe. ECDH l\u00f6st dasselbe Problem auf elliptischen Kurven. Der Vorteil: ECDH erreicht bei viel kleineren Schl\u00fcsseln dieselbe Sicherheit. Ein 256-Bit ECDH-Schl\u00fcssel entspricht einem 3072-Bit DH-Schl\u00fcssel hinsichtlich des Sicherheitsniveaus.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Warum sollte ich HKDF verwenden und nicht SHA-256 direkt auf das Shared Secret?<\/strong><br>HKDF (RFC 5869) ist speziell f\u00fcr diesen Anwendungsfall konzipiert. Direktes SHA-256 auf das Shared Secret hat mehrere Schw\u00e4chen: keine Kontextbindung, kein formales Sicherheitsmodell, keine Unterst\u00fctzung f\u00fcr mehrere unabh\u00e4ngige Schl\u00fcssel. HKDF ist der korrekte Standard f\u00fcr Key Derivation aus Shared Secrets.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sch\u00fctzt ECDH vor Man-in-the-Middle-Angriffen?<\/strong><br>Nein. ECDH allein sch\u00fctzt nicht vor Man-in-the-Middle. Ein Angreifer zwischen Alice und Bob k\u00f6nnte seinen eigenen Public Key an beide senden und zwei separate Shared Secrets aufbauen. Der Schutz kommt durch Authentifizierung: digitale Signaturen oder Zertifikate. In TLS \u00fcbernimmt das die Zertifikatskette. F\u00fcr eigene Protokolle: Public Keys m\u00fcssen \u00fcber einen authentifizierten Kanal ausgetauscht oder mit digitalen Signaturen verkn\u00fcpft werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ist X25519 sicherer als ECDH P-256?<\/strong><br>Beide bieten 128-Bit-Sicherheit und gelten 2026 als sicher. X25519 hat Vorteile: die Implementierung ist schwieriger falsch zu machen (keine Punkt-Validierung n\u00f6tig), resistenter gegen bestimmte Timing-Angriffe, und schneller auf vielen Architekturen. F\u00fcr neue Protokolle ist X25519 die erste Wahl. P-256 bleibt der universelle Standard f\u00fcr Interoperabilit\u00e4t mit \u00e4lteren Systemen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kann ich ECDH f\u00fcr Post-Quantum-Sicherheit verwenden?<\/strong><br>Nein. ECDH und alle klassischen asymmetrischen Verfahren werden durch Shor&#8217;s Algorithmus auf einem ausreichend gro\u00dfen Quantencomputer gebrochen. F\u00fcr Post-Quantum-Sicherheit sind hybride Verfahren n\u00f6tig: klassisches ECDH kombiniert mit ML-KEM (Kyber). Node.js hat noch keine native Post-Quantum-API; externe Bibliotheken wie <code>@noble\/post-quantum<\/code> k\u00f6nnen verwendet werden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Wie werden DH-Parameter sicher bei einem API ausgetauscht?<\/strong><br>In der Praxis werden ECDH Public Keys als Teil des Handshake-Protokolls \u00fcbertragen. Bei REST-APIs: Base64-encoded Public Keys im JSON-Body. Der Salt f\u00fcr HKDF wird typischerweise vom Session-Initiator generiert und mitgesendet. Wichtig: Der Transport der Public Keys muss \u00fcber TLS erfolgen, wenn keine Out-of-Band-Authentifizierung stattfindet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Unterst\u00fctzt Node.js X448 (Goldilocks)?<\/strong><br>Ja, Node.js v22 unterst\u00fctzt X448 \u00fcber dieselbe <code>crypto.diffieHellman()<\/code> API. X448 bietet 224-Bit-Sicherheit und ist langsamer als X25519. Es wird f\u00fcr Anwendungen empfohlen, die \u00fcber 128-Bit-Sicherheit hinausgehen m\u00fcssen, ohne auf ECDH P-521 zur\u00fcckgreifen zu wollen. Die API-Nutzung ist identisch zu X25519, nur der Key-Type \u00e4ndert sich auf <code>'x448'<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vollstaendiges-projektbeispiel-secure-key-exchange-api\">Vollst\u00e4ndiges Projektbeispiel: Secure Key Exchange API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Das folgende Beispiel zeigt eine vollst\u00e4ndige Express-basierte REST-API, die ECDH-Schl\u00fcsselaustausch als Service implementiert. Dieses Muster findet Anwendung bei sicheren Messaging-Apps, End-to-End-verschl\u00fcsselten Datei-Uploads und IoT-Device-Onboarding.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ secure-key-exchange-api.js\n\/\/ Hinweis: npm install express f\u00fcr dieses Beispiel erforderlich\nimport express from 'express';\nimport crypto from 'node:crypto';\n\nconst app = express();\napp.use(express.json());\n\n\/\/ In-Memory Session-Store (f\u00fcr Produktion: Redis verwenden)\nconst sessions = new Map();\n\n\/**\n * POST \/api\/session\/init\n * Client sendet seinen ECDH Public Key.\n * Server antwortet mit seinem Public Key und einem Session-Salt.\n *\/\napp.post('\/api\/session\/init', (req, res) => {\n  const { clientPublicKey, curve = 'prime256v1' } = req.body;\n\n  if (!clientPublicKey) {\n    return res.status(400).json({ error: 'clientPublicKey fehlt' });\n  }\n\n  try {\n    \/\/ Server generiert ephemeres ECDH-Schl\u00fcsselpaar\n    const serverEcdh = crypto.createECDH(curve);\n    serverEcdh.generateKeys();\n\n    \/\/ Shared Secret berechnen\n    const clientPubKeyBuffer = Buffer.from(clientPublicKey, 'base64');\n    const sharedSecret = serverEcdh.computeSecret(clientPubKeyBuffer);\n\n    \/\/ Session-Salt generieren\n    const salt = crypto.randomBytes(32);\n\n    \/\/ Session-Schl\u00fcssel ableiten\n    const sessionKey = crypto.hkdfSync(\n      'sha256',\n      sharedSecret,\n      salt,\n      Buffer.from('secure-api-v1:session-key', 'utf8'),\n      32\n    );\n\n    \/\/ Session speichern\n    const sessionId = crypto.randomUUID();\n    sessions.set(sessionId, {\n      sessionKey,\n      createdAt: Date.now(),\n      expiresAt: Date.now() + 3600_000, \/\/ 1 Stunde\n    });\n\n    res.json({\n      sessionId,\n      serverPublicKey: serverEcdh.getPublicKey('base64'),\n      salt: salt.toString('base64'),\n      expiresIn: 3600,\n    });\n  } catch (err) {\n    res.status(400).json({ error: 'Ung\u00fcltiger Public Key: ' + err.message });\n  }\n});\n\n\/**\n * POST \/api\/message\/send\n * Sendet eine verschl\u00fcsselte Nachricht im Kontext einer Session.\n *\/\napp.post('\/api\/message\/send', (req, res) => {\n  const { sessionId, iv, ciphertext, authTag, threadId } = req.body;\n\n  const session = sessions.get(sessionId);\n  if (!session) {\n    return res.status(401).json({ error: 'Session nicht gefunden oder abgelaufen' });\n  }\n  if (Date.now() > session.expiresAt) {\n    sessions.delete(sessionId);\n    return res.status(401).json({ error: 'Session abgelaufen' });\n  }\n\n  try {\n    const key = session.sessionKey;\n    const decipher = crypto.createDecipheriv(\n      'aes-256-gcm',\n      key,\n      Buffer.from(iv, 'base64')\n    );\n    if (threadId) decipher.setAAD(Buffer.from(threadId, 'utf8'));\n    decipher.setAuthTag(Buffer.from(authTag, 'base64'));\n\n    const decrypted = Buffer.concat([\n      decipher.update(Buffer.from(ciphertext, 'base64')),\n      decipher.final(),\n    ]).toString('utf8');\n\n    res.json({ status: 'ok', message: decrypted });\n  } catch (err) {\n    res.status(400).json({ error: 'Entschl\u00fcsselung fehlgeschlagen: ' + err.message });\n  }\n});\n\napp.listen(3000, () => console.log('Secure Key Exchange API auf Port 3000'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Der zugeh\u00f6rige Client-Code demonstriert, wie ein Frontend oder ein anderer Microservice diese API nutzt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ client-example.js\nimport crypto from 'node:crypto';\n\nasync function initiateSecureSession(apiUrl) {\n  \/\/ Client generiert ephemeres ECDH-Schl\u00fcsselpaar\n  const clientEcdh = crypto.createECDH('prime256v1');\n  clientEcdh.generateKeys();\n\n  const clientPublicKey = clientEcdh.getPublicKey('base64');\n\n  \/\/ Session initialisieren\n  const initResponse = await fetch(`${apiUrl}\/api\/session\/init`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\/json' },\n    body: JSON.stringify({ clientPublicKey, curve: 'prime256v1' }),\n  });\n\n  const { sessionId, serverPublicKey, salt } = await initResponse.json();\n\n  \/\/ Client-seitig Shared Secret berechnen\n  const serverPubKeyBuffer = Buffer.from(serverPublicKey, 'base64');\n  const sharedSecret = clientEcdh.computeSecret(serverPubKeyBuffer);\n\n  \/\/ Denselben Session-Schl\u00fcssel ableiten\n  const saltBuffer = Buffer.from(salt, 'base64');\n  const sessionKey = crypto.hkdfSync(\n    'sha256',\n    sharedSecret,\n    saltBuffer,\n    Buffer.from('secure-api-v1:session-key', 'utf8'),\n    32\n  );\n\n  console.log('Session etabliert:', sessionId);\n  return { sessionId, sessionKey };\n}\n\nasync function sendEncryptedMessage(apiUrl, sessionId, sessionKey, message, threadId) {\n  const iv = crypto.randomBytes(12);\n  const cipher = crypto.createCipheriv('aes-256-gcm', sessionKey, iv);\n\n  if (threadId) cipher.setAAD(Buffer.from(threadId, 'utf8'));\n\n  const ciphertext = Buffer.concat([cipher.update(message, 'utf8'), cipher.final()]);\n  const authTag = cipher.getAuthTag();\n\n  const response = await fetch(`${apiUrl}\/api\/message\/send`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\/json' },\n    body: JSON.stringify({\n      sessionId,\n      iv: iv.toString('base64'),\n      ciphertext: ciphertext.toString('base64'),\n      authTag: authTag.toString('base64'),\n      threadId,\n    }),\n  });\n\n  return response.json();\n}\n\n\/\/ Verwendung\nconst { sessionId, sessionKey } = await initiateSecureSession('http:\/\/localhost:3000');\nconst result = await sendEncryptedMessage(\n  'http:\/\/localhost:3000',\n  sessionId,\n  sessionKey,\n  'Geheime Nachricht an den Server',\n  'thread-001'\n);\nconsole.log('Server Antwort:', result);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"mathematischer-hintergrund-warum-funktioniert-diffie-hellman\">Mathematischer Hintergrund: Warum funktioniert Diffie-Hellman?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ein kurzer Blick auf die Mathematik hinter DH hilft, die Sicherheitsgarantien und Grenzen besser zu verstehen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Klassisches DH \u2013 Die diskreter Logarithmus-Annahme:<\/strong> Gegeben eine gro\u00dfe Primzahl <em>p<\/em> und ein Generator <em>g<\/em>. Alice w\u00e4hlt eine geheime Zahl <em>a<\/em> und berechnet <em>A = g^a mod p<\/em>. Bob w\u00e4hlt eine geheime Zahl <em>b<\/em> und berechnet <em>B = g^b mod p<\/em>. Alice sendet <em>A<\/em>, Bob sendet <em>B<\/em>. Alice berechnet <em>B^a mod p = g^(ba) mod p<\/em>, Bob berechnet <em>A^b mod p = g^(ab) mod p<\/em>. Da <em>ab = ba<\/em>, ist das Ergebnis identisch. Ein Angreifer, der <em>A<\/em> kennt, m\u00fcsste <em>a<\/em> aus <em>g^a mod p<\/em> berechnen, das ist der diskrete Logarithmus, f\u00fcr den kein effizienter klassischer Algorithmus bekannt ist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>ECDH \u2013 Die elliptic curve discrete logarithm-Annahme (ECDLP):<\/strong> Statt Primzahlgruppen werden Punkte auf elliptischen Kurven \u00fcber endlichen K\u00f6rpern verwendet. Die Gruppenoperation ist die Punktaddition auf der Kurve. Gegeben ist ein \u00f6ffentlicher Basispunkt <em>G<\/em> auf der Kurve. Alice w\u00e4hlt ein zuf\u00e4lliges <em>a<\/em> und berechnet <em>A = a\u00b7G<\/em> (Skalarmultiplikation). Bob berechnet <em>B = b\u00b7G<\/em>. Alice berechnet <em>a\u00b7B = a\u00b7(b\u00b7G) = (ab)\u00b7G<\/em>, Bob berechnet <em>b\u00b7A = b\u00b7(a\u00b7G) = (ab)\u00b7G<\/em>. Identisch. Der Angreifer m\u00fcsste <em>a<\/em> aus <em>a\u00b7G<\/em> bestimmen, das ist ECDLP, f\u00fcr den kein sub-exponentieller klassischer Algorithmus bekannt ist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Warum sind kleine DH-Gruppen gef\u00e4hrlich?<\/strong> F\u00fcr klassisches DH existiert der Number Field Sieve (NFS) Algorithmus, der sub-exponentielle Komplexit\u00e4t hat. Er ist effizienter als reine Brute-Force, erm\u00f6glicht aber bei kleinen Gruppengr\u00f6\u00dfen (512 Bit, 768 Bit, 1024 Bit) praktische Angriffe. Das war die Grundlage des Logjam-Angriffs. F\u00fcr ECDLP existiert kein vergleichbarer sub-exponentieller Algorithmus auf klassischen Computern, weshalb ECDH bei deutlich kleineren Schl\u00fcsseln dasselbe Sicherheitsniveau erreicht.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vergleich-mit-rsa-und-anderen-public-key-verfahren\">Vergleich mit RSA und anderen Public-Key-Verfahren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Diffie-Hellman und ECDH l\u00f6sen ein anderes Problem als RSA. RSA wird f\u00fcr Verschl\u00fcsselung und digitale Signaturen eingesetzt. DH\/ECDH sind ausschlie\u00dflich f\u00fcr den Schl\u00fcsselaustausch gedacht. Die Verfahren erg\u00e4nzen sich:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In TLS 1.3 \u00fcbernimmt ECDHE den Schl\u00fcsselaustausch (f\u00fcr Forward Secrecy), w\u00e4hrend RSA oder ECDSA f\u00fcr die Server-Authentifizierung \u00fcber Zertifikate zust\u00e4ndig ist. Diese Trennung der Verantwortlichkeiten macht das Protokoll robuster. Mehr zur RSA-Implementierung in Node.js finden Sie im <a href=\"\/at\/rsa-encryption-nodejs\/\">RSA-Verschl\u00fcsselung in Node.js Tutorial<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr digitale Signaturen in Node.js, die h\u00e4ufig in Kombination mit ECDH eingesetzt werden, empfiehlt sich der Artikel \u00fcber <a href=\"\/at\/sha-256-vs-sha-3\/\">SHA-256 vs SHA-3 im Vergleich<\/a> sowie das Tutorial zu <a href=\"\/at\/digitale-signatur-nodejs\/\">digitalen Signaturen in Node.js<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"weiterlesen\">Weiterlesen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Die hier behandelten Konzepte erg\u00e4nzen folgende Artikel auf shattered.io:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/at\/nodejs-crypto-module\/\">Node.js Crypto Module: 12 Schritte, 30 Min [2026]<\/a> \u2013 Grundlagen des node:crypto Moduls<\/li><li><a href=\"\/at\/aes-256-encryption-nodejs\/\">AES-256 Verschl\u00fcsselung in Node.js: 12 Schritte [2026]<\/a> \u2013 Symmetrische Verschl\u00fcsselung nach dem Schl\u00fcsselaustausch<\/li><li><a href=\"\/at\/hmac-sha256-nodejs\/\">HMAC-SHA256 in Node.js: 10 Schritte, 20 Min [2026]<\/a> \u2013 Nachrichtenintegrit\u00e4t und Authentifizierung<\/li><li><a href=\"\/at\/rsa-encryption-nodejs\/\">RSA-Verschl\u00fcsselung in Node.js: 11 Schritte [2026]<\/a> \u2013 Asymmetrische Verschl\u00fcsselung im Vergleich<\/li><li><a href=\"\/at\/digitale-signatur-nodejs\/\">Digitale Signatur in Node.js: 11 Schritte, 40 Min [2026]<\/a> \u2013 Public-Key-Authentifizierung f\u00fcr ECDH<\/li><li><a href=\"\/at\/cryptography\/\">Kryptographie \u2013 Alle Tutorials auf shattered.io<\/a><\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Der Diffie-Hellman-Schl\u00fcsselaustausch ist das Fundament moderner Kryptographie: Von TLS 1.3 bis zu Signal-Protokollen basiert sichere Kommunikation darauf, dass zwei Parteien \u00fcber einen unsicheren Kanal einen gemeinsamen geheimen Schl\u00fcssel vereinbaren, ohne\u2026<\/p>\n","protected":false},"author":3,"featured_media":142,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-141","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\/141","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\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/comments?post=141"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/posts\/141\/revisions"}],"predecessor-version":[{"id":143,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/posts\/141\/revisions\/143"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/media\/142"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/media?parent=141"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/categories?post=141"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/at\/wp-json\/wp\/v2\/tags?post=141"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}