{"id":83,"date":"2026-06-14T20:55:39","date_gmt":"2026-06-14T20:55:39","guid":{"rendered":"https:\/\/shattered.io\/fi\/2026\/06\/14\/hmac-node-js\/"},"modified":"2026-06-14T20:57:00","modified_gmt":"2026-06-14T20:57:00","slug":"hmac-node-js","status":"publish","type":"post","link":"https:\/\/shattered.io\/fi\/hmac-node-js\/","title":{"rendered":"HMAC Node.js:ss\u00e4: 10 vaihetta, 30 min [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Viestin eheys ja alkuper\u00e4n todennus ovat web-sovellusten n\u00e4kym\u00e4tt\u00f6mi\u00e4 peruspilareita. Kun Stripe l\u00e4hett\u00e4\u00e4 webhookin maksusta, kun GitHub ilmoittaa uudesta pushista tai kun kaksi mikropalvelua vaihtaa tietoja, vastaanottajan on voitava varmistua kahdesta asiasta: dataa ei ole muutettu matkalla, ja se tulee oikealta l\u00e4hett\u00e4j\u00e4lt\u00e4. HMAC (Hash-based Message Authentication Code) ratkaisee molemmat ongelmat yhdell\u00e4 kompaktilla tunnisteella. T\u00e4ss\u00e4 oppaassa rakennat Node.js:ll\u00e4 toimivan HMAC-todennuksen alusta tuotantokuntoon 10 vaiheessa, noin 30 minuutissa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js sis\u00e4lt\u00e4\u00e4 HMAC-tuen suoraan vakiomoduulissa <code>node:crypto<\/code>, joten ulkoisia kirjastoja ei tarvita. K\u00e4ymme l\u00e4pi avaimen turvallisen generoinnin, aikahy\u00f6kk\u00e4yksilt\u00e4 suojaavan vertailun <code>crypto.timingSafeEqual<\/code>-funktiolla, webhookien allekirjoituksen varmennuksen sek\u00e4 selainyhteensopivan WebCrypto-toteutuksen. Lopuksi kokoamme t\u00e4ydellisen Express-projektin, kirjoitamme testit ja k\u00e4ymme l\u00e4pi yli kahdeksan vianm\u00e4\u00e4rityskohtaa. P\u00e4ivitetty 14. kes\u00e4kuuta 2026.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"mika-hmac-on-ja-miksi-sita-tarvitaan-node-jsssa\">Mik\u00e4 HMAC on ja miksi sit\u00e4 tarvitaan Node.js:ss\u00e4<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC on standardin RFC 2104 m\u00e4\u00e4rittelem\u00e4 avaimellinen viestintodennuskoodi. Se yhdist\u00e4\u00e4 kryptografisen tiivistefunktion (kuten SHA-256) ja salaisen avaimen tuottaakseen kiinte\u00e4mittaisen tunnisteen, jota kutsutaan tagiksi. Vain ne osapuolet, jotka tuntevat saman salaisen avaimen, voivat tuottaa kelvollisen tagin tietylle viestille. T\u00e4m\u00e4 erottaa HMAC:n tavallisesta tiivisteest\u00e4: pelkk\u00e4 SHA-256 todistaa vain, ett\u00e4 data ei ole muuttunut, kun taas HMAC todistaa lis\u00e4ksi, ett\u00e4 data tulee avaimen haltijalta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC-rakenne on suunniteltu kest\u00e4m\u00e4\u00e4n niin sanottuja pituuslaajennushy\u00f6kk\u00e4yksi\u00e4 (length extension attacks), jotka vaivaavat naiiveja toteutuksia tyyppi\u00e4 <code>hash(salaisuus + viesti)<\/code>. RFC 2104:n mukainen rakenne k\u00e4ytt\u00e4\u00e4 avainta kahdessa kierroksessa sisemm\u00e4n ja ulomman padding-arvon (ipad ja opad) kanssa, mik\u00e4 tekee siit\u00e4 todistettavasti turvallisen, kun taustalla oleva tiivistefunktio on vahva. Juuri t\u00e4st\u00e4 syyst\u00e4 HMAC-SHA256 on alan de facto -standardi rajapintojen allekirjoituksessa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js:ss\u00e4 HMAC on osa <code>node:crypto<\/code>-moduulia, joka on k\u00e4\u00e4re OpenSSL:n tiiviste-, HMAC-, salaus- ja allekirjoitustoiminnoille. Moduuli on vakaa ja saatavilla ilman asennuksia. Node.js 24.x &#8220;Krypton&#8221; siirtyi pitk\u00e4aikaistuettuun LTS-vaiheeseen 28. lokakuuta 2025, ja se saa tukea huhtikuuhun 2028 asti, joten se on suositeltava ajoymp\u00e4rist\u00f6 tuotantoon vuonna 2026. Uudemmissa julkaisuissa (dokumentaatio kattaa jo v26-sarjan) crypto-rajapinta on yhtenev\u00e4inen, joten t\u00e4m\u00e4n oppaan koodi toimii sellaisenaan sek\u00e4 Node 24 LTS:ss\u00e4 ett\u00e4 tuoreemmissa versioissa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00e4yt\u00e4nn\u00f6n k\u00e4ytt\u00f6kohteita on runsaasti. Stripe allekirjoittaa webhook-tapahtumat HMAC-SHA256:lla ja toimittaa allekirjoituksen <code>Stripe-Signature<\/code>-otsakkeessa. GitHub k\u00e4ytt\u00e4\u00e4 samaa algoritmia ja l\u00e4hett\u00e4\u00e4 tagin <code>X-Hub-Signature-256<\/code>-otsakkeessa. Lis\u00e4ksi HMAC sopii API-avainten allekirjoitukseen, istuntoev\u00e4steiden eheyden suojaukseen, kertak\u00e4ytt\u00f6isten latauslinkkien luontiin ja mikropalveluiden v\u00e4liseen todennukseen. Jos haluat syventy\u00e4 siihen, miten tiivistefunktiot toimivat HMAC:n alla, lue erillinen artikkelimme <a href=\"\/fi\/hash-funktiot\/\">kryptografisista tiivistefunktioista<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"hmac-vs-tavallinen-tiiviste-vs-digitaalinen-allekirjoitus\">HMAC vs tavallinen tiiviste vs digitaalinen allekirjoitus<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ennen koodausta on t\u00e4rke\u00e4\u00e4 ymm\u00e4rt\u00e4\u00e4, milloin HMAC on oikea ty\u00f6kalu ja milloin tarvitset jotain muuta. Kaikki kolme mekanismia takaavat eheyden, mutta ne eroavat avaintenhallinnan ja kiist\u00e4m\u00e4tt\u00f6myyden suhteen. HMAC k\u00e4ytt\u00e4\u00e4 symmetrist\u00e4 salaisuutta: sama avain sek\u00e4 luo ett\u00e4 varmentaa tagin. Digitaalinen allekirjoitus, kuten ECDSA tai EdDSA, k\u00e4ytt\u00e4\u00e4 julkista ja yksityist\u00e4 avainparia, jolloin kuka tahansa voi varmentaa allekirjoituksen mutta vain yksityisen avaimen haltija voi luoda sen.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Ominaisuus<\/th><th>Tavallinen tiiviste (SHA-256)<\/th><th>HMAC-SHA256<\/th><th>Digitaalinen allekirjoitus (EdDSA)<\/th><\/tr><\/thead><tbody><tr><td>Takaa eheyden<\/td><td>Kyll\u00e4<\/td><td>Kyll\u00e4<\/td><td>Kyll\u00e4<\/td><\/tr><tr><td>Todentaa l\u00e4hett\u00e4j\u00e4n<\/td><td>Ei<\/td><td>Kyll\u00e4 (avaimen haltija)<\/td><td>Kyll\u00e4 (yksityinen avain)<\/td><\/tr><tr><td>Avaintyyppi<\/td><td>Ei avainta<\/td><td>Yksi jaettu salaisuus<\/td><td>Julkinen + yksityinen avain<\/td><\/tr><tr><td>Kiist\u00e4m\u00e4tt\u00f6myys<\/td><td>Ei<\/td><td>Ei<\/td><td>Kyll\u00e4<\/td><\/tr><tr><td>Suorituskyky<\/td><td>Nopein<\/td><td>Nopea<\/td><td>Hitain<\/td><\/tr><tr><td>Tyypillinen k\u00e4ytt\u00f6<\/td><td>Tiedoston eheys<\/td><td>Webhookit, API-allekirjoitus<\/td><td>Sertifikaatit, ohjelmistojulkaisut<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Valitse HMAC, kun molemmat osapuolet voivat turvallisesti jakaa yhden salaisuuden, kuten palveluntarjoaja ja sen integroija. T\u00e4m\u00e4 on tyypillinen tilanne webhookeissa ja sis\u00e4isiss\u00e4 rajapinnoissa. Valitse digitaalinen allekirjoitus, kun varmentajia on monta tai kun tarvitset kiist\u00e4m\u00e4tt\u00f6myytt\u00e4, eli todistetta siit\u00e4, ett\u00e4 vain tietty taho on voinut luoda viestin. Esimerkiksi ohjelmistopakettien julkaisuissa allekirjoitus on v\u00e4ltt\u00e4m\u00e4t\u00f6n, koska julkista avainta jaetaan tuhansille k\u00e4ytt\u00e4jille. Lue lis\u00e4\u00e4 erosta artikkelistamme <a href=\"\/fi\/digitaaliset-allekirjoitukset\/\">digitaalisista allekirjoituksista<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tavallinen tiiviste yksin\u00e4\u00e4n ei koskaan todenna l\u00e4hett\u00e4j\u00e4\u00e4. Yleinen virhe on luulla, ett\u00e4 <code>sha256(data)<\/code> suojaa peukaloinnilta, mutta jos hy\u00f6kk\u00e4\u00e4j\u00e4 voi muuttaa dataa, h\u00e4n voi laskea my\u00f6s uuden tiivisteen. HMAC sulkee t\u00e4m\u00e4n aukon vaatimalla salaisen avaimen. T\u00e4st\u00e4 syyst\u00e4 esimerkiksi <a href=\"\/fi\/turvalliset-sessiot-nodejs\/\">turvalliset istunnot Node.js:ss\u00e4<\/a> nojaavat juuri HMAC-allekirjoitettuihin ev\u00e4steisiin sen sijaan, ett\u00e4 ne luottaisivat pelkk\u00e4\u00e4n tiivisteeseen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"esivaatimukset-ja-versiot\">Esivaatimukset ja versiot<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4m\u00e4 opas olettaa perustason JavaScript- ja Node.js-osaamisen sek\u00e4 komentorivin k\u00e4yt\u00f6n. Et tarvitse aiempaa kryptografiakokemusta. Kaikki HMAC-toiminnallisuus tulee Node.js:n vakiokirjastosta, joten ainoa pakollinen riippuvuus on Node itse. Express ja Vitest tarvitaan vain t\u00e4ydellisen projektin ja testien vaiheissa.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Ty\u00f6kalu<\/th><th>Suositeltu versio (2026)<\/th><th>Tarkoitus<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>24.11.0 LTS (&#8220;Krypton&#8221;) tai uudempi<\/td><td>Ajoymp\u00e4rist\u00f6 ja node:crypto-moduuli<\/td><\/tr><tr><td>npm<\/td><td>11.x (Node 24:n mukana)<\/td><td>Pakettienhallinta<\/td><\/tr><tr><td>node:crypto<\/td><td>Sis\u00e4\u00e4nrakennettu<\/td><td>HMAC, timingSafeEqual, WebCrypto<\/td><\/tr><tr><td>Express<\/td><td>5.x<\/td><td>Webhook-vastaanotin (valinnainen)<\/td><\/tr><tr><td>Vitest<\/td><td>3.x<\/td><td>Yksikk\u00f6testaus (valinnainen)<\/td><\/tr><tr><td>dotenv<\/td><td>17.x<\/td><td>Salaisuuksien lataus ymp\u00e4rist\u00f6st\u00e4<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Tarkista asennettu Node.js-versio ennen aloitusta. Jos k\u00e4yt\u00f6ss\u00e4si on vanhempi kuin 24 LTS, p\u00e4ivit\u00e4 se ennen jatkamista, koska vanhentuneet versiot eiv\u00e4t en\u00e4\u00e4 saa tietoturvap\u00e4ivityksi\u00e4. Versionhallinta onnistuu helpoiten nvm-ty\u00f6kalulla.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --version\nv24.11.0\n\n$ npm --version\n11.0.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Yksi t\u00e4rke\u00e4 periaate kulkee mukana koko oppaan ajan: salaisia avaimia ei koskaan kovakoodata l\u00e4hdekoodiin eik\u00e4 committoida versionhallintaan. Lataamme ne ymp\u00e4rist\u00f6muuttujista. T\u00e4m\u00e4 on perusedellytys turvalliselle tuotantok\u00e4yt\u00f6lle, ja palaamme aiheeseen tarkemmin vaiheessa 10.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-1-projektin-alustus-ja-ensimmainen-hmac\">Vaihe 1: Projektin alustus ja ensimm\u00e4inen HMAC<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Aloita luomalla uusi projektikansio ja alustamalla se npm:ll\u00e4. Asetamme projektin k\u00e4ytt\u00e4m\u00e4\u00e4n ES-moduuleita lis\u00e4\u00e4m\u00e4ll\u00e4 <code>\"type\": \"module\"<\/code> package.json-tiedostoon, jotta voimme k\u00e4ytt\u00e4\u00e4 modernia import-syntaksia.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ mkdir hmac-nodejs &amp;&amp; cd hmac-nodejs\n$ npm init -y\n$ npm pkg set type=module<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Luo tiedosto <code>hmac.js<\/code> ja kirjoita ensimm\u00e4inen HMAC-laskelma. Funktio <code>crypto.createHmac(algoritmi, avain)<\/code> palauttaa Hmac-olion, jota p\u00e4ivitet\u00e4\u00e4n <code>update()<\/code>-metodilla ja viimeistell\u00e4\u00e4n <code>digest()<\/code>-metodilla. Digest voidaan koodata heksaksi, base64:ksi tai palauttaa Bufferina.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hmac.js\nimport { createHmac } from 'node:crypto';\n\nconst secret = 'super-salainen-avain-vain-testiin';\nconst message = 'maksu:1000:EUR:tilaus-42';\n\nconst tag = createHmac('sha256', secret)\n  .update(message)\n  .digest('hex');\n\nconsole.log('Viesti:', message);\nconsole.log('HMAC-SHA256:', tag);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aja tiedosto komennolla <code>node hmac.js<\/code>. Saat aina saman 64 merkin heksatunnisteen samalla viestill\u00e4 ja avaimella, koska HMAC on deterministinen. Jos muutat viesti\u00e4 yhdell\u00e4 merkill\u00e4, koko tagi muuttuu t\u00e4ysin. T\u00e4m\u00e4 lumivy\u00f6ryefekti on tiivistefunktioiden ydinominaisuus.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node hmac.js\nViesti: maksu:1000:EUR:tilaus-42\nHMAC-SHA256: 9f4c1e2a7b8d3f6e0a1c2b3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f50617<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">SHA-256 tuottaa aina 256-bittisen eli 32-tavuisen tagin, joka heksana on 64 merkki\u00e4. Jos vaihdat algoritmin <code>sha512<\/code>:ksi, saat 128 merkin tagin. Algoritmin valinnasta puhumme lis\u00e4\u00e4 suorituskykyosiossa, mutta SHA-256 on turvallinen ja nopea oletus l\u00e4hes kaikkeen. Jos haluat ymm\u00e4rt\u00e4\u00e4 SHA-256:n sis\u00e4isen toiminnan, katso syv\u00e4llinen artikkelimme <a href=\"\/fi\/sha-256\/\">SHA-256:sta<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-2-salaisen-avaimen-turvallinen-generointi\">Vaihe 2: Salaisen avaimen turvallinen generointi<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Edellisen vaiheen merkkijonosalaisuus kelpaa kokeiluun, mutta tuotannossa avaimen on oltava satunnainen ja riitt\u00e4v\u00e4n pitk\u00e4. NIST SP 800-107 suosittaa, ett\u00e4 HMAC-avain on v\u00e4hint\u00e4\u00e4n yht\u00e4 pitk\u00e4 kuin tiivisteen ulostulo. SHA-256:lla t\u00e4m\u00e4 tarkoittaa v\u00e4hint\u00e4\u00e4n 256 bitti\u00e4 eli 32 tavua, ja SHA-512:lla v\u00e4hint\u00e4\u00e4n 512 bitti\u00e4 eli 64 tavua. Liian lyhyt avain heikent\u00e4\u00e4 koko mekanismin turvallisuutta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Generoi avain kryptografisesti turvallisella satunnaisl\u00e4hteell\u00e4 <code>crypto.randomBytes<\/code>. \u00c4l\u00e4 koskaan k\u00e4yt\u00e4 <code>Math.random()<\/code>-funktiota avaimiin, koska se ei ole kryptografisesti turvallinen. Seuraava skripti tuottaa 32-tavuisen avaimen ja tulostaa sen heksana, jonka voit tallentaa ymp\u00e4rist\u00f6muuttujaan.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ generate-key.js\nimport { randomBytes } from 'node:crypto';\n\n\/\/ 32 tavua = 256 bitti\u00e4, NIST SP 800-107 -minimi SHA-256:lle\nconst key = randomBytes(32).toString('hex');\n\nconsole.log('HMAC_SECRET=' + key);<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node generate-key.js\nHMAC_SECRET=3b1f8c0d5e2a9476b8c1d0e3f4a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tallenna tulostettu rivi <code>.env<\/code>-tiedostoon ja lis\u00e4\u00e4 tiedosto <code>.gitignore<\/code>:hen heti. Lataa salaisuus koodissa ymp\u00e4rist\u00f6st\u00e4, \u00e4l\u00e4 koskaan suoraan tiedostosta l\u00e4hdekoodissa. Asenna dotenv ja k\u00e4yt\u00e4 sit\u00e4 kehitysvaiheessa.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ key-config.js\nimport 'dotenv\/config';\n\nconst secret = process.env.HMAC_SECRET;\nif (!secret || secret.length &lt; 64) {\n  throw new Error('HMAC_SECRET puuttuu tai on liian lyhyt (vaaditaan 32 tavua heksana)');\n}\n\n\/\/ Buffer on parempi kuin merkkijono: v\u00e4lt\u00e4t koodausvirheet\nexport const hmacKey = Buffer.from(secret, 'hex');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Huomaa kaksi yksityiskohtaa. Ensinn\u00e4kin validoimme, ett\u00e4 salaisuus on olemassa ja riitt\u00e4v\u00e4n pitk\u00e4 jo sovelluksen k\u00e4ynnistyess\u00e4, jolloin virhe paljastuu heti eik\u00e4 vasta ensimm\u00e4isen pyynn\u00f6n kohdalla. Toiseksi muunnamme heksamerkkijonon Bufferiksi. Bufferin tai KeyObjectin k\u00e4ytt\u00f6 merkkijonon sijaan v\u00e4hent\u00e4\u00e4 koodausvirheit\u00e4, koska crypto-funktiot tulkitsevat merkkijonot UTF-8:na ellei toisin m\u00e4\u00e4ritet\u00e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-3-aikaturvallinen-vertailu-timingsafeequal-funktiolla\">Vaihe 3: Aikaturvallinen vertailu timingSafeEqual-funktiolla<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4m\u00e4 on oppaan t\u00e4rkein tietoturvavaihe. Kun varmennat saapuvan tagin, sinun on verrattava sit\u00e4 itse laskemaasi tagiin. Tavallinen vertailu <code>===<\/code> tai merkkijonojen <code>==<\/code> palaa heti ensimm\u00e4isen eroavan tavun kohdalla. T\u00e4m\u00e4 luo mitattavan aikaeron, jota hy\u00f6kk\u00e4\u00e4j\u00e4 voi k\u00e4ytt\u00e4\u00e4 tagin arvaamiseen tavu tavulta. T\u00e4llaista hy\u00f6kk\u00e4yst\u00e4 kutsutaan aikahy\u00f6kk\u00e4ykseksi (timing attack).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js tarjoaa juuri t\u00e4h\u00e4n tarkoitukseen funktion <code>crypto.timingSafeEqual(a, b)<\/code>, joka vertaa kahta Bufferia vakioajassa riippumatta siit\u00e4, miss\u00e4 kohtaa ne eroavat. Funktio vaatii, ett\u00e4 molemmat puskurit ovat samanpituiset, joten vertaa aina raakatavuja, \u00e4l\u00e4 eripituisia merkkijonoja.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ verify.js\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport function sign(message, key) {\n  return createHmac('sha256', key).update(message).digest();\n}\n\nexport function verify(message, key, receivedTagHex) {\n  const expected = sign(message, key);\n  let received;\n  try {\n    received = Buffer.from(receivedTagHex, 'hex');\n  } catch {\n    return false;\n  }\n  \/\/ Pituuden on t\u00e4sm\u00e4tt\u00e4v\u00e4, muuten timingSafeEqual heitt\u00e4\u00e4 poikkeuksen\n  if (received.length !== expected.length) return false;\n  return timingSafeEqual(expected, received);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Huomaa, ett\u00e4 tarkistamme pituuden ennen <code>timingSafeEqual<\/code>-kutsua. T\u00e4m\u00e4 pituustarkistus ei vuoda hy\u00f6dyllist\u00e4 tietoa, koska tagin pituus on julkinen vakio (SHA-256:lla aina 32 tavua). Jos pituudet eiv\u00e4t t\u00e4sm\u00e4\u00e4, sy\u00f6te on selv\u00e4sti virheellinen. \u00c4l\u00e4 koskaan korvaa t\u00e4t\u00e4 funktiota omalla silmukalla, sill\u00e4 JavaScriptin optimoinnit voivat tehd\u00e4 siit\u00e4 huomaamatta aikariippuvaisen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ test-verify.js\nimport { verify, sign } from '.\/verify.js';\n\nconst key = Buffer.from('3b1f8c0d5e2a9476b8c1d0e3f4a5b6c7', 'hex');\nconst msg = 'tilaus-42';\n\nconst goodTag = sign(msg, key).toString('hex');\nconsole.log('Oikea tagi:', verify(msg, key, goodTag));            \/\/ true\nconsole.log('V\u00e4\u00e4r\u00e4 tagi:', verify(msg, key, 'deadbeef'));          \/\/ false\nconsole.log('Muutettu viesti:', verify('tilaus-43', key, goodTag)); \/\/ false<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node test-verify.js\nOikea tagi: true\nV\u00e4\u00e4r\u00e4 tagi: false\nMuutettu viesti: false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4m\u00e4 vertailufunktio on koko todennusj\u00e4rjestelm\u00e4si syd\u00e4n. Jos toteutat t\u00e4m\u00e4n v\u00e4\u00e4rin, vaikkapa tavallisella merkkijonovertailulla, koko HMAC-suojaus heikkenee merkitt\u00e4v\u00e4sti. Pid\u00e4 funktio yksinkertaisena, testattuna ja keskitettyn\u00e4 yhteen paikkaan.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-4-webhookin-allekirjoituksen-varmennus-github-tyyli\">Vaihe 4: Webhookin allekirjoituksen varmennus (GitHub-tyyli)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Yleisin HMAC:n k\u00e4ytt\u00f6kohde on saapuvien webhookien varmennus. GitHub allekirjoittaa jokaisen webhook-toimituksen HMAC-SHA256:lla ja l\u00e4hett\u00e4\u00e4 tagin otsakkeessa <code>X-Hub-Signature-256<\/code> muodossa <code>sha256=&lt;heksatagi&gt;<\/code>. Vastaanottajan on laskettava sama HMAC pyynn\u00f6n raakarungosta ja verrattava sit\u00e4 vakioajassa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Kriittinen yksityiskohta: HMAC on laskettava t\u00e4sm\u00e4lleen samoista raakatavuista, jotka l\u00e4hett\u00e4j\u00e4 allekirjoitti. Jos j\u00e4senn\u00e4t JSON:n ja sarjallistat sen uudelleen, v\u00e4lily\u00f6nnit tai avainj\u00e4rjestys voivat muuttua, ja allekirjoitus ei t\u00e4sm\u00e4\u00e4. K\u00e4yt\u00e4 siis aina pyynn\u00f6n raakarunkoa, \u00e4l\u00e4 j\u00e4sennetty\u00e4 oliota.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ github-webhook.js\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport function verifyGithubSignature(rawBody, signatureHeader, secret) {\n  if (!signatureHeader) return false;\n\n  const expected = 'sha256=' +\n    createHmac('sha256', secret).update(rawBody).digest('hex');\n\n  const a = Buffer.from(expected);\n  const b = Buffer.from(signatureHeader);\n  if (a.length !== b.length) return false;\n  return timingSafeEqual(a, b);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4ss\u00e4 esimerkiss\u00e4 vertaamme koko otsakemerkkijonoa <code>sha256=...<\/code> mukaan lukien etuliite, mik\u00e4 on turvallista ja yksinkertaista. Vaihtoehtoisesti voit irrottaa etuliitteen ja verrata pelkki\u00e4 tagitavuja. Kumpikin tapa toimii, kunhan vertailu tapahtuu <code>timingSafeEqual<\/code>-funktiolla.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Express 5:ss\u00e4 raakarungon saa middlewarella, joka tallentaa puskurin ennen JSON-j\u00e4sennyst\u00e4. T\u00e4m\u00e4 on v\u00e4ltt\u00e4m\u00e4t\u00f6nt\u00e4, koska oletuksena <code>express.json()<\/code> kuluttaa rungon eik\u00e4 raakatavuja ole en\u00e4\u00e4 saatavilla.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ raw-body.js\nimport express from 'express';\n\nexport function rawBodySaver(req, res, buf) {\n  req.rawBody = buf; \/\/ tallenna raakatavut my\u00f6hemp\u00e4\u00e4 HMAC-laskentaa varten\n}\n\nexport const jsonWithRaw = express.json({ verify: rawBodySaver });<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">GitHubin virallinen ohje webhookien varmennukseen vahvistaa t\u00e4m\u00e4n l\u00e4hestymistavan ja korostaa raakarungon k\u00e4ytt\u00f6\u00e4. Voit lukea sen <a href=\"https:\/\/docs.github.com\/en\/webhooks\/using-webhooks\/validating-webhook-deliveries\" target=\"_blank\" rel=\"noopener\">GitHubin dokumentaatiosta<\/a>. Sama periaate p\u00e4tee Stripeen, jonka k\u00e4ymme l\u00e4pi seuraavaksi.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-5-stripe-webhookin-varmennus-aikaleimalla\">Vaihe 5: Stripe-webhookin varmennus aikaleimalla<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Stripe vie webhook-allekirjoituksen askeleen pidemm\u00e4lle lis\u00e4\u00e4m\u00e4ll\u00e4 aikaleiman. Allekirjoitettava sis\u00e4lt\u00f6 on muotoa <code>&lt;aikaleima&gt;.&lt;raakarunko&gt;<\/code>, ja itse allekirjoitus toimitetaan <code>Stripe-Signature<\/code>-otsakkeessa muodossa <code>t=aikaleima,v1=tagi<\/code>. Aikaleima est\u00e4\u00e4 toistohy\u00f6kk\u00e4ykset (replay attacks): jos sama, kerran kaapattu pyynt\u00f6 l\u00e4hetet\u00e4\u00e4n uudelleen tuntien kuluttua, vastaanottaja voi hyl\u00e4t\u00e4 sen liian vanhana.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ stripe-webhook.js\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nconst TOLERANCE_SECONDS = 300; \/\/ 5 minuuttia\n\nexport function verifyStripeSignature(rawBody, sigHeader, secret, nowSeconds) {\n  const parts = Object.fromEntries(\n    sigHeader.split(',').map((p) =&gt; p.split('='))\n  );\n  const timestamp = Number(parts.t);\n  const received = parts.v1;\n  if (!timestamp || !received) return false;\n\n  \/\/ Toistohy\u00f6kk\u00e4yssuojaus: hylk\u00e4\u00e4 liian vanhat pyynn\u00f6t\n  if (Math.abs(nowSeconds - timestamp) &gt; TOLERANCE_SECONDS) return false;\n\n  const signedPayload = `${timestamp}.${rawBody}`;\n  const expected = createHmac('sha256', secret)\n    .update(signedPayload)\n    .digest('hex');\n\n  const a = Buffer.from(expected);\n  const b = Buffer.from(received);\n  if (a.length !== b.length) return false;\n  return timingSafeEqual(a, b);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Funktio ottaa nykyhetken sekunteina parametrina, mik\u00e4 tekee siit\u00e4 helpon testata. Tuotannossa kutsut sit\u00e4 arvolla <code>Math.floor(Date.now() \/ 1000)<\/code>. Toleranssi viisi minuuttia on Stripen suosittelema oletus, joka siet\u00e4\u00e4 pient\u00e4 kellojen eroa palvelimien v\u00e4lill\u00e4 mutta torjuu vanhojen pyynt\u00f6jen uudelleenl\u00e4hetyksen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00e4yt\u00e4nn\u00f6ss\u00e4 tuotannossa kannattaa k\u00e4ytt\u00e4\u00e4 Stripen omaa kirjastoa, joka hoitaa n\u00e4m\u00e4 yksityiskohdat puolestasi, mutta oman toteutuksen ymm\u00e4rt\u00e4minen auttaa sinua varmentamaan mink\u00e4 tahansa HMAC-pohjaisen webhookin. Stripen viralliset ohjeet l\u00f6ytyv\u00e4t <a href=\"https:\/\/docs.stripe.com\/webhooks\" target=\"_blank\" rel=\"noopener\">Stripen webhook-dokumentaatiosta<\/a>. Toistohy\u00f6kk\u00e4yssuojaus on kohta, jonka moni oma toteutus unohtaa, joten \u00e4l\u00e4 ohita sit\u00e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-6-lahtevien-api-pyyntojen-allekirjoitus\">Vaihe 6: L\u00e4htevien API-pyynt\u00f6jen allekirjoitus<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC ei ole vain saapuvien viestien varmennusta varten. Kun sovelluksesi kutsuu ulkoista rajapintaa, joka vaatii allekirjoitettuja pyynt\u00f6j\u00e4, sinun on luotava HMAC itse. Tyypillinen kaava allekirjoittaa kanonisen merkkijonon, joka koostuu HTTP-metodista, polusta, aikaleimasta ja rungosta. T\u00e4m\u00e4 est\u00e4\u00e4 sek\u00e4 peukaloinnin ett\u00e4 toistohy\u00f6kk\u00e4ykset.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ sign-request.js\nimport { createHmac } from 'node:crypto';\n\nexport function signRequest({ method, path, body, key, timestamp }) {\n  \/\/ Kanoninen muoto: kiinte\u00e4 j\u00e4rjestys ja erotin\n  const canonical = [\n    method.toUpperCase(),\n    path,\n    timestamp,\n    body ?? '',\n  ].join('\\n');\n\n  const signature = createHmac('sha256', key)\n    .update(canonical)\n    .digest('hex');\n\n  return {\n    'X-Timestamp': String(timestamp),\n    'X-Signature': `sha256=${signature}`,\n  };\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Olennaista on kanoninen muoto: l\u00e4hett\u00e4j\u00e4n ja vastaanottajan on rakennettava t\u00e4sm\u00e4lleen sama merkkijono, muuten allekirjoitukset eiv\u00e4t t\u00e4sm\u00e4\u00e4. Kiinte\u00e4 kentt\u00e4j\u00e4rjestys ja yksiselitteinen erotin (t\u00e4ss\u00e4 rivinvaihto) ovat v\u00e4ltt\u00e4m\u00e4tt\u00f6mi\u00e4. V\u00e4lt\u00e4 JSON:n serialisointia kanonisessa muodossa, ellei avainj\u00e4rjestys ole deterministist\u00e4, koska eri kielet ja kirjastot voivat j\u00e4rjest\u00e4\u00e4 avaimet eri tavoin.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ use-sign.js\nimport { signRequest } from '.\/sign-request.js';\n\nconst headers = signRequest({\n  method: 'POST',\n  path: '\/v1\/orders',\n  body: JSON.stringify({ amount: 1000, currency: 'EUR' }),\n  key: Buffer.from(process.env.HMAC_SECRET, 'hex'),\n  timestamp: Math.floor(Date.now() \/ 1000),\n});\n\nconsole.log(headers);\n\/\/ {\n\/\/   'X-Timestamp': '1781469600',\n\/\/   'X-Signature': 'sha256=a1b2c3...'\n\/\/ }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vastaanottava palvelin toistaa saman kanonisen muodon omasta pyynn\u00f6st\u00e4\u00e4n, laskee HMAC:n ja vertaa sit\u00e4 <code>timingSafeEqual<\/code>-funktiolla otsakkeen tagiin sek\u00e4 tarkistaa aikaleiman tuoreuden. N\u00e4in saat symmetrisen, kevyen ja vahvan todennuksen kahden hallitsemasi palvelun v\u00e4lille ilman raskasta julkisen avaimen infrastruktuuria.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-7-webcrypto-yhteensopiva-hmac-crypto-subtle-rajapinnalla\">Vaihe 7: WebCrypto-yhteensopiva HMAC crypto.subtle-rajapinnalla<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Jos haluat kirjoittaa koodia, joka toimii sek\u00e4 Node.js:ss\u00e4 ett\u00e4 selaimessa tai Edge-ajoymp\u00e4rist\u00f6iss\u00e4 (kuten Cloudflare Workers tai Deno), k\u00e4yt\u00e4 standardoitua WebCrypto-rajapintaa <code>crypto.subtle<\/code>. Se on saatavilla Node.js:ss\u00e4 globaalina ja noudattaa samaa W3C-m\u00e4\u00e4rityst\u00e4 kuin selaimet. Rajapinta on asynkroninen ja perustuu lupauksiin (Promise).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ webcrypto-hmac.js\nconst enc = new TextEncoder();\n\nexport async function importKey(rawKeyBytes) {\n  return crypto.subtle.importKey(\n    'raw',\n    rawKeyBytes,\n    { name: 'HMAC', hash: 'SHA-256' },\n    false,            \/\/ ei viet\u00e4viss\u00e4\n    ['sign', 'verify']\n  );\n}\n\nexport async function signWeb(message, cryptoKey) {\n  const sig = await crypto.subtle.sign('HMAC', cryptoKey, enc.encode(message));\n  return Buffer.from(sig).toString('hex');\n}\n\nexport async function verifyWeb(message, cryptoKey, tagHex) {\n  const tag = Buffer.from(tagHex, 'hex');\n  \/\/ subtle.verify hoitaa vakioaikaisen vertailun sis\u00e4isesti\n  return crypto.subtle.verify('HMAC', cryptoKey, tag, enc.encode(message));\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">WebCrypto-rajapinnan etu on, ett\u00e4 <code>crypto.subtle.verify<\/code> hoitaa vakioaikaisen vertailun automaattisesti, joten erillist\u00e4 <code>timingSafeEqual<\/code>-kutsua ei tarvita varmennuksessa. Lis\u00e4ksi avain merkit\u00e4\u00e4n ei-viet\u00e4v\u00e4ksi (<code>extractable: false<\/code>), jolloin raaka-avainta ei voi vahingossa lukea takaisin muistista. T\u00e4m\u00e4 on hyv\u00e4 puolustava k\u00e4yt\u00e4nt\u00f6.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ test-webcrypto.js\nimport { importKey, signWeb, verifyWeb } from '.\/webcrypto-hmac.js';\nimport { randomBytes } from 'node:crypto';\n\nconst key = await importKey(randomBytes(32));\nconst tag = await signWeb('hei maailma', key);\nconsole.log('Tagi:', tag);\nconsole.log('Varmennus:', await verifyWeb('hei maailma', key, tag)); \/\/ true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Valitse <code>node:crypto<\/code>:n synkroninen <code>createHmac<\/code>, kun koodi ajetaan vain Node.js:ss\u00e4 ja haluat yksinkertaisuutta. Valitse WebCrypto, kun tavoittelet siirrett\u00e4vyytt\u00e4 useaan ajoymp\u00e4rist\u00f6\u00f6n. Molemmat tuottavat t\u00e4sm\u00e4lleen saman HMAC-tagin samalla avaimella ja viestill\u00e4, joten ne ovat kesken\u00e4\u00e4n yhteentoimivia.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-8-taydellinen-express-webhook-projekti\">Vaihe 8: T\u00e4ydellinen Express-webhook-projekti<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nyt kokoamme palaset toimivaksi sovellukseksi. Rakennamme Express 5 -palvelimen, joka vastaanottaa webhookeja, varmentaa niiden HMAC-allekirjoituksen ja k\u00e4sittelee vain kelvolliset pyynn\u00f6t. Asenna riippuvuudet ensin.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ npm install express dotenv<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js\nimport 'dotenv\/config';\nimport express from 'express';\nimport { verifyGithubSignature } from '.\/github-webhook.js';\n\nconst app = express();\nconst secret = Buffer.from(process.env.HMAC_SECRET, 'hex');\n\n\/\/ Tallenna raakatavut ennen JSON-j\u00e4sennyst\u00e4\napp.use(express.json({\n  verify: (req, _res, buf) =&gt; { req.rawBody = buf; },\n}));\n\napp.post('\/webhook', (req, res) =&gt; {\n  const signature = req.get('X-Hub-Signature-256');\n\n  if (!verifyGithubSignature(req.rawBody, signature, secret)) {\n    console.warn('Hyl\u00e4tty: virheellinen allekirjoitus');\n    return res.status(401).json({ error: 'invalid signature' });\n  }\n\n  \/\/ Allekirjoitus on kelvollinen, k\u00e4sittele tapahtuma turvallisesti\n  console.log('Hyv\u00e4ksytty tapahtuma:', req.body.action ?? 'tuntematon');\n  res.status(200).json({ received: true });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () =&gt; console.log(`Kuuntelee porttia ${PORT}`));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Palvelin hylk\u00e4\u00e4 kaikki pyynn\u00f6t, joiden allekirjoitus puuttuu tai ei t\u00e4sm\u00e4\u00e4, ja palauttaa HTTP 401:n. Vasta kelvollisen allekirjoituksen j\u00e4lkeen sovellus k\u00e4sittelee rungon sis\u00e4ll\u00f6n. T\u00e4m\u00e4 on oikea j\u00e4rjestys: varmenna ensin, k\u00e4sittele sitten. \u00c4l\u00e4 koskaan luota rungon dataan ennen allekirjoituksen varmennusta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Testaa palvelinta paikallisesti laskemalla kelvollinen allekirjoitus ja l\u00e4hett\u00e4m\u00e4ll\u00e4 se curlilla. Seuraava apuskripti tulostaa valmiin curl-komennon.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ make-request.js\nimport 'dotenv\/config';\nimport { createHmac } from 'node:crypto';\n\nconst secret = Buffer.from(process.env.HMAC_SECRET, 'hex');\nconst body = JSON.stringify({ action: 'opened', number: 42 });\nconst sig = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');\n\nconsole.log(`curl -X POST http:\/\/localhost:3000\/webhook \\\\\n  -H \"Content-Type: application\/json\" \\\\\n  -H \"X-Hub-Signature-256: ${sig}\" \\\\\n  -d '${body}'`);<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node make-request.js | sh\n{\"received\":true}\n\n# Palvelimen lokissa:\nHyv\u00e4ksytty tapahtuma: opened<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Jos muutat runkoa tai allekirjoitusta yhdell\u00e4kin merkill\u00e4, palvelin vastaa <code>401 invalid signature<\/code>. T\u00e4m\u00e4 on koko HMAC-suojauksen konkreettinen lopputulos: vain oikealla salaisuudella allekirjoitetut pyynn\u00f6t p\u00e4\u00e4sev\u00e4t l\u00e4pi.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-9-yksikkotestit-vitestilla\">Vaihe 9: Yksikk\u00f6testit Vitestill\u00e4<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Kryptografinen koodi on juuri sit\u00e4 koodia, joka on testattava huolellisesti, koska virheet eiv\u00e4t n\u00e4y normaalissa k\u00e4yt\u00f6ss\u00e4 vaan vasta hy\u00f6kk\u00e4yksess\u00e4. Kirjoitamme testit varmennusfunktiolle Vitestill\u00e4. Asenna se kehitysriippuvuudeksi.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ npm install -D vitest\n$ npm pkg set scripts.test=vitest<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ verify.test.js\nimport { describe, it, expect } from 'vitest';\nimport { createHmac, randomBytes } from 'node:crypto';\nimport { verifyGithubSignature } from '.\/github-webhook.js';\n\nconst key = randomBytes(32);\nconst body = Buffer.from(JSON.stringify({ action: 'opened' }));\nconst validSig = 'sha256=' + createHmac('sha256', key).update(body).digest('hex');\n\ndescribe('verifyGithubSignature', () =&gt; {\n  it('hyv\u00e4ksyy kelvollisen allekirjoituksen', () =&gt; {\n    expect(verifyGithubSignature(body, validSig, key)).toBe(true);\n  });\n\n  it('hylk\u00e4\u00e4 muutetun rungon', () =&gt; {\n    const tampered = Buffer.from(JSON.stringify({ action: 'closed' }));\n    expect(verifyGithubSignature(tampered, validSig, key)).toBe(false);\n  });\n\n  it('hylk\u00e4\u00e4 v\u00e4\u00e4r\u00e4n avaimen', () =&gt; {\n    expect(verifyGithubSignature(body, validSig, randomBytes(32))).toBe(false);\n  });\n\n  it('hylk\u00e4\u00e4 puuttuvan otsakkeen', () =&gt; {\n    expect(verifyGithubSignature(body, undefined, key)).toBe(false);\n  });\n\n  it('hylk\u00e4\u00e4 v\u00e4\u00e4r\u00e4n pituisen tagin', () =&gt; {\n    expect(verifyGithubSignature(body, 'sha256=abcd', key)).toBe(false);\n  });\n});<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ npm test\n\n \u2713 verify.test.js (5 tests) 12ms\n   \u2713 hyv\u00e4ksyy kelvollisen allekirjoituksen\n   \u2713 hylk\u00e4\u00e4 muutetun rungon\n   \u2713 hylk\u00e4\u00e4 v\u00e4\u00e4r\u00e4n avaimen\n   \u2713 hylk\u00e4\u00e4 puuttuvan otsakkeen\n   \u2713 hylk\u00e4\u00e4 v\u00e4\u00e4r\u00e4n pituisen tagin\n\n Test Files  1 passed (1)\n      Tests  5 passed (5)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e4m\u00e4 viisi testitapausta kattavat t\u00e4rkeimm\u00e4t polut: kelvollinen pyynt\u00f6, muutettu runko, v\u00e4\u00e4r\u00e4 avain, puuttuva otsake ja v\u00e4\u00e4r\u00e4n pituinen tagi. Lis\u00e4\u00e4 tarvittaessa testit aikaleiman toleranssille ja Stripe-tyyliselle allekirjoitukselle. Hyv\u00e4 testikattavuus on paras suoja regressioilta, kun koodia muokataan my\u00f6hemmin.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-10-salaisuuksien-hallinta-ja-avainten-kierto-tuotannossa\">Vaihe 10: Salaisuuksien hallinta ja avainten kierto tuotannossa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Viimeinen vaihe vie projektin tuotantokuntoon. Salaisuudet eiv\u00e4t kuulu koodiin eiv\u00e4tk\u00e4 versionhallintaan. K\u00e4yt\u00e4 alustasi salaisuuksien hallintaa: pilvialustojen secret managereita, Kubernetesin Secret-resursseja tai erillist\u00e4 holvia kuten HashiCorp Vault. Lataa salaisuus ymp\u00e4rist\u00f6muuttujasta k\u00e4ynnistyksess\u00e4 ja validoi se heti.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Avaintenkierto on t\u00e4rke\u00e4 mutta usein unohdettu osa. Tue useaa voimassa olevaa avainta samanaikaisesti, jotta voit vaihtaa avaimen ilman katkoa. Varmennuksessa kokeile saapuvaa tagia kaikkia voimassa olevia avaimia vasten, mutta allekirjoita aina uusimmalla avaimella.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ key-rotation.js\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n\/\/ Uusin ensin; allekirjoitus k\u00e4ytt\u00e4\u00e4 keys[0]\nconst keys = (process.env.HMAC_KEYS || '')\n  .split(',')\n  .filter(Boolean)\n  .map((hex) =&gt; Buffer.from(hex, 'hex'));\n\nexport function signCurrent(message) {\n  return createHmac('sha256', keys[0]).update(message).digest('hex');\n}\n\nexport function verifyAnyKey(message, receivedHex) {\n  const received = Buffer.from(receivedHex, 'hex');\n  return keys.some((key) =&gt; {\n    const expected = createHmac('sha256', key).update(message).digest();\n    return received.length === expected.length &amp;&amp;\n      timingSafeEqual(expected, received);\n  });\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4ll\u00e4 rakenteella avaimenvaihto on hallittu: lis\u00e4\u00e4 uusi avain listan alkuun, anna molempien avainten olla voimassa siirtym\u00e4ajan, ja poista vanha avain vasta kun kaikki sit\u00e4 k\u00e4ytt\u00e4neet integraatiot ovat siirtyneet. Suosittele integraatiokumppaneille s\u00e4\u00e4nn\u00f6llist\u00e4 kiertoa, esimerkiksi 90 p\u00e4iv\u00e4n v\u00e4lein, ja vaadi avaimen vaihto heti, jos ep\u00e4ilet vuotoa. Avaimen vuotaessa HMAC-suojaus murtuu t\u00e4ysin, joten vuodon havaitseminen ja nopea reagointi ovat kriittisi\u00e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Muista my\u00f6s HTTPS. HMAC suojaa peukaloinnilta ja todentaa l\u00e4hett\u00e4j\u00e4n, mutta se ei salaa sis\u00e4lt\u00f6\u00e4. Aja webhook-vastaanotin aina TLS:n takana, jotta sis\u00e4lt\u00f6 ei vuoda matkalla. Lue lis\u00e4\u00e4 siit\u00e4, miten salattu yhteys toimii, artikkelistamme <a href=\"\/fi\/https-ja-tls\/\">HTTPS:st\u00e4 ja TLS:st\u00e4<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"bonus-hmac-allekirjoitetut-kertakayttoiset-latauslinkit\">Bonus: HMAC-allekirjoitetut kertak\u00e4ytt\u00f6iset latauslinkit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Yksi tyylik\u00e4s HMAC:n k\u00e4ytt\u00f6kohde on aikarajoitettujen, allekirjoitettujen latauslinkkien luonti. Sen sijaan, ett\u00e4 yll\u00e4pit\u00e4isit tietokannassa kertak\u00e4ytt\u00f6isi\u00e4 tokeneita, voit allekirjoittaa resurssin tunnisteen ja vanhenemisajan HMAC:lla. Palvelin varmentaa allekirjoituksen pyynn\u00f6n saapuessa eik\u00e4 tarvitse erillist\u00e4 tilaa. T\u00e4t\u00e4 mallia k\u00e4ytt\u00e4v\u00e4t esimerkiksi monet pilvitallennuspalvelut esiallekirjoitetuissa URL-osoitteissaan.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ signed-url.js\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport function createSignedUrl(fileId, expiresAt, key) {\n  const payload = `${fileId}.${expiresAt}`;\n  const sig = createHmac('sha256', key).update(payload).digest('hex');\n  return `\/download\/${fileId}?expires=${expiresAt}&amp;sig=${sig}`;\n}\n\nexport function verifySignedUrl(fileId, expiresAt, sig, key, nowSeconds) {\n  \/\/ Tarkista vanheneminen ennen kryptografista ty\u00f6t\u00e4\n  if (nowSeconds &gt; Number(expiresAt)) return { ok: false, reason: 'expired' };\n\n  const payload = `${fileId}.${expiresAt}`;\n  const expected = createHmac('sha256', key).update(payload).digest();\n  const received = Buffer.from(sig, 'hex');\n  if (received.length !== expected.length) return { ok: false, reason: 'invalid' };\n  if (!timingSafeEqual(expected, received)) return { ok: false, reason: 'invalid' };\n  return { ok: true };\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Linkki vanhenee automaattisesti, koska vanhenemisaika on osa allekirjoitettua sis\u00e4lt\u00f6\u00e4. Hy\u00f6kk\u00e4\u00e4j\u00e4 ei voi pident\u00e4\u00e4 voimassaoloa muuttamatta allekirjoitusta, eik\u00e4 h\u00e4n voi tuottaa kelvollista allekirjoitusta ilman salaista avainta. T\u00e4m\u00e4 tekee mallista sek\u00e4 tilattoman ett\u00e4 turvallisen. Huomaa, ett\u00e4 tarkistamme vanhenemisen ennen HMAC-laskentaa, mik\u00e4 s\u00e4\u00e4st\u00e4\u00e4 turhaa ty\u00f6t\u00e4 selv\u00e4sti vanhentuneissa pyynn\u00f6iss\u00e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ use-signed-url.js\nimport { createSignedUrl, verifySignedUrl } from '.\/signed-url.js';\n\nconst key = Buffer.from(process.env.HMAC_SECRET, 'hex');\nconst now = Math.floor(Date.now() \/ 1000);\nconst url = createSignedUrl('raportti-2026', now + 3600, key); \/\/ voimassa 1 h\n\nconsole.log('Linkki:', url);\nconsole.log('Varmennus:', verifySignedUrl('raportti-2026', now + 3600,\n  url.split('sig=')[1], key, now)); \/\/ { ok: true }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sama periaate sopii salasanan palautuslinkkeihin, s\u00e4hk\u00f6postin vahvistuslinkkeihin ja kutsulinkkeihin. Lis\u00e4\u00e4 aina kontekstierotin (esimerkiksi <code>download:<\/code> tai <code>reset:<\/code>) allekirjoitettavaan sis\u00e4lt\u00f6\u00f6n, jottei yhden tarkoituksen linkki kelpaa toisessa. Yhdist\u00e4 t\u00e4m\u00e4 lyhyeen vanhenemisaikaan, niin saat vahvan ja kevyen suojan ilman tietokantatilaa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-yleista-virhetta-hmac-toteutuksissa\">5 yleist\u00e4 virhett\u00e4 HMAC-toteutuksissa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e4m\u00e4 virheet toistuvat tuotantokoodissa ja heikent\u00e4v\u00e4t tai mit\u00e4t\u00f6iv\u00e4t HMAC-suojauksen. K\u00e4y lista l\u00e4pi ennen kuin viet koodisi tuotantoon.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Tavallinen vertailu aikahy\u00f6kk\u00e4ykselle alttiina.<\/strong> K\u00e4ytt\u00e4m\u00e4ll\u00e4 <code>===<\/code> tai <code>==<\/code> tagien vertailuun avaat oven aikahy\u00f6kk\u00e4ykselle. K\u00e4yt\u00e4 aina <code>crypto.timingSafeEqual<\/code>-funktiota.<\/li><li><strong>Uudelleenserialisoitu runko.<\/strong> Jos lasket HMAC:n j\u00e4sennetyst\u00e4 ja uudelleen sarjallistetusta JSON:sta etk\u00e4 raakatavuista, allekirjoitus ei t\u00e4sm\u00e4\u00e4 tai (pahempaa) ohitat eron sallimalla l\u00f6ys\u00e4n vertailun. K\u00e4yt\u00e4 aina raakarunkoa.<\/li><li><strong>Liian lyhyt tai heikko avain.<\/strong> Lyhyt tai <code>Math.random()<\/code>-pohjainen avain on arvattavissa. K\u00e4yt\u00e4 v\u00e4hint\u00e4\u00e4n 32 satunnaista tavua kohteesta <code>crypto.randomBytes<\/code>.<\/li><li><strong>Kovakoodattu salaisuus.<\/strong> L\u00e4hdekoodiin tai versionhallintaan p\u00e4\u00e4tynyt salaisuus on vuotanut salaisuus. Lataa se ymp\u00e4rist\u00f6muuttujasta tai secret managerista.<\/li><li><strong>Toistohy\u00f6kk\u00e4yssuojauksen puute.<\/strong> Ilman aikaleimaa ja tuoreustarkistusta kerran kaapattu kelvollinen pyynt\u00f6 voidaan l\u00e4hett\u00e4\u00e4 uudelleen. Sis\u00e4llyt\u00e4 aikaleima allekirjoitettuun sis\u00e4lt\u00f6\u00f6n ja hylk\u00e4\u00e4 vanhat pyynn\u00f6t.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vianmaaritys-yli-8-yleista-ongelmaa\">Vianm\u00e4\u00e4ritys: yli 8 yleist\u00e4 ongelmaa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Kun allekirjoitukset eiv\u00e4t t\u00e4sm\u00e4\u00e4 tai koodi heitt\u00e4\u00e4 virheen, k\u00e4y t\u00e4m\u00e4 lista l\u00e4pi. Suurin osa HMAC-ongelmista johtuu koodauksesta, raakarungosta tai pituuseroista.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Oire<\/th><th>Todenn\u00e4k\u00f6inen syy<\/th><th>Ratkaisu<\/th><\/tr><\/thead><tbody><tr><td>Allekirjoitus ei koskaan t\u00e4sm\u00e4\u00e4 webhookissa<\/td><td>HMAC lasketaan j\u00e4sennetyst\u00e4 rungosta<\/td><td>K\u00e4yt\u00e4 raakatavuja (req.rawBody)<\/td><\/tr><tr><td>timingSafeEqual heitt\u00e4\u00e4 poikkeuksen<\/td><td>Puskurit ovat eripituiset<\/td><td>Tarkista pituus ennen kutsua, palauta false jos eroaa<\/td><\/tr><tr><td>Tagi vaihtelee samalla sy\u00f6tteell\u00e4<\/td><td>Avain tai koodaus muuttuu ajojen v\u00e4lill\u00e4<\/td><td>Lataa kiinte\u00e4 avain Bufferina, varmista UTF-8<\/td><\/tr><tr><td>Toimii lokaalisti, ei tuotannossa<\/td><td>Ymp\u00e4rist\u00f6muuttuja puuttuu tai eroaa<\/td><td>Validoi HMAC_SECRET k\u00e4ynnistyksess\u00e4<\/td><\/tr><tr><td>Heksa- ja base64-tagi eiv\u00e4t t\u00e4sm\u00e4\u00e4<\/td><td>Eri koodaus l\u00e4hett\u00e4j\u00e4ll\u00e4 ja vastaanottajalla<\/td><td>Sovi yhteinen koodaus (yleens\u00e4 heksa)<\/td><\/tr><tr><td>Stripe-pyynt\u00f6 hyl\u00e4t\u00e4\u00e4n aina<\/td><td>Aikaleimaa ei sis\u00e4llytet\u00e4 allekirjoitettuun sis\u00e4lt\u00f6\u00f6n<\/td><td>Allekirjoita muoto aikaleima.runko<\/td><\/tr><tr><td>Vanha pyynt\u00f6 hyv\u00e4ksyt\u00e4\u00e4n uudelleen<\/td><td>Toistohy\u00f6kk\u00e4yssuojaus puuttuu<\/td><td>Lis\u00e4\u00e4 aikaleiman toleranssitarkistus<\/td><\/tr><tr><td>ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH<\/td><td>timingSafeEqual sai eripituiset puskurit<\/td><td>Muunna molemmat samaan koodaukseen ja tarkista pituus<\/td><\/tr><tr><td>Avaimenvaihto katkaisee integraation<\/td><td>Vain yksi avain voimassa kerralla<\/td><td>Tue useaa avainta varmennuksessa<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Jos mik\u00e4\u00e4n n\u00e4ist\u00e4 ei ratkaise ongelmaa, tulosta sek\u00e4 laskemasi ett\u00e4 saapunut tagi v\u00e4liaikaisesti lokiin (vain kehityksess\u00e4, ei tuotannossa) ja vertaa niit\u00e4 silm\u00e4m\u00e4\u00e4r\u00e4isesti. Useimmiten n\u00e4et heti, ett\u00e4 toinen on heksa ja toinen base64, tai ett\u00e4 runko sis\u00e4lt\u00e4\u00e4 ylim\u00e4\u00e4r\u00e4isen rivinvaihdon. Poista debug-lokitus ennen tuotantoon vienti\u00e4, koska tagien lokitus voi vuotaa tietoa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"suorituskyky-ja-algoritmin-valinta\">Suorituskyky ja algoritmin valinta<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC on kevyt operaatio. Moderni palvelin laskee satoja tuhansia HMAC-SHA256-tageja sekunnissa, joten suorituskyky on harvoin pullonkaula webhook-liikenteess\u00e4. Algoritmin valinta vaikuttaa silti hieman nopeuteen ja tagin pituuteen. SHA-256 on paras yleisvalinta: se on nopea, turvallinen ja yhteensopiva l\u00e4hes kaikkien kumppanien kanssa.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Algoritmi<\/th><th>Tagin pituus<\/th><th>Suhteellinen nopeus<\/th><th>Suositus<\/th><\/tr><\/thead><tbody><tr><td>HMAC-SHA256<\/td><td>32 tavua (64 heksamerkki\u00e4)<\/td><td>Nopea<\/td><td>Oletusvalinta l\u00e4hes kaikkeen<\/td><\/tr><tr><td>HMAC-SHA512<\/td><td>64 tavua (128 heksamerkki\u00e4)<\/td><td>Nopea 64-bittisill\u00e4 alustoilla<\/td><td>Kun halutaan pidempi tagi<\/td><\/tr><tr><td>HMAC-SHA384<\/td><td>48 tavua<\/td><td>Kuten SHA-512<\/td><td>Sertifikaattiyhteensopivuus<\/td><\/tr><tr><td>HMAC-SHA1<\/td><td>20 tavua<\/td><td>Nopea<\/td><td>V\u00e4lt\u00e4 uudessa koodissa<\/td><\/tr><tr><td>HMAC-MD5<\/td><td>16 tavua<\/td><td>Nopein<\/td><td>\u00c4l\u00e4 k\u00e4yt\u00e4, vanhentunut<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Vaikka HMAC-SHA1 on edelleen rakenteellisesti turvallisempi kuin pelkk\u00e4 SHA-1 (HMAC ei kaadu samoin kuin tiivisteen t\u00f6rm\u00e4ys), uudessa koodissa kannattaa silti valita SHA-256 tai vahvempi. SHA-1:n heikkoudet on dokumentoitu hyvin, ja jos haluat ymm\u00e4rt\u00e4\u00e4 miksi, lue artikkelimme <a href=\"\/fi\/sha-256\/\">SHA-256:sta<\/a>. MD5 ja SHA-1 tulevat vastaan vain vanhojen j\u00e4rjestelmien yhteensopivuudessa, eik\u00e4 niit\u00e4 pid\u00e4 ottaa k\u00e4ytt\u00f6\u00f6n uusissa integraatioissa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mik\u00e4li sovelluksesi laskee HMAC:n eritt\u00e4in suurelle m\u00e4\u00e4r\u00e4lle pieni\u00e4 viestej\u00e4, huomaa ett\u00e4 Hmac-olion uudelleenk\u00e4ytt\u00f6 on mahdotonta (jokainen tarvitsee uuden), mutta avaimen pit\u00e4minen Bufferina tai KeyObjectina v\u00e4hent\u00e4\u00e4 toistuvaa koodausta. K\u00e4yt\u00e4nn\u00f6ss\u00e4 t\u00e4m\u00e4 optimointi merkitsee vain \u00e4\u00e4rimm\u00e4isen suuren volyymin tapauksissa, ja l\u00e4hes kaikessa tavallisessa k\u00e4yt\u00f6ss\u00e4 oletustoteutus on enemm\u00e4n kuin riitt\u00e4v\u00e4n nopea.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"edistyneet-vinkit-kokeneille-kehittajille\">Edistyneet vinkit kokeneille kehitt\u00e4jille<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Kun perusteet ovat hallussa, n\u00e4ill\u00e4 tekniikoilla viet HMAC-toteutuksesi seuraavalle tasolle. Ne ovat valinnaisia mutta hy\u00f6dyllisi\u00e4 vaativissa ymp\u00e4rist\u00f6iss\u00e4.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>K\u00e4yt\u00e4 KeyObjectia raaka-avaimen sijaan.<\/strong> <code>crypto.createSecretKey(buffer)<\/code> luo KeyObjectin, joka kapseloi avaimen ja v\u00e4hent\u00e4\u00e4 sen vahingossa tapahtuvaa kopiointia tai lokitusta. Anna se suoraan createHmac-funktiolle.<\/li><li><strong>Erota viestialueet etuliitteell\u00e4.<\/strong> Jos k\u00e4yt\u00e4t samaa avainta useaan tarkoitukseen, lis\u00e4\u00e4 allekirjoitettavaan sis\u00e4lt\u00f6\u00f6n kiinte\u00e4 konteksti-etuliite (domain separation), esimerkiksi <code>webhook-v1:<\/code>. N\u00e4in yhden kontekstin tagi ei kelpaa toisessa.<\/li><li><strong>Streamaa suuret hy\u00f6tykuormat.<\/strong> Hmac-olio tukee useaa <code>update()<\/code>-kutsua, joten voit sy\u00f6tt\u00e4\u00e4 tiedoston tai virran palasina ilman, ett\u00e4 koko sis\u00e4lt\u00f6 on muistissa kerralla.<\/li><li><strong>Lis\u00e4\u00e4 nonce toistohy\u00f6kk\u00e4ysten estoon.<\/strong> Aikaleiman lis\u00e4ksi voit vaatia kertak\u00e4ytt\u00f6isen noncen, jonka tallennat lyhyeksi ajaksi (esimerkiksi Redisiin) ja hylk\u00e4\u00e4t jo n\u00e4hdyt arvot. T\u00e4m\u00e4 torjuu toistot my\u00f6s toleranssi-ikkunan sis\u00e4ll\u00e4.<\/li><li><strong>Mittaa ja h\u00e4lyt\u00e4 ep\u00e4onnistumisista.<\/strong> Lokita ep\u00e4onnistuneet varmennukset metriikkana. \u00c4killinen 401-piikki voi viesti\u00e4 joko v\u00e4\u00e4rin m\u00e4\u00e4ritetyst\u00e4 kumppanista tai aktiivisesta hy\u00f6kk\u00e4ysyrityksest\u00e4.<\/li><li><strong>Harkitse HMAC:n ja salauksen yhdist\u00e4mist\u00e4.<\/strong> Jos tarvitset sek\u00e4 luottamuksellisuutta ett\u00e4 eheytt\u00e4, k\u00e4yt\u00e4 autentikoivaa salausta kuten AES-GCM:\u00e4\u00e4, joka sis\u00e4lt\u00e4\u00e4 HMAC:n kaltaisen todennuksen sis\u00e4\u00e4nrakennettuna.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">N\u00e4m\u00e4 tekniikat eiv\u00e4t ole pakollisia perustoteutuksessa, mutta ne ovat arvokkaita, kun j\u00e4rjestelm\u00e4si kasvaa tai kun se k\u00e4sittelee korkean riskin tapahtumia, kuten maksuja. Domain separation ja nonce-pohjainen toistosuojaus ovat erityisen suositeltavia rahoitusalan integraatioissa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"usein-kysytyt-kysymykset\">Usein kysytyt kysymykset<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"onko-hmac-sama-asia-kuin-salaus\">Onko HMAC sama asia kuin salaus?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ei. HMAC todentaa viestin alkuper\u00e4n ja eheyden mutta ei salaa sis\u00e4lt\u00f6\u00e4. Kuka tahansa, joka n\u00e4kee viestin, voi lukea sen, mutta vain salaisen avaimen haltija voi tuottaa kelvollisen tagin. Jos tarvitset sek\u00e4 luottamuksellisuutta ett\u00e4 todennusta, yhdist\u00e4 HMAC salaukseen tai k\u00e4yt\u00e4 autentikoivaa salausta kuten AES-GCM:\u00e4\u00e4.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"miksi-en-voi-verrata-tageja-tavallisella-operaattorilla\">Miksi en voi verrata tageja tavallisella ===-operaattorilla?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Tavallinen vertailu palaa heti ensimm\u00e4isen eroavan tavun kohdalla, mik\u00e4 luo mitattavan aikaeron. Hy\u00f6kk\u00e4\u00e4j\u00e4 voi k\u00e4ytt\u00e4\u00e4 t\u00e4t\u00e4 eroa arvatakseen tagin tavu kerrallaan. <code>crypto.timingSafeEqual<\/code> vertaa vakioajassa riippumatta siit\u00e4, miss\u00e4 kohtaa puskurit eroavat, ja torjuu n\u00e4in aikahy\u00f6kk\u00e4yksen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"mika-on-suositeltu-hmac-avaimen-pituus\">Mik\u00e4 on suositeltu HMAC-avaimen pituus?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">NIST SP 800-107 suosittaa, ett\u00e4 avain on v\u00e4hint\u00e4\u00e4n yht\u00e4 pitk\u00e4 kuin tiivisteen ulostulo. SHA-256:lla t\u00e4m\u00e4 on v\u00e4hint\u00e4\u00e4n 32 tavua (256 bitti\u00e4) ja SHA-512:lla v\u00e4hint\u00e4\u00e4n 64 tavua. Generoi avain aina kryptografisesti turvallisella satunnaisl\u00e4hteell\u00e4, kuten <code>crypto.randomBytes(32)<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"tarvitsenko-ulkoisen-kirjaston-hmaciin-node-jsssa\">Tarvitsenko ulkoisen kirjaston HMAC:iin Node.js:ss\u00e4?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Et. Node.js:n vakiomoduuli <code>node:crypto<\/code> sis\u00e4lt\u00e4\u00e4 t\u00e4yden HMAC-tuen sek\u00e4 synkronisen <code>createHmac<\/code>-rajapinnan ett\u00e4 selainyhteensopivan WebCrypto-rajapinnan <code>crypto.subtle<\/code> kautta. Vanhentunutta crypto-js-kirjastoa ei suositella, koska natiivi moduuli on nopeampi, turvallisempi ja yll\u00e4pidetty.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"miksi-webhookin-allekirjoitus-ei-tasmaa-vaikka-avain-on-oikein\">Miksi webhookin allekirjoitus ei t\u00e4sm\u00e4\u00e4, vaikka avain on oikein?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yleisin syy on, ett\u00e4 HMAC lasketaan j\u00e4sennetyst\u00e4 ja uudelleen sarjallistetusta rungosta eik\u00e4 alkuper\u00e4isist\u00e4 raakatavuista. Jo yksi muuttunut v\u00e4lily\u00f6nti tai avainj\u00e4rjestys rikkoo allekirjoituksen. Tallenna pyynn\u00f6n raakarunko (esimerkiksi Expressin <code>verify<\/code>-takaisinkutsulla) ja laske HMAC suoraan siit\u00e4.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"miten-suojaudun-toistohyokkayksilta\">Miten suojaudun toistohy\u00f6kk\u00e4yksilt\u00e4?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sis\u00e4llyt\u00e4 aikaleima allekirjoitettavaan sis\u00e4lt\u00f6\u00f6n ja hylk\u00e4\u00e4 pyynn\u00f6t, joiden aikaleima on kauempana kuin toleranssi (esimerkiksi 5 minuuttia) nykyhetkest\u00e4. Lis\u00e4turvaa saat kertak\u00e4ytt\u00f6isell\u00e4 noncella, jonka tallennat lyhyeksi aikaa ja jonka jo n\u00e4hneet arvot hylk\u00e4\u00e4t. N\u00e4in sama kelvollinen pyynt\u00f6 ei kelpaa uudelleen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"voinko-kayttaa-samaa-hmac-koodia-node-jsssa-ja-selaimessa\">Voinko k\u00e4ytt\u00e4\u00e4 samaa HMAC-koodia Node.js:ss\u00e4 ja selaimessa?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Kyll\u00e4. K\u00e4yt\u00e4 WebCrypto-rajapintaa <code>crypto.subtle<\/code>, joka on saatavilla sek\u00e4 modernissa Node.js:ss\u00e4 ett\u00e4 selaimissa ja Edge-ajoymp\u00e4rist\u00f6iss\u00e4. Se tuottaa t\u00e4sm\u00e4lleen saman tagin kuin <code>node:crypto<\/code>:n <code>createHmac<\/code> samalla avaimella ja viestill\u00e4, joten toteutukset ovat kesken\u00e4\u00e4n yhteentoimivia.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"mika-node-js-versio-kannattaa-valita-hmac-tuotantokayttoon-2026\">Mik\u00e4 Node.js-versio kannattaa valita HMAC-tuotantok\u00e4ytt\u00f6\u00f6n 2026?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Valitse Node.js 24.x LTS (&#8220;Krypton&#8221;) tai uudempi. Versio 24 siirtyi pitk\u00e4aikaistuettuun LTS-vaiheeseen lokakuussa 2025 ja saa tietoturvap\u00e4ivityksi\u00e4 huhtikuuhun 2028 asti. Crypto-rajapinta on yhtenev\u00e4inen tuoreemmissa julkaisuissa, joten t\u00e4m\u00e4n oppaan koodi toimii my\u00f6s Node 26 -sarjassa.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"aiheeseen-liittyvaa-luettavaa\">Aiheeseen liittyv\u00e4\u00e4 luettavaa<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/fi\/digitaaliset-allekirjoitukset\/\">Digitaaliset allekirjoitukset: tiivisteet ja ep\u00e4symmetriset avaimet<\/a><\/li><li><a href=\"\/fi\/hash-funktiot\/\">Kryptografiset tiivistefunktiot: ominaisuudet ja k\u00e4ytt\u00f6<\/a><\/li><li><a href=\"\/fi\/sha-256\/\">SHA-256 selitettyn\u00e4: SHA-2-perheen ty\u00f6juhta<\/a><\/li><li><a href=\"\/fi\/turvalliset-sessiot-nodejs\/\">Turvalliset sessiot Node.js:ss\u00e4: 10 vaihetta<\/a><\/li><li><a href=\"\/fi\/https-ja-tls\/\">HTTPS ja TLS: miten salattu yhteys suojaa sinua<\/a><\/li><li><a href=\"\/fi\/cryptography-hub\/\">Kryptografia: tiivistefunktiot ja luottamuksen perusta verkossa<\/a><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">L\u00e4hteet ja viralliset dokumentaatiot: <a href=\"https:\/\/nodejs.org\/api\/crypto.html\" target=\"_blank\" rel=\"noopener\">Node.js crypto-dokumentaatio<\/a>, <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc2104\" target=\"_blank\" rel=\"noopener\">RFC 2104 (HMAC)<\/a>, <a href=\"https:\/\/csrc.nist.gov\/pubs\/sp\/800\/107\/r1\/final\" target=\"_blank\" rel=\"noopener\">NIST SP 800-107<\/a>, <a href=\"https:\/\/nodejs.org\/en\/blog\/release\/v24.11.0\" target=\"_blank\" rel=\"noopener\">Node.js 24.11.0 -julkaisutiedot<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Viestin eheys ja alkuper\u00e4n todennus ovat web-sovellusten n\u00e4kym\u00e4tt\u00f6mi\u00e4 peruspilareita. Kun Stripe l\u00e4hett\u00e4\u00e4 webhookin maksusta, kun GitHub ilmoittaa uudesta pushista tai kun kaksi mikropalvelua vaihtaa tietoja, vastaanottajan on voitava varmistua kahdesta\u2026<\/p>\n","protected":false},"author":4,"featured_media":84,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,2],"tags":[],"class_list":["post-83","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-10","category-cryptography"],"_links":{"self":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/83","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/comments?post=83"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/83\/revisions"}],"predecessor-version":[{"id":85,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/83\/revisions\/85"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/media\/84"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/media?parent=83"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/categories?post=83"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/tags?post=83"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}