{"id":99,"date":"2026-06-17T16:55:42","date_gmt":"2026-06-17T16:55:42","guid":{"rendered":"https:\/\/shattered.io\/se\/2026\/06\/17\/ecdsa-digitala-signaturer-nodejs\/"},"modified":"2026-06-17T16:56:57","modified_gmt":"2026-06-17T16:56:57","slug":"ecdsa-digitala-signaturer-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/se\/ecdsa-digitala-signaturer-nodejs\/","title":{"rendered":"Digitala signaturer i Node.js: 12 steg med ECDSA [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">En digital signatur bevisar att ett meddelande kom fr\u00e5n en specifik avs\u00e4ndare och inte \u00e4ndrades under v\u00e4gen. \u00c5r 2026 signeras allt fr\u00e5n Kubernetes-manifest och firmware-uppdateringar till API-svar och npm-paket med elliptiska kurvor. Node.js 24 med OpenSSL 3.5 ger inbyggt st\u00f6d f\u00f6r ECDSA (P-256, P-384) och Ed25519 direkt via <code>node:crypto<\/code>, utan externa beroenden.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Den h\u00e4r guiden tar dig igenom 12 konkreta steg f\u00f6r att implementera digitala signaturer i Node.js. Du l\u00e4r dig generera nyckelpar, signera godtycklig data, verifiera signaturer och hantera nycklar i PEM- och JWK-format. I slutet har du en komplett, produktionsklar API-signeringstj\u00e4nst med Express.js. F\u00f6rv\u00e4ntat tids\u00e5tg\u00e5ng: 35 minuter.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vad-ar-digitala-signaturer-och-varfor-de-ar-kritiska-2026\">Vad \u00e4r digitala signaturer och varf\u00f6r de \u00e4r kritiska 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">En digital signatur \u00e4r ett kryptografiskt bevis p\u00e5 autenticitet och integritet. Den fungerar med ett nyckelpar: du signerar med din privata nyckel, motparten verifierar med din publika nyckel. Ingen kan f\u00f6rfalska din signatur utan \u00e5tkomst till den privata nyckeln, och ingen kan \u00e4ndra det signerade meddelandet utan att signaturen bryts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I 2026 anv\u00e4nds digitala signaturer i praktiskt taget varje lager av modern infrastruktur. TLS-certifikat signeras med ECDSA P-256 eller P-384. Docker-avbildningar signeras med Notary och Sigstore. GitHub-commits kan signeras med Ed25519 SSH-nycklar. JWT-tokens signeras med RS256 eller ES256. Firmware-uppdateringar i inbyggda system verifieras med ECDSA innan de exekveras. Enligt NIST g\u00e4ller rekommendationen att anv\u00e4nda P-256 eller starkare f\u00f6r all ny kod som kr\u00e4ver minst 128 bitars s\u00e4kerhetsniv\u00e5 (se <a href=\"https:\/\/csrc.nist.gov\/pubs\/fips\/186-5\/final\" rel=\"noopener noreferrer\" target=\"_blank\">NIST FIPS 186-5<\/a>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Det matematiska fundamentet \u00e4r diskret logaritmproblemet p\u00e5 elliptiska kurvor. Givet en publik nyckel Q = dG (produkten av skal\u00e4ren d och basgeneratorn G p\u00e5 kurvan) \u00e4r det ber\u00e4kningsm\u00e4ssigt om\u00f6jligt att r\u00e4kna ut d utan att l\u00f6sa det diskreta logaritmproblemet, som anses h\u00e5rt f\u00f6r de kurvor NIST standardiserat. ECDSA f\u00f6rlitar sig dessutom p\u00e5 en slumpm\u00e4ssig nonce k per signatur. \u00c5teranv\u00e4ndning av k \u00e4r katastrofalt: en angripare kan \u00e5terskapa din privata nyckel direkt fr\u00e5n bara tv\u00e5 signaturer med samma k-v\u00e4rde. Det \u00e4r precis vad som h\u00e4nde med Sony PlayStation 3 2010. Node.js hanterar k-generering s\u00e4kert internt med en kryptografiskt s\u00e4ker slumptalsgenerator, vilket vi g\u00e5r igenom i steg 4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ecdsa-eddsa-och-rsa-algoritmer-jamforda\">ECDSA, EdDSA och RSA: algoritmer j\u00e4mf\u00f6rda<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Tre algoritmer dominerar digital signering i 2026: ECDSA (Elliptic Curve Digital Signature Algorithm), EdDSA med Ed25519, och RSA. Alla tre st\u00f6ds av Node.js 24:s <code>node:crypto<\/code>-modul. De skiljer sig i nyckelstorlek, signaturstorlek, prestanda och s\u00e4kerhetsegenskaper.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Algoritm<\/th><th>Nyckelstorlek (bit)<\/th><th>Signaturstorlek (byte)<\/th><th>S\u00e4kerhetsniv\u00e5 (bit)<\/th><th>NIST-status 2026<\/th><\/tr><\/thead><tbody><tr><td>ECDSA P-256<\/td><td>256<\/td><td>64 (DER: ~72)<\/td><td>128<\/td><td>Rekommenderad<\/td><\/tr><tr><td>ECDSA P-384<\/td><td>384<\/td><td>96 (DER: ~104)<\/td><td>192<\/td><td>Rekommenderad<\/td><\/tr><tr><td>Ed25519 (EdDSA)<\/td><td>255<\/td><td>64 (alltid fast)<\/td><td>128<\/td><td>Rekommenderad<\/td><\/tr><tr><td>RSA-2048<\/td><td>2048<\/td><td>256<\/td><td>112<\/td><td>Till 2030 (avvecklas)<\/td><\/tr><tr><td>RSA-3072<\/td><td>3072<\/td><td>384<\/td><td>128<\/td><td>Rekommenderad (tung)<\/td><\/tr><tr><td>ECDSA P-192<\/td><td>192<\/td><td>48<\/td><td>96<\/td><td>F\u00f6rbjuden i OpenSSL 3.5<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Ed25519 \u00e4r det modernaste valet f\u00f6r de flesta \u00e4ndam\u00e5l. Signeringen \u00e4r deterministisk (ingen slumpm\u00e4ssig nonce beh\u00f6vs), signaturstorleken \u00e4r alltid precis 64 byte, och implementationer \u00e4r enklare att skriva s\u00e4kert. ECDSA P-256 \u00e4r det vanligaste valet i TLS-ekosystemet eftersom st\u00f6det finns i praktiskt taget alla plattformar och webbl\u00e4sare. P-384 ger en h\u00f6gre s\u00e4kerhetsmarginal f\u00f6r system med l\u00e4ngre livsl\u00e4ngd, som myndighets- och finanssystem.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">RSA-2048 b\u00f6r undvikas f\u00f6r ny kod. NIST rekommenderar \u00f6verg\u00e5ng till elliptiska kurvor eller Ed25519. Dessutom \u00e4r RSA-signaturer 256 byte mot ECDSA:s 64-72 byte, vilket ger m\u00e4rkbar bandbreddsskillnad vid h\u00f6g volym. Node.js 24 med OpenSSL 3.5 och s\u00e4kerhetsniv\u00e5 2 <strong>f\u00f6rbjuder automatiskt<\/strong> ECC-nycklar kortare \u00e4n 224 bitar, vilket inneb\u00e4r att P-192 inte l\u00e4ngre fungerar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"forutsattningar-versioner-och-verktyg\">F\u00f6ruts\u00e4ttningar: versioner och verktyg<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Innan du b\u00f6rjar beh\u00f6ver du f\u00f6ljande:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Node.js 24.11.0 LTS<\/strong> (eller senare, st\u00f6ds till april 2028). Node.js 24 inneh\u00e5ller OpenSSL 3.5 med FIPS 140-3-st\u00f6d och inbyggd ML-DSA (post-kvantum). Undvik \u00e4ldre LTS-grenar; Node.js 18 har OpenSSL 1.1.1 som saknar flera moderna funktioner.<\/li>\n\n\n\n<li><strong>npm 10+<\/strong> (ing\u00e5r i Node.js 24). Den h\u00e4r guiden beh\u00f6ver bara <code>express<\/code> och <code>express-validator<\/code> f\u00f6r det slutliga projektet.<\/li>\n\n\n\n<li><strong>Grundl\u00e4ggande JavaScript-kunskaper<\/strong>. Guiden f\u00f6ruts\u00e4tter att du f\u00f6rst\u00e5r asynkron programmering, Promises och Buffer.<\/li>\n\n\n\n<li><strong>OpenSSL 3.5<\/strong> (ing\u00e5r med Node.js 24, beh\u00f6ver inte installeras separat). Verifiera med <code>node -e \"console.log(process.versions.openssl)\"<\/code>.<\/li>\n\n\n\n<li><strong>Textredigerare<\/strong> med JavaScript-st\u00f6d (VS Code, Neovim, WebStorm).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Kontrollera din Node.js-version:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node --version\n# F\u00f6rv\u00e4ntat: v24.11.0 eller senare\n\nnode -e \"console.log(process.versions.openssl)\"\n# F\u00f6rv\u00e4ntat: 3.5.x\n\nnode -e \"const c = require('node:crypto'); console.log(c.getCurves().includes('prime256v1'))\"\n# F\u00f6rv\u00e4ntat: true<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-1-3-projektstruktur-och-installation\">Steg 1-3: Projektstruktur och installation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa ett nytt Node.js-projekt med en tydlig katalogstruktur. Hela guiden bygger p\u00e5 denna grund.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 1: Skapa projektkatalogen<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir node-ecdsa-signatures\ncd node-ecdsa-signatures\nnpm init -y\n\n# Skapa katalogstruktur\nmkdir -p src\/keys src\/examples src\/api<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 2: Uppdatera package.json<\/strong> f\u00f6r att anv\u00e4nda ES-moduler:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"name\": \"node-ecdsa-signatures\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"engines\": { \"node\": \">=24.0.0\" },\n  \"scripts\": {\n    \"start\": \"node src\/api\/server.js\",\n    \"keygen\": \"node src\/examples\/keygen.js\",\n    \"sign\": \"node src\/examples\/sign.js\",\n    \"verify\": \"node src\/examples\/verify.js\"\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 3: Installera beroenden<\/strong> f\u00f6r API-projektet:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install express express-validator<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Notera att <code>node:crypto<\/code> \u00e4r inbyggt i Node.js. Du beh\u00f6ver inga externa kryptografibibliotek f\u00f6r ECDSA eller Ed25519. Undvik \u00e4ldre bibliotek som <code>elliptic<\/code> eller tredjepartspaket f\u00f6r produktionskod. Den inbyggda modulen \u00e4r testad, underh\u00e5llen av Node.js-teamet och h\u00e5ller sig synkroniserad med OpenSSL-s\u00e4kerhetsuppdateringar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-4-6-generera-ecdsa-nyckelpar\">Steg 4-6: Generera ECDSA-nyckelpar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ett ECDSA-nyckelpar best\u00e5r av en privat nyckel (ett stort slumpm\u00e4ssigt heltal d) och en publik nyckel (punkten Q = dG p\u00e5 kurvan). Den privata nyckeln m\u00e5ste h\u00e5llas hemlig. Den publika nyckeln kan delas fritt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 4: Generera ett P-256-nyckelpar<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa filen <code>src\/examples\/keygen.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { generateKeyPairSync, generateKeyPair } from 'node:crypto';\nimport { writeFileSync } from 'node:fs';\n\n\/\/ Synkron nyckelgenerering f\u00f6r skript vid serverstart\nfunction generateP256Keys() {\n  const { privateKey, publicKey } = generateKeyPairSync('ec', {\n    namedCurve: 'P-256',\n    publicKeyEncoding: {\n      type: 'spki',    \/\/ SubjectPublicKeyInfo: standard PEM-format\n      format: 'pem'\n    },\n    privateKeyEncoding: {\n      type: 'pkcs8',   \/\/ PrivateKeyInfo: standard PEM-format\n      format: 'pem'\n      \/\/ L\u00e4gg till cipher + passphrase f\u00f6r krypterad privat nyckel:\n      \/\/ cipher: 'aes-256-cbc',\n      \/\/ passphrase: process.env.KEY_PASSPHRASE\n    }\n  });\n\n  return { privateKey, publicKey };\n}\n\n\/\/ Asynkron nyckelgenerering blockerar inte event loop (rekommenderat f\u00f6r servers)\nasync function generateP384KeysAsync() {\n  return new Promise((resolve, reject) => {\n    generateKeyPair('ec', {\n      namedCurve: 'P-384',\n      publicKeyEncoding: { type: 'spki', format: 'pem' },\n      privateKeyEncoding: { type: 'pkcs8', format: 'pem' }\n    }, (err, publicKey, privateKey) => {\n      if (err) reject(err);\n      else resolve({ privateKey, publicKey });\n    });\n  });\n}\n\n\/\/ Generera och spara nycklar till disk\nconst p256 = generateP256Keys();\nwriteFileSync('src\/keys\/p256-private.pem', p256.privateKey, { mode: 0o600 });\nwriteFileSync('src\/keys\/p256-public.pem', p256.publicKey);\n\nconst p384 = await generateP384KeysAsync();\nwriteFileSync('src\/keys\/p384-private.pem', p384.privateKey, { mode: 0o600 });\nwriteFileSync('src\/keys\/p384-public.pem', p384.publicKey);\n\nconsole.log('P-256 publik nyckel (PEM):\\n', p256.publicKey);\nconsole.log('Nycklar sparade till src\/keys\/');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00f6rresultat fr\u00e5n <code>npm run keygen<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>P-256 publik nyckel (PEM):\n -----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9G4LPwCqb5f3Z2PxQ+8mJv1hY7k\nAo3TwD2WXKl5sRg8EzT9bWvHPh3NLPQ7RdB+ZUdX8lKtF1mWwNqAR9J3MA==\n-----END PUBLIC KEY-----\n\nNycklar sparade till src\/keys\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 5: F\u00f6rst\u00e5 PEM-formatet<\/strong>. PEM-filer \u00e4r Base64-kodade DER-strukturer med BEGIN\/END-huvuden. <code>spki<\/code> (SubjectPublicKeyInfo) \u00e4r standardformatet f\u00f6r publika nycklar i X.509-certifikat och TLS. <code>pkcs8<\/code> \u00e4r standardformatet f\u00f6r privata nycklar. Dessa format \u00e4r interoperabla med OpenSSL, Java, Go och de flesta andra plattformar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 6: Skydda den privata nyckeln<\/strong>. Filr\u00e4ttigheter <code>0o600<\/code> begr\u00e4nsar l\u00e4sning till \u00e4garen. F\u00f6r produktionssystem b\u00f6r du dessutom kryptera nyckeln med en l\u00f6senfras via <code>cipher: 'aes-256-cbc'<\/code> och <code>passphrase<\/code>-parametern. Lagra sedan l\u00f6senfransen i en hemlighetshanterare som HashiCorp Vault, AWS Secrets Manager eller Azure Key Vault, inte i milj\u00f6variabler som klartext.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-7-8-signera-data-med-ecdsa\">Steg 7-8: Signera data med ECDSA<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Signeringsprocessen hashar meddelandet med SHA-256 (f\u00f6r P-256) eller SHA-384 (f\u00f6r P-384), ber\u00e4knar sedan signaturen \u00f6ver hashv\u00e4rdet med den privata nyckeln. Resultatet \u00e4r ett signaturv\u00e4rde som kan exporteras i DER-format (bin\u00e4rt, variabel l\u00e4ngd) eller IEEE P1363-format (r och s konkatenerade, fast l\u00e4ngd).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 7: Implementera signeringslogiken<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa filen <code>src\/examples\/sign.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { createSign, createPrivateKey } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\n\n\/\/ L\u00e4s in privat nyckel fr\u00e5n PEM-fil och skapa KeyObject (cachelagra f\u00f6r \u00e5teranv\u00e4ndning)\nconst privateKeyPem = readFileSync('src\/keys\/p256-private.pem', 'utf8');\nconst privateKey = createPrivateKey(privateKeyPem);\n\nexport function signMessage(message, key = privateKey) {\n  \/\/ createSign tar hash-algoritmen som argument\n  \/\/ 'SHA256' \u00e4r korrekt f\u00f6r P-256; anv\u00e4nd 'SHA384' f\u00f6r P-384\n  const sign = createSign('SHA256');\n\n  \/\/ update() accepterar string (UTF-8), Buffer eller Uint8Array\n  sign.update(message);\n  sign.end();\n\n  \/\/ sign() returnerar signaturen\n  \/\/ dsaEncoding: 'ieee-p1363' ger r||s (64 byte f\u00f6r P-256, fast storlek)\n  \/\/ dsaEncoding: 'der' (standard) ger DER-kodad signatur (~70-72 byte, variabel)\n  const signature = sign.sign({\n    key,\n    dsaEncoding: 'ieee-p1363'  \/\/ IEEE P1363: enhetlig storlek, enklare f\u00f6r JWT och API\n  }, 'base64url');\n\n  return signature;\n}\n\n\/\/ Signera ett JSON-objekt som API-nyttolast\nconst payload = JSON.stringify({\n  sub: 'user-abc123',\n  action: 'transfer',\n  amount: 5000,\n  currency: 'SEK',\n  timestamp: Date.now()\n});\n\nconst signature = signMessage(payload);\nconsole.log('Meddelande:', payload);\nconsole.log('Signatur (Base64url):', signature);\nconsole.log('Signaturstorlek (byte):', Buffer.from(signature, 'base64url').length);\n\/\/ F\u00f6rv\u00e4ntad output: 64 byte f\u00f6r P-256 med ieee-p1363<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 8: Signera filer och bin\u00e4rdata<\/strong>. F\u00f6r bin\u00e4rdata, bilder, dokument och firmware skickar du en <code>Buffer<\/code> direkt eller anv\u00e4nder stream-API:t:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { createSign, createPrivateKey } from 'node:crypto';\nimport { readFileSync, createReadStream } from 'node:fs';\n\nconst privateKey = createPrivateKey(readFileSync('src\/keys\/p256-private.pem'));\n\n\/\/ Signera en stor fil som stream (minness\u00e4kert, blockerar inte event loop)\nexport async function signLargeFile(filePath) {\n  return new Promise((resolve, reject) => {\n    const sign = createSign('SHA256');\n    const stream = createReadStream(filePath);\n\n    stream.pipe(sign);\n    stream.on('end', () => {\n      sign.end();\n      resolve(sign.sign({ key: privateKey, dsaEncoding: 'ieee-p1363' }, 'base64url'));\n    });\n    stream.on('error', reject);\n  });\n}\n\nconst sig = await signLargeFile('src\/keys\/p256-public.pem');\nconsole.log('Filsignatur (Base64url):', sig);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-9-10-verifiera-ecdsa-signaturer\">Steg 9-10: Verifiera ECDSA-signaturer<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Verifieringen tar meddelandet, signaturen och den publika nyckeln som indata. Den r\u00e4knar om hashv\u00e4rdet och kontrollerar matematiskt att signaturen st\u00e4mmer. Resultatet \u00e4r <code>true<\/code> eller <code>false<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 9: Implementera verifieringslogik<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Skapa filen <code>src\/examples\/verify.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { createVerify, createPublicKey } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { signMessage } from '.\/sign.js';\n\nconst publicKeyPem = readFileSync('src\/keys\/p256-public.pem', 'utf8');\nconst publicKey = createPublicKey(publicKeyPem);\n\nexport function verifySignature(message, signature, key = publicKey) {\n  try {\n    const verify = createVerify('SHA256');\n    verify.update(message);\n    verify.end();\n\n    const isValid = verify.verify(\n      { key, dsaEncoding: 'ieee-p1363' },\n      signature,\n      'base64url'\n    );\n\n    return isValid;\n  } catch (err) {\n    \/\/ verify() kastar ett fel om signaturen \u00e4r felaktigt formaterad (inte bara ogiltig)\n    \/\/ Logga felet men returnera false f\u00f6r att inte avsl\u00f6ja intern information\n    console.error('Verifieringsfel (ogiltigt format):', err.code);\n    return false;\n  }\n}\n\n\/\/ Test 1: giltig signatur\nconst testPayload = 'Kritiskt meddelande att verifiera';\nconst sig = signMessage(testPayload);\nconsole.log('Giltig?', verifySignature(testPayload, sig));              \/\/ true\n\n\/\/ Test 2: manipulerat meddelande\nconsole.log('Manipulerat?', verifySignature(testPayload + ' !', sig)); \/\/ false\n\n\/\/ Test 3: slumpm\u00e4ssig signatur\nconst randomSig = Buffer.alloc(64).toString('base64url'); \/\/ 64 noll-byte\nconsole.log('Slumpsignatur?', verifySignature(testPayload, randomSig)); \/\/ false\n\n\/\/ Test 4: trunkerad signatur (63 byte, fel l\u00e4ngd f\u00f6r ieee-p1363 P-256)\nconst truncated = Buffer.alloc(63).toString('base64url');\nconsole.log('Trunkerad?', verifySignature(testPayload, truncated));     \/\/ false (returnerar false via catch)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Steg 10: Tidningsattacker och s\u00e4ker j\u00e4mf\u00f6relse<\/strong>. <code>createVerify().verify()<\/code> i Node.js utf\u00f6r sin j\u00e4mf\u00f6relse internt i OpenSSL, som \u00e4r designad f\u00f6r att motst\u00e5 tidsattacker. Implementera aldrig en egen byte-f\u00f6r-byte-j\u00e4mf\u00f6relse av signaturer med <code>===<\/code> eller <code>Buffer.compare()<\/code>. Anv\u00e4nd <code>crypto.timingSafeEqual()<\/code> om du beh\u00f6ver j\u00e4mf\u00f6ra r\u00e5a hashv\u00e4rden eller hemligheter av konstant l\u00e4ngd direkt i din kod.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-11-ed25519-signaturer-det-deterministiska-alternativet\">Steg 11: Ed25519-signaturer, det deterministiska alternativet<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ed25519 (EdDSA med Curve25519) skiljer sig fr\u00e5n ECDSA p\u00e5 ett fundamentalt s\u00e4tt: signeringen \u00e4r deterministisk. Samma privata nyckel och samma meddelande ger alltid exakt samma signatur. Det eliminerar hela klassen av nonce-\u00e5teranv\u00e4ndningsangrepp som drabbade ECDSA-implementationer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js 24 har dessutom ut\u00f6kat Ed25519-st\u00f6det med ett valfritt <code>context<\/code>-argument f\u00f6r dom\u00e4nseparation och st\u00f6d f\u00f6r r\u00e5a nyckelformat (<code>raw-private<\/code>, <code>raw-public<\/code>), tillagt i Node.js 26-grenen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import {\n  generateKeyPairSync,\n  sign,\n  verify,\n  createPrivateKey,\n  createPublicKey\n} from 'node:crypto';\nimport { writeFileSync, readFileSync } from 'node:fs';\n\n\/\/ Generera Ed25519-nyckelpar (mycket snabbt, deterministisk signering)\nconst { privateKey: privPem, publicKey: pubPem } = generateKeyPairSync('ed25519', {\n  publicKeyEncoding: { type: 'spki', format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }\n});\n\nwriteFileSync('src\/keys\/ed25519-private.pem', privPem, { mode: 0o600 });\nwriteFileSync('src\/keys\/ed25519-public.pem', pubPem);\n\nconst privKey = createPrivateKey(readFileSync('src\/keys\/ed25519-private.pem'));\nconst pubKey  = createPublicKey(readFileSync('src\/keys\/ed25519-public.pem'));\n\nconst message = Buffer.from('Hemlighetsfullt meddelande fr\u00e5n Stockholm');\n\n\/\/ Ed25519 anv\u00e4nder one-shot sign()\/verify() i st\u00e4llet f\u00f6r createSign\/createVerify\n\/\/ VIKTIGT: algoritm-argumentet M\u00c5STE vara null f\u00f6r Ed25519\nconst signature = sign(null, message, privKey);\nconsole.log('Ed25519-signatur (hex):', signature.toString('hex'));\nconsole.log('Signaturstorlek (byte):', signature.length); \/\/ Alltid exakt 64 byte\n\nconst isValid = verify(null, message, pubKey, signature);\nconsole.log('Verifiering giltig:', isValid); \/\/ true\n\n\/\/ Deterministisk egenskap: samma input ger alltid exakt samma signatur\nconst sig2 = sign(null, message, privKey);\nconsole.log('Deterministisk:', signature.equals(sig2)); \/\/ true (alltid)\n\n\/\/ Test: manipulerat meddelande\nconst tampered = Buffer.from('Hemlighetsfullt meddelande fr\u00e5n G\u00f6teborg');\nconsole.log('Manipulerat:', verify(null, tampered, pubKey, signature)); \/\/ false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ed25519 \u00e4r det rekommenderade valet f\u00f6r nya projekt d\u00e4r du kontrollerar b\u00e5da parter (till exempel intern mikrotj\u00e4nstkommunikation, SSH-nycklar, API-token). ECDSA P-256 \u00e4r det r\u00e4tta valet n\u00e4r du beh\u00f6ver interoperabilitet med befintlig TLS-infrastruktur, \u00e4ldre system eller plattformar som \u00e4nnu inte st\u00f6der Ed25519.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steg-12-nyckelhantering-i-pem-och-jwk-format\">Steg 12: Nyckelhantering i PEM- och JWK-format<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PEM-format fungerar utm\u00e4rkt f\u00f6r filbaserat nyckellagring och OpenSSL-interoperabilitet. JWK (JSON Web Key) \u00e4r standardformatet f\u00f6r nyckelutbyte via API:er och i JWT-ekosystemet. Node.js 24 kan exportera och importera b\u00e5da formaten.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { generateKeyPairSync, createPublicKey, createPrivateKey, createHash } from 'node:crypto';\n\n\/\/ Generera nyckelpar direkt i JWK-format\nconst { privateKey: privJwk, publicKey: pubJwk } = generateKeyPairSync('ec', {\n  namedCurve: 'P-256',\n  publicKeyEncoding: { type: 'jwk' },\n  privateKeyEncoding: { type: 'jwk' }\n});\n\nconsole.log('Publik JWK:\\n', JSON.stringify(pubJwk, null, 2));\n\/\/ {\n\/\/   \"kty\": \"EC\",\n\/\/   \"crv\": \"P-256\",\n\/\/   \"x\": \"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9OzXzlmWc\",\n\/\/   \"y\": \"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0\"\n\/\/ }\n\n\/\/ Konvertera PEM till JWK\nexport function pemToJwk(pemStr, type = 'public') {\n  const keyFn = type === 'public' ? createPublicKey : createPrivateKey;\n  return keyFn(pemStr).export({ format: 'jwk' });\n}\n\n\/\/ Konvertera JWK till PEM\nexport function jwkToPem(jwk, type = 'public') {\n  const keyFn = type === 'public' ? createPublicKey : createPrivateKey;\n  return keyFn({ key: jwk, format: 'jwk' }).export({\n    type: type === 'public' ? 'spki' : 'pkcs8',\n    format: 'pem'\n  });\n}\n\n\/\/ RFC 7638: nyckelfingeravtryck (Key ID) f\u00f6r JWKS-\u00e4ndam\u00e5l\nexport function keyThumbprint(jwk) {\n  const canonical = JSON.stringify({  \/\/ f\u00e4lten M\u00c5STE vara i alfabetisk ordning\n    crv: jwk.crv,\n    kty: jwk.kty,\n    x: jwk.x,\n    y: jwk.y\n  });\n  return createHash('sha256').update(canonical).digest('base64url');\n}\n\nconsole.log('Nyckel-ID (kid):', keyThumbprint(pubJwk));<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ecdsa-vs-rsa-vs-ed25519-prestandajamforelse\">ECDSA vs RSA vs Ed25519: prestandaj\u00e4mf\u00f6relse<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prestandaskillnaderna mellan algoritmer \u00e4r stora och relevanta f\u00f6r system med h\u00f6g genomstr\u00f6mning. Tabellen nedan visar relativa prestanda p\u00e5 modern server-h\u00e5rdvara. Exakta tal varierar med h\u00e5rdvara, Node.js-version och nyckelstorlek, men f\u00f6rh\u00e5llandena \u00e4r konsekventa och v\u00e4lk\u00e4nda fr\u00e5n OpenSSL-benchmarks.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Algoritm<\/th><th>Signering (relativ hastighet)<\/th><th>Verifiering (relativ hastighet)<\/th><th>Signaturstorlek<\/th><th>Nyckelgeneration<\/th><\/tr><\/thead><tbody><tr><td>Ed25519<\/td><td>Snabbast<\/td><td>Snabb<\/td><td>64 byte (fast)<\/td><td>Snabb (&lt;1 ms)<\/td><\/tr><tr><td>ECDSA P-256<\/td><td>Snabb (~80% av Ed25519)<\/td><td>Lite l\u00e5ngsammare<\/td><td>64-72 byte<\/td><td>Snabb (&lt;1 ms)<\/td><\/tr><tr><td>ECDSA P-384<\/td><td>Medel (~40% av Ed25519)<\/td><td>Medel<\/td><td>96-104 byte<\/td><td>Medel (~2 ms)<\/td><\/tr><tr><td>RSA-2048<\/td><td>L\u00e5ngsam (~4% av Ed25519)<\/td><td>Snabb (verifiering)<\/td><td>256 byte<\/td><td>L\u00e5ngsam (sekunder)<\/td><\/tr><tr><td>RSA-3072<\/td><td>Mycket l\u00e5ngsam<\/td><td>Snabb (verifiering)<\/td><td>384 byte<\/td><td>Mycket l\u00e5ngsam<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">RSA-signering \u00e4r 20-25 g\u00e5nger l\u00e5ngsammare \u00e4n Ed25519-signering. RSA-verifiering \u00e4r dock relativt snabb eftersom verifieringen bara kr\u00e4ver en potensupph\u00f6jning med den lilla publika exponenten (typiskt 65537). F\u00f6r system som signerar ofta men verifierar s\u00e4llan \u00e4r RSA-signering en flaskhals. F\u00f6r TLS-handskakningar, API-signering, JWT-generering och kodnyckelrotation \u00e4r Ed25519 eller ECDSA P-256 alltid det r\u00e4tta valet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js 24 med OpenSSL 3.5 drar full nytta av h\u00e5rdvaruaccelerering. P\u00e5 processorer med AVX2-st\u00f6d (Intel Haswell och senare, AMD Zen och senare) k\u00f6rs Ed25519-operationer delvis i parallell via SIMD-instruktioner. Det \u00e4r ytterligare ett sk\u00e4l att v\u00e4lja moderna elliptiska kurvor framf\u00f6r RSA.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vanliga-fallgropar-och-sakerhetsrisker\">Vanliga fallgropar och s\u00e4kerhetsrisker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Digital signering i Node.js \u00e4r enkel att implementera fel. Dessa sex misstag orsakar de allvarligaste s\u00e5rbarheterna.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fallgrop-1-ateranvandning-av-ecdsa-nonce-k-varde\">Fallgrop 1: \u00c5teranv\u00e4ndning av ECDSA-nonce (k-v\u00e4rde)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Det farligaste m\u00f6jliga felet med ECDSA \u00e4r att anv\u00e4nda samma nonce k f\u00f6r tv\u00e5 signaturer med samma privata nyckel. Det avsl\u00f6jar omedelbart den privata nyckeln via enkel algebra. Det \u00e4r precis vad som h\u00e4nde med PlayStation 3 2010 och med den tidiga Bitcoin-klienten p\u00e5 Android. Node.js hanterar k-generering automatiskt via OpenSSL:s CSPRNG, men om du av misstag h\u00e5rdkodar k i ett test eller porterar en C-implementation felaktigt \u00e4r konsekvensen total kompromiss av den privata nyckeln. Anv\u00e4nd alltid Node.js-inbyggda signeringsrutiner och implementera aldrig ECDSA manuellt. Ed25519 eliminerar detta problem helt eftersom signeringen \u00e4r deterministisk.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fallgrop-2-anvanda-sha-1-eller-md5-med-ecdsa\">Fallgrop 2: Anv\u00e4nda SHA-1 eller MD5 med ECDSA<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js accepterar tekniskt <code>createSign('SHA1')<\/code> med ECDSA-nycklar i \u00e4ldre versioner, men OpenSSL 3.5 p\u00e5 s\u00e4kerhetsniv\u00e5 2 (standardl\u00e4ge i Node.js 24) blockerar SHA-1 och MD5. Mer grundl\u00e4ggande: SHA-1 \u00e4r kryptoanalytiskt bruten sedan SHAttered-kollisionen 2017. Anv\u00e4nd alltid SHA-256 med P-256 och SHA-384 med P-384. Se mer om SHA-kollisioner i <a href=\"\/sha1-kollision\/\">SHAttered: den f\u00f6rsta praktiska SHA-1-kollisionen<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fallgrop-3-jamfora-signaturer-med-eller-buffer-compare\">Fallgrop 3: J\u00e4mf\u00f6ra signaturer med === eller Buffer.compare()<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Om du manuellt j\u00e4mf\u00f6r signaturer med <code>sigA === sigB<\/code> \u00e4r koden s\u00e5rbar f\u00f6r tidsattacker. Angriparen kan m\u00e4ta hur l\u00e5ng tid j\u00e4mf\u00f6relsen tar och byte-f\u00f6r-byte sluta sig till den f\u00f6rv\u00e4ntade signaturen. Anv\u00e4nd alltid <code>crypto.timingSafeEqual()<\/code> f\u00f6r byte-f\u00f6r-byte-k\u00e4nsliga j\u00e4mf\u00f6relser, eller l\u00e5t <code>createVerify().verify()<\/code> g\u00f6ra hela jobbet.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fallgrop-4-lagra-den-privata-nyckeln-i-miljovariabler-som-klartext\">Fallgrop 4: Lagra den privata nyckeln i milj\u00f6variabler som klartext<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Milj\u00f6variabler \u00e4r synliga i procestabellen, loggar, core-dumpar och <code>docker inspect<\/code>-utdata. En PEM-privat nyckel p\u00e5 227 tecken i en milj\u00f6variabel \u00e4r ett vanligt s\u00e4kerhetsproblem som dyker upp i kodgranskningar. Kryptera alltid privata nycklar med en stark l\u00f6senfras (<code>cipher: 'aes-256-cbc'<\/code>) och lagra l\u00f6senfransen i en dedikerad hemlighetshanterare. Alternativt, anv\u00e4nd ett HSM eller molnnyckeltj\u00e4nst (AWS KMS, Google Cloud KMS, Azure Key Vault) d\u00e4r den privata nyckeln aldrig l\u00e4mnar h\u00e5rdvaran.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fallgrop-5-blanda-der-och-ieee-p1363-signatuurformat\">Fallgrop 5: Blanda DER- och IEEE-P1363-signatuurformat<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js st\u00f6der tv\u00e5 signatuurformat f\u00f6r ECDSA: <code>der<\/code> (standard, ASN.1-kodning, variabel l\u00e4ngd) och <code>ieee-p1363<\/code> (r||s, fast l\u00e4ngd). En P-256-signatur \u00e4r 64 byte i ieee-p1363 men 70-72 byte i DER. Om du signerar med ett format och verifierar med ett annat misslyckas verifieringen med ett kryptiskt fel. V\u00e4lj ett format och dokumentera det i din kod.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fallgrop-6-skapa-keyobject-vid-varje-request\">Fallgrop 6: Skapa KeyObject vid varje request<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Att anropa <code>createPrivateKey(readFileSync(...))<\/code> vid varje inkommande request inneb\u00e4r att du l\u00e4ser disk och parsear PEM-data upprepat. I ett system med 1000 requests\/sekund medf\u00f6r det on\u00f6dig I\/O och CPU-overhead. Ladda och cachelagra <code>KeyObject<\/code> en g\u00e5ng vid serverstart och \u00e5teranv\u00e4nd det f\u00f6r alla signeringsoperationer.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Risk<\/th><th>Allvarlighet<\/th><th>\u00c5tg\u00e4rd<\/th><\/tr><\/thead><tbody><tr><td>ECDSA nonce-\u00e5teranv\u00e4ndning<\/td><td>Kritisk<\/td><td>Anv\u00e4nd Node.js inbyggd signering<\/td><\/tr><tr><td>SHA-1 med ECDSA<\/td><td>H\u00f6g<\/td><td>Kr\u00e4v SHA-256 eller SHA-384<\/td><\/tr><tr><td>Tidsbaserad signaturj\u00e4mf\u00f6relse<\/td><td>Medium<\/td><td>Anv\u00e4nd timingSafeEqual() eller verify()<\/td><\/tr><tr><td>Privat nyckel i env-variabel<\/td><td>H\u00f6g<\/td><td>Kryptera nyckel, anv\u00e4nd secrets manager<\/td><\/tr><tr><td>Blandat DER\/P1363-format<\/td><td>Medium<\/td><td>Definiera ett format, dokumentera det<\/td><\/tr><tr><td>KeyObject-parsning per request<\/td><td>L\u00e5g<\/td><td>Cachelagra KeyObject vid serverstart<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"komplett-projekt-api-signeringstjanst-med-express-js\">Komplett projekt: API-signeringstj\u00e4nst med Express.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Det h\u00e4r projektet implementerar en Express.js-tj\u00e4nst som signerar API-svar med ECDSA P-256 och verifierar inkommande signerade f\u00f6rfr\u00e5gningar. Det \u00e4r en grundl\u00e4ggande arkitektur f\u00f6r s\u00e4ker kommunikation mellan mikrotj\u00e4nster.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/api\/server.js\nimport express from 'express';\nimport { createSign, createVerify, createPrivateKey, createPublicKey } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { body, validationResult } from 'express-validator';\n\nconst app = express();\napp.use(express.json({ limit: '10kb' }));\n\n\/\/ Ladda nycklar en g\u00e5ng vid start (inte vid varje request)\nconst PRIVATE_KEY = createPrivateKey(readFileSync('src\/keys\/p256-private.pem'));\nconst PUBLIC_KEY  = createPublicKey(readFileSync('src\/keys\/p256-public.pem'));\n\nfunction signPayload(payload) {\n  const sign = createSign('SHA256');\n  sign.update(typeof payload === 'string' ? payload : JSON.stringify(payload));\n  sign.end();\n  return sign.sign({ key: PRIVATE_KEY, dsaEncoding: 'ieee-p1363' }, 'base64url');\n}\n\nfunction verifyPayload(payload, signature) {\n  try {\n    const verify = createVerify('SHA256');\n    verify.update(typeof payload === 'string' ? payload : JSON.stringify(payload));\n    verify.end();\n    return verify.verify(\n      { key: PUBLIC_KEY, dsaEncoding: 'ieee-p1363' },\n      signature,\n      'base64url'\n    );\n  } catch {\n    return false;\n  }\n}\n\n\/\/ Middleware: verifiera inkommande signerade f\u00f6rfr\u00e5gningar\nfunction requireSignature(req, res, next) {\n  const signature = req.headers['x-signature'];\n  const timestamp = req.headers['x-timestamp'];\n\n  if (!signature || !timestamp) {\n    return res.status(401).json({ error: 'X-Signature och X-Timestamp \u00e4r obligatoriska' });\n  }\n\n  \/\/ Avvisa tidsst\u00e4mplar utanf\u00f6r 60-sekunders f\u00f6nster (f\u00f6rhindrar replay-attacker)\n  if (Math.abs(Date.now() - parseInt(timestamp)) > 60_000) {\n    return res.status(401).json({ error: 'Tidsst\u00e4mpel utanf\u00f6r 60-sekunders f\u00f6nster' });\n  }\n\n  const canonicalPayload = JSON.stringify(req.body) + timestamp;\n  if (!verifyPayload(canonicalPayload, signature)) {\n    return res.status(401).json({ error: 'Ogiltig signatur' });\n  }\n\n  next();\n}\n\n\/\/ GET \/api\/public-key: publicera publik nyckel f\u00f6r klienter att h\u00e4mta och cacha\napp.get('\/api\/public-key', (req, res) => {\n  res.json({\n    format: 'jwk',\n    key: PUBLIC_KEY.export({ format: 'jwk' }),\n    algorithm: 'ES256',\n    curve: 'P-256'\n  });\n});\n\n\/\/ POST \/api\/sign: signera ett meddelande\napp.post('\/api\/sign',\n  body('message').isString().trim().notEmpty().isLength({ max: 10000 }),\n  (req, res) => {\n    const errors = validationResult(req);\n    if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });\n\n    const { message } = req.body;\n    const timestamp = Date.now().toString();\n    const canonical = message + timestamp;\n    const signature = signPayload(canonical);\n\n    res.json({ message, signature, timestamp, algorithm: 'ES256' });\n  }\n);\n\n\/\/ POST \/api\/verify: verifiera ett signerat meddelande\napp.post('\/api\/verify',\n  body('message').isString().notEmpty(),\n  body('signature').isString().notEmpty(),\n  body('timestamp').isNumeric(),\n  (req, res) => {\n    const errors = validationResult(req);\n    if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });\n\n    const { message, signature, timestamp } = req.body;\n\n    if (Math.abs(Date.now() - parseInt(timestamp)) > 300_000) {\n      return res.status(400).json({ valid: false, reason: 'Signatur \u00e4ldre \u00e4n 5 minuter' });\n    }\n\n    const canonical = message + timestamp;\n    const valid = verifyPayload(canonical, signature);\n    res.json({ valid, message, timestamp });\n  }\n);\n\n\/\/ POST \/api\/protected: skyddad endpoint som kr\u00e4ver giltig signatur\napp.post('\/api\/protected', requireSignature, (req, res) => {\n  res.json({ success: true, data: req.body, message: 'Autentiserad f\u00f6rfr\u00e5gan processad' });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () => {\n  console.log(`API-signeringstj\u00e4nst k\u00f6rs p\u00e5 port ${PORT}`);\n  console.log(`Publik nyckel: http:\/\/localhost:${PORT}\/api\/public-key`);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Testa tj\u00e4nsten med curl:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Starta servern\nnpm start\n\n# H\u00e4mta publik nyckel i JWK-format\ncurl -s http:\/\/localhost:3000\/api\/public-key | python3 -m json.tool\n\n# Signera ett meddelande\ncurl -s -X POST http:\/\/localhost:3000\/api\/sign \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\"message\": \"Viktig API-beg\u00e4ran 2026-06-17\"}' | python3 -m json.tool\n\n# F\u00f6rv\u00e4ntad respons:\n# {\n#   \"message\": \"Viktig API-beg\u00e4ran 2026-06-17\",\n#   \"signature\": \"mT9xRkL2...base64url...\",\n#   \"timestamp\": \"1781700000000\",\n#   \"algorithm\": \"ES256\"\n# }\n\n# Verifiera signaturen (ers\u00e4tt med faktiska v\u00e4rden fr\u00e5n f\u00f6reg\u00e5ende svar)\ncurl -s -X POST http:\/\/localhost:3000\/api\/verify \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\n    \"message\": \"Viktig API-beg\u00e4ran 2026-06-17\",\n    \"signature\": \"mT9xRkL2...base64url...\",\n    \"timestamp\": \"1781700000000\"\n  }' | python3 -m json.tool\n\n# { \"valid\": true, \"message\": \"...\", \"timestamp\": \"...\" }<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"felsokning-8-vanliga-problem-och-losningar\">Fels\u00f6kning: 8 vanliga problem och l\u00f6sningar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">H\u00e4r \u00e4r de \u00e5tta vanligaste problemen vid implementering av ECDSA i Node.js och hur du l\u00f6ser dem.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 1: ERR_OSSL_EVP_UNSUPPORTED<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Felet uppst\u00e5r n\u00e4r du f\u00f6rs\u00f6ker anv\u00e4nda en kryptoalgoritm blockerad av OpenSSL:s aktiva konfiguration. Vanligaste orsak: P-192 eller en annan kurva under 224 bitar som OpenSSL 3.5 blockerar vid s\u00e4kerhetsniv\u00e5 2. L\u00f6sning: byt till P-256 eller P-384. Tempor\u00e4r workaround f\u00f6r testmilj\u00f6er: <code>NODE_OPTIONS=--openssl-legacy-provider<\/code> men g\u00f6r aldrig det i produktion.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 2: Error: error:0200100D:rsa routines::key size too small<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">OpenSSL 3.5 i Node.js 24 till\u00e5ter inte RSA-nycklar kortare \u00e4n 2048 bitar vid s\u00e4kerhetsniv\u00e5 2. Gammal kod som genererar RSA-1024-nycklar misslyckas. L\u00f6sning: uppgradera till RSA-2048 minimum, eller byt till ECDSA P-256 som ger samma s\u00e4kerhetsniv\u00e5 med en 8 g\u00e5nger kortare nyckel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 3: Verifieringen returnerar alltid false<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vanligaste orsak: du signerade med <code>dsaEncoding: 'ieee-p1363'<\/code> men verifierar med standard-<code>'der'<\/code> (eller vice versa). Dubbelkolla att samma <code>dsaEncoding<\/code>-v\u00e4rde anv\u00e4nds i b\u00e5de signerings- och verifieringsanropet. En annan vanlig orsak: meddelandet kodas som UTF-8-str\u00e4ng vid signering men som Buffer vid verifiering, vilket ger olika byte-sekvenser f\u00f6r icke-ASCII-tecken.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 4: Error: error:09091064:PEM routines:PEM_read_bio:no start line<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">PEM-filen \u00e4r skadad, trunkerad eller har fel radbrytningar (CRLF i st\u00e4llet f\u00f6r LF p\u00e5 Windows). Kontrollera filen med <code>openssl ec -in p256-private.pem -noout -text<\/code>. Om filen l\u00e4ses via milj\u00f6variabel kan <code>\\n<\/code>-tecken ha konverterats till bokstavliga n. L\u00f6sning: l\u00e4s alltid PEM-filer fr\u00e5n disk med <code>readFileSync<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 5: TypeError: The &#8220;algorithm&#8221; argument is invalid for Ed25519<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00f6r Ed25519 <strong>m\u00e5ste<\/strong> algoritm-argumentet i <code>sign()<\/code> och <code>verify()<\/code> vara <code>null<\/code>. Ed25519 specificerar sin interna hashfunktion som en del av algoritmdefinitionen. Om du skickar <code>'SHA256'<\/code> kastar Node.js ett fel. Kontrollera att du inte blandar ihop ECDSA-kod med Ed25519-kod.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 6: Mycket l\u00e5ngsam RSA-nyckelgenerering blockerar event loop<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">RSA-nyckelgenerering (>= 3072 bitar) kan ta 1-3 sekunder och blockerar event loop om du anv\u00e4nder <code>generateKeyPairSync()<\/code>. L\u00f6sning: anv\u00e4nd alltid den asynkrona <code>generateKeyPair()<\/code>-varianten f\u00f6r RSA. F\u00f6r ECDSA och Ed25519 \u00e4r nyckelgenereringen tillr\u00e4ckligt snabb f\u00f6r synkron version vid serverstart, men aldrig i request handlers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 7: JWK-import misslyckas med ERR_CRYPTO_INVALID_JWK<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">JWK-validering i Node.js 24 \u00e4r strikt. Vanligaste orsaker: saknat <code>crv<\/code>-f\u00e4lt, felaktig Base64url-kodning av x\/y-koordinaterna, eller att du f\u00f6rs\u00f6ker importera en privat JWK (med <code>d<\/code>-f\u00e4ltet) med <code>createPublicKey()<\/code>. L\u00f6sning: validera alltid JWK-objektet innan import och anv\u00e4nd <code>createPrivateKey()<\/code> f\u00f6r privata JWK:ar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problem 8: Signaturen skiljer sig \u00e5t vid upprepade anrop med ECDSA P-256<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Det \u00e4r normalt och f\u00f6rv\u00e4ntat! ECDSA genererar ett nytt slumpm\u00e4ssigt k-v\u00e4rde (nonce) f\u00f6r varje signatur. Varje signatur av samma meddelande med samma nyckel ser annorlunda ut men verifieras korrekt. Det \u00e4r en grundl\u00e4ggande s\u00e4kerhetsegenskap. Om du beh\u00f6ver deterministiska signaturer (samma input ger alltid samma output), anv\u00e4nd Ed25519 i st\u00e4llet.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"avancerade-tips-for-produktion\">Avancerade tips f\u00f6r produktion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dessa rekommendationer g\u00e4ller system som hanterar signaturer i skarp drift med h\u00f6ga krav p\u00e5 tillg\u00e4nglighet och s\u00e4kerhet.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"nyckelrotation-utan-nedtid-med-jwks\">Nyckelrotation utan nedtid med JWKS<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Implementera nyckelrotation med ett JWKS-endpoint (JSON Web Key Set) som exponerar flera aktiva publika nycklar. Varje signatur inkluderar ett <code>kid<\/code>-f\u00e4lt (Key ID, ber\u00e4knat med RFC 7638-fingeravtrycket fr\u00e5n f\u00f6reg\u00e5ende steg) som pekar p\u00e5 vilket nyckelpar som anv\u00e4ndes. Verifieraren h\u00e4mtar JWKS och v\u00e4ljer r\u00e4tt publik nyckel baserat p\u00e5 <code>kid<\/code>. Ny nyckel l\u00e4ggs till JWKS innan den b\u00f6rjar anv\u00e4ndas f\u00f6r signering. Gammal nyckel tas bort fr\u00e5n JWKS efter att alla signaturer med sin maximala livsl\u00e4ngd har l\u00f6pt ut. Ett typiskt rotationsintervall f\u00f6r API-signeringsnycklar \u00e4r 90 dagar. <a href=\"https:\/\/nodejs.org\/en\/learn\/getting-started\/security-best-practices\" rel=\"noopener noreferrer\" target=\"_blank\">Node.js Security Best Practices<\/a> rekommenderar regelbunden nyckelrotation f\u00f6r alla produktionssystem.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"fips-140-3-lage-i-node-js-24\">FIPS 140-3-l\u00e4ge i Node.js 24<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js 24 med OpenSSL 3.5 st\u00f6der FIPS 140-3-l\u00e4ge f\u00f6r myndigheter och finanssystem som kr\u00e4ver certifierad kryptografi. Aktivera med <code>--enable-fips<\/code>-flaggan eller <code>crypto.setFips(1)<\/code>. I FIPS-l\u00e4ge blockeras alla icke-godk\u00e4nda algoritmer, inklusive MD5, SHA-1 och RC4. ECDSA P-256, P-384 och Ed25519 \u00e4r alla godk\u00e4nda under FIPS 140-3. L\u00e4s mer i <a href=\"https:\/\/www.openssl.org\/news\/changelog.html\" rel=\"noopener noreferrer\" target=\"_blank\">OpenSSL 3.5 changelog<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"signaturtidsstamplar-och-replay-skydd\">Signaturtidsst\u00e4mplar och replay-skydd<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">En giltig signatur \u00e4r giltig f\u00f6r alltid om du inte l\u00e4gger till en tidsdimension. Inkludera alltid en <code>timestamp<\/code> (Unix-millisekunder) och ett <code>nonce<\/code> (kryptografisk slumpbytes, 16 byte) i det signerade meddelandet. Servern avvisar signaturer \u00e4ldre \u00e4n 60-300 sekunder och kontrollerar att nonce inte redan anv\u00e4nts (med Redis eller en in-memory-m\u00e4ngd med TTL). Det \u00e4r precis vad API-signeringstj\u00e4nsten i det fullst\u00e4ndiga projektet ovan implementerar.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"post-kvantum-forberedelser-med-node-js-24\">Post-kvantum f\u00f6rberedelser med Node.js 24<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js 24 introducerade st\u00f6d f\u00f6r ML-DSA (CRYSTALS-Dilithium), NIST:s standardiserade post-kvantumsigneringsalgoritm, via <code>crypto.sign()<\/code> och <code>crypto.verify()<\/code> med nyckeltypen <code>ml-dsa44<\/code>, <code>ml-dsa65<\/code> eller <code>ml-dsa87<\/code>. ML-DSA-signaturer \u00e4r 2420-4627 byte j\u00e4mf\u00f6rt med 64 byte f\u00f6r Ed25519, men de t\u00e5l angrepp fr\u00e5n kvantnodatorer. F\u00f6r system med 10+ \u00e5r livsl\u00e4ngd \u00e4r det v\u00e4rt att planera en hybridmigrering med b\u00e5de ECDSA och ML-DSA i parallell, det som kallas <em>hybrid post-quantum<\/em>. Bakgrunden till NIST:s val finns p\u00e5 <a href=\"https:\/\/csrc.nist.gov\/publications\/detail\/fips\/186\/5\/final\" rel=\"noopener noreferrer\" target=\"_blank\">NIST FIPS 186-5-sidan<\/a>. OWASP:s rekommendationer f\u00f6r kryptografisk lagring finns p\u00e5 <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Cryptographic_Storage_Cheat_Sheet.html\" rel=\"noopener noreferrer\" target=\"_blank\">OWASP Cryptographic Storage Cheat Sheet<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"relaterade-artiklar\">Relaterade artiklar<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"relaterad-lasning-pa-shattered-io\">Relaterad l\u00e4sning p\u00e5 shattered.io<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/openssl-nycklar-certifikat\/\">OpenSSL 3.5: nycklar och certifikat i 12 steg [2026]<\/a> \u2014 generera X.509-certifikat och hantera CA-kedjor med OpenSSL-kommandoraden.<\/li>\n\n\n\n<li><a href=\"\/hmac-sha256-nodejs\/\">HMAC-SHA256 i Node.js: 10 steg, 20 min [2026]<\/a> \u2014 meddelandeautentiseringskoder f\u00f6r API-s\u00e4kerhet utan asymmetrisk kryptografi.<\/li>\n\n\n\n<li><a href=\"\/aes-256-encryption-nodejs\/\">AES-256-kryptering i Node.js: 12 steg [2026]<\/a> \u2014 symmetrisk kryptering f\u00f6r skydd av data i vila.<\/li>\n\n\n\n<li><a href=\"\/rsa-encryption-nodejs\/\">RSA-kryptering i Node.js: 11 steg [2026]<\/a> \u2014 asymmetrisk kryptering och nyckelutbyte med RSA.<\/li>\n\n\n\n<li><a href=\"\/sha-256\/\">SHA-256 f\u00f6rklarad: 256-bitars fingeravtryck i SHA-2<\/a> \u2014 det matematiska fundamentet bakom ECDSA:s hashsteg.<\/li>\n\n\n\n<li><a href=\"\/cryptography-hub\/\">Kryptografi: hashfunktioner, SHA och digitalt f\u00f6rtroende<\/a> \u2014 \u00f6vergripande guide till kryptografiska grunder.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vanliga-fragor-om-ecdsa-och-digitala-signaturer-i-node-js\">Vanliga fr\u00e5gor om ECDSA och digitala signaturer i Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"vilken-kurva-ska-jag-anvanda-p-256-eller-p-384\">Vilken kurva ska jag anv\u00e4nda, P-256 eller P-384?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">V\u00e4lj P-256 f\u00f6r de flesta \u00e4ndam\u00e5l, inklusive API-signering, JWT och TLS-certifikat. P-256 ger 128 bitars s\u00e4kerhetsniv\u00e5, \u00e4r h\u00e5rdvaruaccelererad p\u00e5 moderna processorer och st\u00f6ds universellt i webbl\u00e4sare och plattformar. V\u00e4lj P-384 om ditt system hanterar data med l\u00e4ngre klassificeringskrav (statlig sekretess, finansiella transaktioner med 15+ \u00e5r lagringskrav) eller om s\u00e4kerhetspolicyn kr\u00e4ver 192 bitars s\u00e4kerhetsniv\u00e5.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"vad-ar-skillnaden-mellan-ecdsa-och-hmac\">Vad \u00e4r skillnaden mellan ECDSA och HMAC?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC (Hash-based Message Authentication Code) \u00e4r ett symmetriskt schema: b\u00e5da parter delar en hemlig nyckel. Det bevisar integritet och autenticitet men kr\u00e4ver att mottagaren k\u00e4nner till den hemliga nyckeln, vilket inneb\u00e4r att mottagaren tekniskt kan skapa en ny giltig HMAC. ECDSA \u00e4r asymmetriskt: bara innehavaren av den privata nyckeln kan skapa en signatur, men vem som helst med den publika nyckeln kan verifiera den. ECDSA ger \u00e4kta oavvislighet (non-repudiation). V\u00e4lj HMAC f\u00f6r intern kommunikation mellan betrodda tj\u00e4nster; v\u00e4lj ECDSA n\u00e4r du beh\u00f6ver bevis som kan presenteras f\u00f6r tredje part.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ska-jag-anvanda-nodecrypto-eller-ett-externt-bibliotek-som-elliptic\">Ska jag anv\u00e4nda node:crypto eller ett externt bibliotek som elliptic?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Anv\u00e4nd alltid <code>node:crypto<\/code> f\u00f6r produktionskod p\u00e5 server-sidan. Det \u00e4r underh\u00e5llet av Node.js-teamet, h\u00e5ller sig uppdaterat med OpenSSL-s\u00e4kerhetsuppdateringar och granskas av kryptografer. Biblioteket <code>elliptic<\/code> \u00e4r ett tredjepartsbibliotek med \u00f6ppna underh\u00e5llsfr\u00e5gor som identifierades 2024. Det finns legitima anv\u00e4ndningsfall f\u00f6r tredjepartsbibliotek i webbl\u00e4sarmilj\u00f6n (f\u00f6re WebCrypto-st\u00f6d var fullt utbrett), men p\u00e5 server-sidan \u00e4r <code>node:crypto<\/code> alltid det r\u00e4tta valet.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kan-ecdsa-signaturer-verifieras-av-andra-programmeringssprak\">Kan ECDSA-signaturer verifieras av andra programmeringsspr\u00e5k?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ja. ECDSA med P-256 och SHA-256 (ES256 i JWA-terminologi) \u00e4r en \u00f6ppen standard som implementeras i Go (<code>crypto\/ecdsa<\/code>), Python (<code>cryptography<\/code>-paketet), Java (<code>java.security<\/code>), Rust (<code>p256<\/code>-crate), C\/C++ (OpenSSL) och praktiskt taget alla moderna programmeringsspr\u00e5k. Nyckeln \u00e4r att anv\u00e4nda standardiserade format (PEM, JWK) och dokumentera vilken signaturkodning du valt (DER eller IEEE P1363). JWK-format med base64url-kodning ger b\u00e4st portabilitet i API-sammanhang.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hur-lang-tid-tar-ecdsa-signering-i-node-js\">Hur l\u00e5ng tid tar ECDSA-signering i Node.js?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">P\u00e5 moderna serverprocessorer tar en P-256-signering med Node.js typiskt under 0,1 millisekunder. Ed25519-signering \u00e4r marginellt snabbare. Tiotusentals signeringsoperationer per sekund \u00e4r m\u00f6jliga i en enda tr\u00e5d. Nyckelgenerering tar l\u00e4ngre (millisekunder f\u00f6r EC, sekunder f\u00f6r RSA) men den b\u00f6r bara ske en g\u00e5ng vid serverstart. Att skapa ett nytt <code>KeyObject<\/code> fr\u00e5n PEM-text vid varje request kostar on\u00f6dig CPU; cachelagra objektet vid uppstart.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"vad-hander-med-ecdsa-nar-kvantnodatorer-blir-tillgangliga\">Vad h\u00e4nder med ECDSA n\u00e4r kvantnodatorer blir tillg\u00e4ngliga?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Shors algoritm p\u00e5 en tillr\u00e4ckligt stor kvantnodator bryter ECDSA, RSA och alla algoritmer baserade p\u00e5 diskret logaritmproblemet. NIST publicerade i 2024 slutliga standarder f\u00f6r post-kvantums\u00e4kra algoritmer: ML-DSA (signaturer) och ML-KEM (nyckelkapsling). Node.js 24 st\u00f6der b\u00e5da. F\u00f6r system med data som beh\u00f6ver skyddas i 10+ \u00e5r b\u00f6r en migrationsplan finnas. F\u00f6r typiska API-signaturer med korta livsl\u00e4ngder \u00e4r ECDSA P-256 s\u00e4kert i det f\u00f6rutsebara perspektivet fram till 2030 och troligtvis l\u00e4ngre.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hur-verifierar-jag-att-min-implementation-ar-korrekt\">Hur verifierar jag att min implementation \u00e4r korrekt?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Skriv tester som verifierar dessa fem scenarion: (1) en giltig signatur returnerar true, (2) ett manipulerat meddelande returnerar false, (3) en slumpm\u00e4ssig signatur returnerar false, (4) en signatur skapad med en annan privat nyckel returnerar false, och (5) signaturen har f\u00f6rv\u00e4ntad storlek och format. Dessa fem tester t\u00e4cker de vanligaste implementationsfelen. K\u00f6r dessutom dina tester mot k\u00e4nda testvektorer fr\u00e5n NIST CAVP-programmet om ditt system kr\u00e4ver formell validering.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>En digital signatur bevisar att ett meddelande kom fr\u00e5n en specifik avs\u00e4ndare och inte \u00e4ndrades under v\u00e4gen. \u00c5r 2026 signeras allt fr\u00e5n Kubernetes-manifest och firmware-uppdateringar till API-svar och npm-paket med\u2026<\/p>\n","protected":false},"author":7,"featured_media":100,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,2],"tags":[],"class_list":["post-99","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-10","category-cryptography"],"_links":{"self":[{"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts\/99","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/comments?post=99"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts\/99\/revisions"}],"predecessor-version":[{"id":101,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/posts\/99\/revisions\/101"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/media\/100"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/media?parent=99"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/categories?post=99"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/se\/wp-json\/wp\/v2\/tags?post=99"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}