{"id":107,"date":"2026-06-18T16:57:57","date_gmt":"2026-06-18T16:57:57","guid":{"rendered":"https:\/\/shattered.io\/fi\/2026\/06\/18\/webcrypto-api-nodejs\/"},"modified":"2026-06-18T17:00:44","modified_gmt":"2026-06-18T17:00:44","slug":"webcrypto-api-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/fi\/webcrypto-api-nodejs\/","title":{"rendered":"Node.js WebCrypto API: 12 vaihetta, 35 min [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Node.js WebCrypto API muutti turvallisen kryptografian kirjoittamisen palvelimella vuodesta 2020 alkaen. <code>globalThis.crypto.subtle<\/code>-rajapinta noudattaa W3C-standardia, joten sama koodi toimii sek\u00e4 selaimessa ett\u00e4 Node.js:ss\u00e4. Node.js 24.7.0 lis\u00e4si ChaCha20-Poly1305-salauksen ja <code>SubtleCrypto.supports()<\/code>-metodin, ja Node.js 24.8.0 toi Argon2-tuen avainjohdannaisiin. T\u00e4ss\u00e4 oppaassa k\u00e4yt l\u00e4pi 12 konkreettista vaihetta: turvallinen satunnaisluku, SHA-tiivisteet, PBKDF2-avainjohdannainen, AES-GCM-salaus, ECDSA-allekirjoitukset, HKDF, JWK-vienti ja avainten k\u00e4\u00e4riminen. Valmis projekti syntyy 35 minuutissa ilman yht\u00e4\u00e4n npm-pakettia.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">WebCrypto API on erityisen ajankohtainen vuonna 2026. EU:n kyberresilienssiasetus (CRA) asettaa syyskuussa 2026 ohjelmistokomponenteille tiukat vaatimukset, mukaan lukien kryptografisten algoritmien dokumentointi ja turvallisten oletusasetusten k\u00e4ytt\u00f6. Suomen kyberturvallisuuslaki 124\/2025, joka implementoi NIS2-direktiivin, edellytt\u00e4\u00e4 teknisten suojatoimien k\u00e4ytt\u00f6\u00e4 kaikilta NIS2:n piiriss\u00e4 olevilta organisaatioilta. Standardoitujen algoritmien k\u00e4ytt\u00f6 WebCrypto-rajapinnan kautta on konkreettisin tapa t\u00e4ytt\u00e4\u00e4 n\u00e4m\u00e4 vaatimukset Node.js-sovelluksissa. ENISA:n 2026 kyberturvallisuusosaamiskehys tunnustaa erikseen kryptografian k\u00e4yt\u00e4nn\u00f6ntoteutuksen kehitt\u00e4j\u00e4tason taitona, jonka hallitseminen on keskeinen osa turvallisuusosaamista.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"mita-on-node-js-webcrypto-api\">Mit\u00e4 on Node.js WebCrypto API?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">WebCrypto API on <a href=\"https:\/\/www.w3.org\/TR\/WebCryptoAPI\/\" rel=\"noopener noreferrer\" target=\"_blank\">W3C:n standardoima<\/a> kryptografinen rajapinta, joka tuli osaksi Node.js:\u00e4\u00e4 versiossa 15.0.0. Ydin on <strong>SubtleCrypto<\/strong>-rajapinta, johon p\u00e4\u00e4see <code>crypto.subtle<\/code>-ominaisuuden kautta. <a href=\"https:\/\/nodejs.org\/api\/webcrypto.html\" rel=\"noopener noreferrer\" target=\"_blank\">Node.js:n virallinen WebCrypto-dokumentaatio<\/a> kattaa kaikki tuetut algoritmit ja metodit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">WebCrypto eroaa vanhasta <code>node:crypto<\/code>-moduulista kahdella keskeisell\u00e4 tavalla. Ensinn\u00e4kin se perustuu W3C-standardiin, joten sama koodi toimii selaimessa ja palvelimella ilman muutoksia. Toiseksi se k\u00e4ytt\u00e4\u00e4 <strong>CryptoKey<\/strong>-objekteja pelk\u00e4n puskuridatan sijaan: avain on olio, jonka sallitut operaatiot on sidottu sen luontiparametreihin. T\u00e4m\u00e4 tekee avainten v\u00e4\u00e4rink\u00e4yt\u00f6st\u00e4 vaikeampaa jo ohjelmointitasolla.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js WebCrypto tukee vuonna 2026 seuraavia algoritmiryhmi\u00e4: AES-GCM, AES-CBC, AES-CTR, AES-KW (salaus ja avainten k\u00e4\u00e4riminen), RSA-OAEP ja RSA-PSS (ep\u00e4symmetrinen salaus ja allekirjoitukset), ECDSA ja ECDH P-256, P-384, P-521 sek\u00e4 X25519 ja X448 (allekirjoitukset ja avaintenvaihto), PBKDF2 ja HKDF (avainjohdannaiset), SHA-256, SHA-384 ja SHA-512 (tiivisteet), ja versiosta 24.7.0 alkaen ChaCha20-Poly1305 ja AES-OCB. Argon2 lis\u00e4ttiin versiossa 24.8.0.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">SubtleCrypto-rajapinta tarjoaa 12 p\u00e4\u00e4metodia: <code>encrypt()<\/code>, <code>decrypt()<\/code>, <code>sign()<\/code>, <code>verify()<\/code>, <code>digest()<\/code>, <code>generateKey()<\/code>, <code>importKey()<\/code>, <code>exportKey()<\/code>, <code>wrapKey()<\/code>, <code>unwrapKey()<\/code>, <code>deriveKey()<\/code> ja <code>deriveBits()<\/code>. Kaikki metodit palauttavat Promise-objekteja, joten ne sopivat luontevasti <code>async\/await<\/code>-syntaksiin. Jokainen metodikutsu on atominen: se joko onnistuu tai heitt\u00e4\u00e4 poikkeuksen. Osittaisen onnistumisen tilaa ei ole. T\u00e4m\u00e4 tekee virheenk\u00e4sittelyst\u00e4 suoraviivaista: <code>try\/catch<\/code>-blokki riitt\u00e4\u00e4 kattamaan kaikki WebCrypto-virhetilat.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Ominaisuus<\/th><th>WebCrypto API (crypto.subtle)<\/th><th>node:crypto<\/th><\/tr><\/thead><tbody><tr><td>Standardi<\/td><td>W3C WebCrypto API<\/td><td>Node.js-spesifinen<\/td><\/tr><tr><td>Selainyhteensopivuus<\/td><td>Kyll\u00e4, sama koodi<\/td><td>Ei<\/td><\/tr><tr><td>Avaintyypit<\/td><td>CryptoKey-objektit<\/td><td>KeyObject tai Buffer<\/td><\/tr><tr><td>Ed25519-tuki<\/td><td>Ei (ei W3C-standardissa)<\/td><td>Kyll\u00e4 (v15.0.0+)<\/td><\/tr><tr><td>Argon2-tuki<\/td><td>Kyll\u00e4 (Node.js v24.8.0+)<\/td><td>Ei<\/td><\/tr><tr><td>ChaCha20-Poly1305<\/td><td>Kyll\u00e4 (Node.js v24.7.0+)<\/td><td>Kyll\u00e4 (aiemmin)<\/td><\/tr><tr><td>API-tyyli<\/td><td>Promise-pohjainen<\/td><td>Callback ja Promise<\/td><\/tr><tr><td>Avainten suojaus<\/td><td>extractable: false est\u00e4\u00e4 vuodon<\/td><td>Ei suoraa vastaavaa<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"esitietovaatimukset\">Esitietovaatimukset<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Tarvitset seuraavat ennen kuin aloitat:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Node.js 18.x LTS tai uudempi<\/strong>, suositellaan 22.x LTS tai 24.x. Versio 18 on minimivaatimus vakaalle WebCrypto-tuelle. ChaCha20-Poly1305 ja <code>SubtleCrypto.supports()<\/code> vaativat version 24.7.0.<\/li><li><strong>npm 9+<\/strong> tai pnpm tai yarn projektin hallintaan<\/li><li>Perustiedot JavaScriptist\u00e4 ja <code>async\/await<\/code>-syntaksista<\/li><li>Tekstieditori, esimerkiksi VS Code tai Neovim<\/li><li>Terminaali: Unix-komentorivi tai WSL Windowsilla<\/li><\/ul>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Node.js-versio<\/th><th>WebCrypto-tila<\/th><th>Huomioitavaa<\/th><\/tr><\/thead><tbody><tr><td>v15.0.0<\/td><td>Ensimm\u00e4inen julkaisu<\/td><td>Kokeellinen, ei tuotantoon<\/td><\/tr><tr><td>v16.x<\/td><td>Vakaa<\/td><td>X448 lis\u00e4tty v16.17.0:ssa<\/td><\/tr><tr><td>v18.x LTS<\/td><td>T\u00e4ysin vakaa<\/td><td>X25519 lis\u00e4tty v18.4.0:ssa, minimisuositus<\/td><\/tr><tr><td>v20.x LTS<\/td><td>Vakaa<\/td><td>Kaikki keskeiset algoritmit mukana<\/td><\/tr><tr><td>v22.x LTS<\/td><td>Vakaa, suositeltava<\/td><td>Pitk\u00e4aikainen tuki 2027 asti<\/td><\/tr><tr><td>v24.7.0+<\/td><td>Laajennettu<\/td><td>ChaCha20-Poly1305, AES-OCB, supports()<\/td><\/tr><tr><td>v24.8.0+<\/td><td>Laajennettu<\/td><td>Argon2-avainjohdannainen WebCryptossa<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-1-projektin-alustaminen\">Vaihe 1 \u2013 Projektin alustaminen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Luo uusi hakemisto ja alusta npm-projekti. WebCrypto on sis\u00e4\u00e4nrakennettu Node.js:\u00e4\u00e4n, eik\u00e4 vaadi ulkoisia riippuvuuksia:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir webcrypto-projekti\ncd webcrypto-projekti\nnpm init -y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Avaa <code>package.json<\/code> ja lis\u00e4\u00e4 <code>\"type\": \"module\"<\/code>-kentt\u00e4. T\u00e4m\u00e4 mahdollistaa ylimm\u00e4n tason <code>await<\/code>-avainsanan ilman erillist\u00e4 <code>async<\/code>-funktiota, mik\u00e4 tekee koodista selke\u00e4mp\u00e4\u00e4 oppimisen kannalta:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"name\": \"webcrypto-projekti\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\"\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Luo p\u00e4\u00e4ohjelmatiedosto ja tarkista ett\u00e4 WebCrypto toimii:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>touch index.js\nnode -e \"console.log(typeof crypto.subtle === 'object' ? 'WebCrypto OK' : 'Ei tuettu')\"\n# Tulostaa: WebCrypto OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>globalThis.crypto<\/code> on saatavilla automaattisesti Node.js 18+:ssa. Et tarvitse <code>require<\/code>&#8211; tai <code>import<\/code>-lausetta. Vanhemmissa versioissa (15.x\u201317.x) voit k\u00e4ytt\u00e4\u00e4 <code>const { webcrypto: crypto } = require('crypto')<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-2-turvallinen-satunnaisluku-getrandomvalues-metodilla\">Vaihe 2 \u2013 Turvallinen satunnaisluku getRandomValues()-metodilla<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Kryptografinen satunnaisluku on kaiken turvallisen kryptografian perusta. <code>Math.random()<\/code> ei koskaan sovi salausavaimiin, alustusvektoreihin tai suolaarvoihin, koska se ei ole kryptografisesti turvallinen. WebCryton <code>crypto.getRandomValues()<\/code> k\u00e4ytt\u00e4\u00e4 k\u00e4ytt\u00f6j\u00e4rjestelm\u00e4n kryptografista satunnaislukugeneraattoria (<code>\/dev\/urandom<\/code> Linuxilla, <code>BCryptGenRandom<\/code> Windowsilla).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lis\u00e4\u00e4 <code>index.js<\/code>-tiedostoon:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 2: Turvallinen satunnaisluku\n\n\/\/ 32 tavua (256 bitti\u00e4) \u2013 sopii salausavainmateriaaliksi\nconst bytes32 = crypto.getRandomValues(new Uint8Array(32));\nconsole.log('Satunnaistavut (hex):', Buffer.from(bytes32).toString('hex'));\n\n\/\/ 12 tavun alustusvektori AES-GCM-salaukseen\nconst iv = crypto.getRandomValues(new Uint8Array(12));\nconsole.log('AES-GCM IV (hex):', Buffer.from(iv).toString('hex'));\n\n\/\/ Satunnainen UUID v4\nconst uuid = crypto.randomUUID();\nconsole.log('UUID:', uuid);<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>node index.js\n# Satunnaistavut (hex): a3f2c1d8e4b59f7a2c3d1e0f8b7a6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0ab\n# AES-GCM IV (hex): 9f1a2b3c4d5e6f7a8b9c0d1e\n# UUID: 550e8400-e29b-41d4-a716-446655440000<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>getRandomValues()<\/code> hyv\u00e4ksyy vain tyypitetyn taulukon (<code>Uint8Array<\/code>, <code>Uint16Array<\/code> tai <code>Uint32Array<\/code>). Maksimikoko on 65 536 tavua yhdell\u00e4 kutsulla. Jos tarvitset yli 64 kilotavua kerralla, kutsu metodia useammin pienemmiss\u00e4 eriss\u00e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-3-sha-tiivisteet-subtle-digest-metodilla\">Vaihe 3 \u2013 SHA-tiivisteet subtle.digest()-metodilla<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">WebCryton <code>crypto.subtle.digest()<\/code> laskee kryptografisia tiivisteit\u00e4. Tuetut algoritmit ovat SHA-256, SHA-384 ja SHA-512. SHA-1 ja MD5 eiv\u00e4t ole tuettu WebCrypton kautta, koska ne ovat kryptografisesti murtuneet. Lue lis\u00e4\u00e4 tiivistefunktioiden teoriasta <a href=\"\/fi\/blake3-hash-nodejs\/\">BLAKE3-oppaastamme<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tiivistefunktio ottaa sy\u00f6tteekseen <code>ArrayBuffer<\/code>&#8211; tai <code>TypedArray<\/code>-dataa. Muunna tekstimerkkijono <code>TextEncoder<\/code>:lla, joka on globaali sek\u00e4 selaimessa ett\u00e4 Node.js:ss\u00e4 versiosta 11 alkaen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 3: SHA-tiivisteet\n\nconst enc = new TextEncoder();\nconst viesti = enc.encode('Hei, WebCrypto!');\n\nconst sha256 = await crypto.subtle.digest('SHA-256', viesti);\nconsole.log('SHA-256:', Buffer.from(sha256).toString('hex'));\nconsole.log('SHA-256 pituus:', sha256.byteLength, 'tavua'); \/\/ 32\n\nconst sha512 = await crypto.subtle.digest('SHA-512', viesti);\nconsole.log('SHA-512:', Buffer.from(sha512).toString('hex'));\nconsole.log('SHA-512 pituus:', sha512.byteLength, 'tavua'); \/\/ 64\n\nconst sha384 = await crypto.subtle.digest('SHA-384', viesti);\nconsole.log('SHA-384:', Buffer.from(sha384).toString('hex'));\nconsole.log('SHA-384 pituus:', sha384.byteLength, 'tavua'); \/\/ 48<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">SHA-256 tuottaa aina 32 tavua (64 heksadesimaalista merkki\u00e4), SHA-384 tuottaa 48 tavua ja SHA-512 tuottaa 64 tavua. <code>digest()<\/code> on deterministinen: sama sy\u00f6te tuottaa aina saman tulosteen. Se sopii datan eheyden tarkistukseen, mutta <strong>ei salasanojen tallentamiseen<\/strong>, koska tiivistefunktio on liian nopea raa&#8217;an voiman hy\u00f6kk\u00e4yksi\u00e4 vastaan. Salasanoille k\u00e4yt\u00e4 PBKDF2:ta tai Argon2:ta seuraavissa vaiheissa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-4-pbkdf2-avainjohdannainen-salasanoista\">Vaihe 4 \u2013 PBKDF2-avainjohdannainen salasanoista<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PBKDF2 (Password-Based Key Derivation Function 2) muuntaa salasanan kryptografiseksi avaimeksi. Se toistaa hajautuslaskennan tuhansia kertoja hidastaen raa&#8217;an voiman hy\u00f6kk\u00e4yksi\u00e4 merkitt\u00e4v\u00e4sti. <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Cryptographic_Storage_Cheat_Sheet.html\" rel=\"noopener noreferrer\" target=\"_blank\">OWASP suosittelee vuodelle 2026<\/a> v\u00e4hint\u00e4\u00e4n 310 000 iteraatiota SHA-256:n kanssa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">PBKDF2 vaatii kaksi vaihetta: ensin salasana tuodaan <code>importKey()<\/code>:lla &#8220;raakamateriaaliksi&#8221;, sitten varsinainen avain johdetaan <code>deriveKey()<\/code>:lla. Sama rakenne sallii yhden perusmateriaalin k\u00e4yt\u00f6n useiden eri avainten johtamiseen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 4: PBKDF2-avainjohdannainen\n\nconst enc = new TextEncoder();\nconst salasana = 'oikea-hevonen-paristo-hakasulku';\n\n\/\/ Satunnainen suola \u2013 tallennetaan salatun datan rinnalle\nconst suola = crypto.getRandomValues(new Uint8Array(16));\n\n\/\/ Tuo salasana avainmateriaaliksi\nconst perusAvain = await crypto.subtle.importKey(\n  'raw',\n  enc.encode(salasana),\n  'PBKDF2',\n  false,           \/\/ ei extrahoida\n  ['deriveKey']\n);\n\n\/\/ Johda 256-bittinen AES-GCM-avain\nconst avain = await crypto.subtle.deriveKey(\n  {\n    name: 'PBKDF2',\n    salt: suola,\n    iterations: 310_000,   \/\/ OWASP 2026 -suositus\n    hash: 'SHA-256',\n  },\n  perusAvain,\n  { name: 'AES-GCM', length: 256 },\n  false,           \/\/ avain ei ole extrahoitavissa tuotannossa\n  ['encrypt', 'decrypt']\n);\n\nconsole.log('PBKDF2-avain luotu onnistuneesti');\nconsole.log('Suola (tallenna t\u00e4m\u00e4!):', Buffer.from(suola).toString('hex'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Suola on tallennettava salatun datan rinnalle, koska sama avain voidaan johtaa vain samalla suolalla ja salasanalla. Suola ei ole salainen, mutta sen on oltava ainutlaatuinen jokaiselle k\u00e4ytt\u00e4j\u00e4lle tai salasanalle. 310 000 iteraatiota hidastaa PBKDF2-laskentaa tarkoituksellisesti, noin 150-300 millisekuntia tavallisella palvelimella. Raa&#8217;an voiman hy\u00f6kk\u00e4\u00e4j\u00e4 hidastuu samassa suhteessa. Jos tarvitset muistivaikean vaihtoehdon, k\u00e4yt\u00e4 Argon2:ta Node.js 24.8.0+:ssa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">PBKDF2:n SHA-256:n 310 000 iteraatiota on OWASP:n 2026-suositus, koska se tarjoaa riitt\u00e4v\u00e4n hitauden nykyisill\u00e4 GPU-hy\u00f6kk\u00e4yksill\u00e4. Vertailun vuoksi: SHA-512:lla 120 000 iteraatiota tarjoaa vastaavan suojaustason, ja SHA-1:n kanssa OWASP suosittelee 1 300 000 iteraatiota. Iteraatiom\u00e4\u00e4r\u00e4 kannattaa tehd\u00e4 konfiguroitavaksi, jotta se voidaan nostaa tulevaisuudessa ilman salasanojen uudelleenhajautusta: tallenna iteraatiom\u00e4\u00e4r\u00e4 suolan rinnalle ja k\u00e4yt\u00e4 tallennettua arvoa purkaessa. T\u00e4m\u00e4 mahdollistaa asteittaisen siirtymisen korkeampiin iteraatiom\u00e4\u00e4riin kirjautumisen yhteydess\u00e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Argon2:ta WebCryton kautta (Node.js 24.8.0+) harkitaan tilanteissa, joissa PBKDF2:n suorituskyky ei riit\u00e4 hy\u00f6kk\u00e4yksi\u00e4 vastaan: Argon2id on muistivaikea, joten GPU-hy\u00f6kk\u00e4ys on paljon kalliimpi kuin PBKDF2:lla. Argon2id-parametrit Node.js WebCryptossa noudattavat OWASP:n 2026-suositusta: muistiparametri 19 456 kilotavua, aikalaskelma 2, rinnakkaisuus 1 v\u00e4hint\u00e4\u00e4n. N\u00e4m\u00e4 vastaavat bcrypt-laskennan kovuuskerrointa 12 turvallisuustasoltaan. Lue lis\u00e4\u00e4 salasanatiivisteist\u00e4 <a href=\"\/fi\/turvalliset-sessiot-nodejs\/\">istunnonhallintaoppaastamme<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-5-aes-gcm-salaus-ja-purku\">Vaihe 5 \u2013 AES-GCM-salaus ja purku<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">AES-GCM (Advanced Encryption Standard Galois\/Counter Mode) on WebCryton suositelluin salausalgorimi vuonna 2026. Se tarjoaa sek\u00e4 luottamuksellisuuden (salaus) ett\u00e4 eheyden varmistuksen (autentikointikoodi), joten erillist\u00e4 HMAC-laskentaa ei tarvita. 256-bittinen AES-GCM on NIST-sertifioitu ja <a href=\"https:\/\/www.iana.org\/assignments\/aead-parameters\/aead-parameters.xhtml\" rel=\"noopener noreferrer\" target=\"_blank\">IANA:n AEAD-parametrirekisterin<\/a> listattu standardi.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">AES-GCM vaatii 12 tavun alustusvektorin (IV). IV:n on oltava ainutlaatuinen jokaiselle salausoperaatiolle samalla avaimella. IV:n toistaminen saman avaimen kanssa vaarantaa sek\u00e4 luottamuksellisuuden ett\u00e4 eheyden. K\u00e4yt\u00e4 aina <code>getRandomValues()<\/code> IV:n generointiin:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 5: AES-GCM-salaus ja purku\n\/\/ (jatkaa vaiheen 4 avain-muuttujaa)\n\nasync function salaaData(avain, teksti) {\n  const enc = new TextEncoder();\n  const iv = crypto.getRandomValues(new Uint8Array(12));\n\n  const salattu = await crypto.subtle.encrypt(\n    { name: 'AES-GCM', iv },\n    avain,\n    enc.encode(teksti)\n  );\n\n  \/\/ Palauta IV + salattu data yhten\u00e4 puskurina\n  const tulos = new Uint8Array(12 + salattu.byteLength);\n  tulos.set(iv, 0);\n  tulos.set(new Uint8Array(salattu), 12);\n  return tulos;\n}\n\nasync function puraData(avain, yhdistettyData) {\n  const iv = yhdistettyData.slice(0, 12);\n  const ciphertext = yhdistettyData.slice(12);\n\n  const plaintext = await crypto.subtle.decrypt(\n    { name: 'AES-GCM', iv },\n    avain,\n    ciphertext\n  );\n\n  return new TextDecoder().decode(plaintext);\n}\n\nconst salattuData = await salaaData(avain, 'T\u00e4m\u00e4 on salainen viesti');\nconsole.log('Salattu (base64):', Buffer.from(salattuData).toString('base64'));\nconsole.log('Salattu koko:', salattuData.byteLength, 'tavua');\n\nconst palautettu = await puraData(avain, salattuData);\nconsole.log('Purettu:', palautettu);<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>Salattu (base64): mK3f9aZ1Xp2qR7sT+uV5wX4yZ6aB3cD2eF1gH0iJkLmN...\nSalattu koko: 57 tavua\nPurettu: T\u00e4m\u00e4 on salainen viesti<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pakettimalla IV salatun datan eteen tallennuksen yhteydess\u00e4 varmistat, ett\u00e4 kaikki puramiseen tarvittava tieto on yhdess\u00e4 paikassa. AES-GCM:n 16-tavun autentikointikoodi (GCM-tunniste) sis\u00e4ltyy automaattisesti <code>encrypt()<\/code>:n tuottamaan salatekstiin. Jos data on muuttunut tai IV on v\u00e4\u00e4r\u00e4, <code>decrypt()<\/code> heitt\u00e4\u00e4 <code>DOMException<\/code>-virheen sen sijaan ett\u00e4 palauttaisi v\u00e4\u00e4r\u00e4\u00e4 dataa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">AES-GCM tukee my\u00f6s valinnaista <strong>lis\u00e4tietokentt\u00e4\u00e4<\/strong> (additionalData, eli AAD), joka lis\u00e4t\u00e4\u00e4n autentikointilaskentaan mutta ei salata. T\u00e4m\u00e4 mahdollistaa salauksen sitomisen kontekstiinsa: voit esimerkiksi sis\u00e4llytt\u00e4\u00e4 tietueId:n tai k\u00e4ytt\u00e4j\u00e4tunnuksen additionalData-kentt\u00e4\u00e4n, jolloin salattu data ei kelpaa ilman oikeaa kontekstia. Tietue, jonka additionalData on <code>user:123<\/code>, ei aukea user:456:n kontekstissa, vaikka avain ja IV olisivat samat. K\u00e4yt\u00e4 t\u00e4t\u00e4 ominaisuutta tietokantapohjaisissa sovelluksissa est\u00e4m\u00e4\u00e4n salattujen rivien uudelleenk\u00e4ytt\u00f6hy\u00f6kk\u00e4yksi\u00e4:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ AES-GCM additionalData-esimerkki\nconst enc = new TextEncoder();\nconst kayttajaId = enc.encode('user:123');\n\nconst salattuAad = await crypto.subtle.encrypt(\n  { name: 'AES-GCM', iv, additionalData: kayttajaId },\n  avain,\n  enc.encode('Salainen data')\n);\n\n\/\/ Purku onnistuu vain oikealla kontekstilla\nconst purettuAad = await crypto.subtle.decrypt(\n  { name: 'AES-GCM', iv, additionalData: kayttajaId },\n  avain,\n  salattuAad\n);\nconsole.log(new TextDecoder().decode(purettuAad)); \/\/ Salainen data<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">AES-GCM:n 12-tavun IV on standardin mukainen ja optimaalinen valinta: lyhyempi IV heikent\u00e4\u00e4 turvallisuutta ja pidempi IV vaatii ylim\u00e4\u00e4r\u00e4isen hajautuslaskennan. Jotkut \u00e4\u00e4rimm\u00e4isesti suorituskyky\u00e4 vaativat sovellukset k\u00e4ytt\u00e4v\u00e4t laskuria IV:n sijaan (counter-based nonce), mutta t\u00e4m\u00e4 vaatii huolellista toteutusta samanaikaisuuden hallitsemiseksi. Satunnainen IV <code>getRandomValues(new Uint8Array(12))<\/code>:lla on turvallinen oletusvalinta kaikissa tapauksissa, joissa salausm\u00e4\u00e4r\u00e4 pysyy alle 2^32:ssa samalla avaimella.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-6-hkdf-avainjohdannainen-istuntoavaimille\">Vaihe 6 \u2013 HKDF-avainjohdannainen istuntoavaimille<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HKDF (HMAC-based Key Derivation Function, RFC 5869) on suunniteltu tilanteisiin, joissa sinulla on jo korkealaatuista avainmateriaalia, kuten Diffie-Hellman-vaihdon tulos tai satunnainen tavu. Siit\u00e4 halutaan johtaa useita eritarkoituksisia avaimia. PBKDF2 on suunniteltu salasanoille, HKDF avaintenvaihdoille ja olemassa oleville avaimille.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">HKDF:n <code>info<\/code>-parametri on sovelluspesifinen kontekstimerkkijono, joka sitoo johdetun avaimen k\u00e4ytt\u00f6tarkoitukseensa. Se ei ole salainen, mutta erottaa saman perusmateriaalin eri k\u00e4ytt\u00f6tarkoituksiin johdetut avaimet toisistaan:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 6: HKDF-avainjohdannainen\n\nconst enc = new TextEncoder();\nconst avainMateriaali = crypto.getRandomValues(new Uint8Array(32));\n\n\/\/ Tuo avainmateriaali HKDF:lle\nconst hkdfPerus = await crypto.subtle.importKey(\n  'raw',\n  avainMateriaali,\n  'HKDF',\n  false,\n  ['deriveKey']\n);\n\nconst hkdfSuola = crypto.getRandomValues(new Uint8Array(32));\n\n\/\/ Johda AES-GCM-avain viestinn\u00e4lle\nconst viestintaAvain = await crypto.subtle.deriveKey(\n  {\n    name: 'HKDF',\n    hash: 'SHA-256',\n    salt: hkdfSuola,\n    info: enc.encode('viestintaavain-v1'),\n  },\n  hkdfPerus,\n  { name: 'AES-GCM', length: 256 },\n  false,\n  ['encrypt', 'decrypt']\n);\n\n\/\/ Johda toinen avain eri k\u00e4ytt\u00f6tarkoitukseen\nconst varmuusAvain = await crypto.subtle.deriveKey(\n  {\n    name: 'HKDF',\n    hash: 'SHA-256',\n    salt: hkdfSuola,\n    info: enc.encode('varmuusavain-v1'),   \/\/ eri konteksti -> eri avain\n  },\n  hkdfPerus,\n  { name: 'AES-GCM', length: 256 },\n  false,\n  ['encrypt', 'decrypt']\n);\n\nconsole.log('HKDF-avaimet johdettu onnistuneesti');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sis\u00e4llyt\u00e4 <code>info<\/code>-merkkijonoon versionumero (<code>'v1'<\/code>, <code>'v2'<\/code>), jotta avaimet voidaan kierr\u00e4tt\u00e4\u00e4 tulevaisuudessa purkamisen mahdollistaminen vanhalla avaimella samalla kun uudet tiedot salataan uudella avaimella.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-7-ecdsa-digitaaliset-allekirjoitukset\">Vaihe 7 \u2013 ECDSA-digitaaliset allekirjoitukset<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA (Elliptic Curve Digital Signature Algorithm) mahdollistaa viestin allekirjoittamisen yksityisell\u00e4 avaimella ja allekirjoituksen verifioimisen julkisella avaimella. WebCrypto tukee P-256, P-384 ja P-521 -k\u00e4yri\u00e4. P-256:n turvallisuustaso on 128 bitti\u00e4, mik\u00e4 riitt\u00e4\u00e4 useimpiin sovelluksiin vuoteen 2030 asti NIST:n arvion mukaan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4rke\u00e4 huomio: <strong>Ed25519 ei kuulu W3C WebCrypto -standardiin<\/strong>. Se puuttuu SubtleCrypto-rajapinnasta, koska Ed25519 k\u00e4ytt\u00e4\u00e4 PureEdDSA-mallia ilman esihajautusta, mik\u00e4 ei sovi yhteen WebCryptoAPI:n nykyisen allekirjoitusarkkitehtuurin kanssa. Ed25519 on kuitenkin saatavilla <code>node:crypto<\/code>-moduulin kautta. Lue <a href=\"\/fi\/ed25519-allekirjoitus-nodejs\/\">Ed25519-oppaastamme<\/a> lis\u00e4tietoja t\u00e4st\u00e4 vaihtoehdosta.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 7: ECDSA-digitaaliset allekirjoitukset\n\nconst enc = new TextEncoder();\n\n\/\/ Luo P-256 ECDSA-avainpari\nconst avainpari = await crypto.subtle.generateKey(\n  { name: 'ECDSA', namedCurve: 'P-256' },\n  true,                    \/\/ extractable: avaimet voidaan vied\u00e4 JWK:ksi\n  ['sign', 'verify']\n);\n\nconst viesti = enc.encode('T\u00e4m\u00e4 viesti allekirjoitetaan ECDSA P-256:lla');\n\n\/\/ Allekirjoita yksityisell\u00e4 avaimella\nconst allekirjoitus = await crypto.subtle.sign(\n  { name: 'ECDSA', hash: 'SHA-256' },\n  avainpari.privateKey,\n  viesti\n);\n\nconsole.log('Allekirjoitus (hex):', Buffer.from(allekirjoitus).toString('hex'));\nconsole.log('Allekirjoituksen pituus:', allekirjoitus.byteLength, 'tavua'); \/\/ 70\u201372\n\n\/\/ Verifioi julkisella avaimella\nconst kelpaa = await crypto.subtle.verify(\n  { name: 'ECDSA', hash: 'SHA-256' },\n  avainpari.publicKey,\n  allekirjoitus,\n  viesti\n);\nconsole.log('Allekirjoitus kelvollinen:', kelpaa);  \/\/ true\n\n\/\/ Testaa muutetulla viestill\u00e4\nconst muutettu = enc.encode('T\u00e4m\u00e4 on eri viesti');\nconst vaaraKelpaa = await crypto.subtle.verify(\n  { name: 'ECDSA', hash: 'SHA-256' },\n  avainpari.publicKey,\n  allekirjoitus,\n  muutettu\n);\nconsole.log('Muutettu viesti kelvollinen:', vaaraKelpaa);  \/\/ false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA-allekirjoituksen pituus P-256:lla vaihtelee 70-72 tavun v\u00e4lill\u00e4, koska allekirjoituksen kaksi komponenttia (r ja s) ovat DER-enkoodattuja ja niiden pituus vaihtelee. Jos tarvitset kiinte\u00e4\u00e4 64 tavun allekirjoitusta, harkitse HMAC-SHA256:ta tai Ed25519:\u00e4\u00e4 <code>node:crypto<\/code>:n kautta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA P-256 -avainparin generaatio on nopea: alle millisekunnin nykyaikaisella laitteistolla. Allekirjoittaminen on my\u00f6s nopea, noin 0,1-0,5 millisekuntia. Verifiointi on hivenen hitaampaa kuin allekirjoittaminen P-256:lla. Vertailuksi: RSA-2048-allekirjoitus kest\u00e4\u00e4 allekirjoitusoperaatiossa noin 1-3 millisekuntia ja verifiointi alle millisekunnin. ECDSA P-256 on siis nopeampi allekirjoittamisessa kuin RSA-2048. Kun kyseess\u00e4 on palvelinkuorma, jossa allekirjoitetaan tuhansia pyynt\u00f6j\u00e4 sekunnissa, P-256-valinta on perusteltu sek\u00e4 suorituskyvyn ett\u00e4 turvallisuuden puolesta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA:n k\u00e4ytt\u00f6 JWT-tokeneiden allekirjoittamiseen on yleinen k\u00e4ytt\u00f6tapaus. ES256 (ECDSA P-256 SHA-256) on yksi virallisista JWT RFC 7518 -algoritmeista. Se on vaihtoehto HS256:lle (HMAC-SHA256), kun allekirjoitus t\u00e4ytyy voida verifioida julkisella avaimella ilman, ett\u00e4 verifikoijalla on p\u00e4\u00e4sy yksityiseen avaimeen. T\u00e4m\u00e4 soveltuu erityisesti mikropalveluarkkitehtuureihin, joissa yksi palvelu allekirjoittaa tokenin ja muut palvelut verifioivat sen omalla julkisella avaimellaan. Lue lis\u00e4\u00e4 <a href=\"\/fi\/jwt-todennus-nodejs\/\">JWT-todennusoppaastamme<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-8-avainten-vienti-ja-tuonti-jwk-muodossa\">Vaihe 8 \u2013 Avainten vienti ja tuonti JWK-muodossa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">JWK (JSON Web Key) on standardoitu muoto kryptografisten avainten esitt\u00e4miseen JSON-objekteina. WebCrypto tukee avainten vienti\u00e4 nelj\u00e4ss\u00e4 muodossa: <code>raw<\/code> (symmetriset avaimet), <code>pkcs8<\/code> (yksityiset avaimet), <code>spki<\/code> (julkiset avaimet) ja <code>jwk<\/code>. JWK on suositeltavin, koska se sis\u00e4lt\u00e4\u00e4 algoritmin metatiedot eik\u00e4 vaadi erillist\u00e4 ASN.1-enkoodausta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Avain on oltava extrahoitavissa (<code>extractable: true<\/code>) jo luontivaiheessa. Jos loit avaimen <code>false<\/code>:lla, sit\u00e4 ei voi en\u00e4\u00e4 muuttaa extrahoitavaksi j\u00e4lkik\u00e4teen. Suunnittele avainten elinkaari ennen koodin kirjoittamista:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 8: JWK-vienti ja tuonti\n\n\/\/ Vie julkinen avain JWK-muodossa\nconst julkinenJWK = await crypto.subtle.exportKey('jwk', avainpari.publicKey);\nconsole.log('Julkinen avain (JWK):', JSON.stringify(julkinenJWK, null, 2));\n\n\/\/ Tuo avain takaisin JWK:sta\nconst tuotuJulkinen = await crypto.subtle.importKey(\n  'jwk',\n  julkinenJWK,\n  { name: 'ECDSA', namedCurve: 'P-256' },\n  true,\n  ['verify']   \/\/ julkinen avain vain verifiointiin\n);\n\n\/\/ Verifioi alkuper\u00e4isell\u00e4 allekirjoituksella tuodulla avaimella\nconst toimii = await crypto.subtle.verify(\n  { name: 'ECDSA', hash: 'SHA-256' },\n  tuotuJulkinen,\n  allekirjoitus,\n  viesti\n);\nconsole.log('Tuotu avain toimii:', toimii);  \/\/ true\n\n\/\/ Vie yksityinen avain PKCS8-muodossa (yleinen tallennusmuoto)\nconst yksityinenPKCS8 = await crypto.subtle.exportKey(\n  'pkcs8',\n  avainpari.privateKey\n);\nconsole.log('Yksityinen avain (PKCS8, tavua):', yksityinenPKCS8.byteLength);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">JWK-muotoinen julkinen P-256-avain n\u00e4ytt\u00e4\u00e4 t\u00e4lt\u00e4:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"kty\": \"EC\",\n  \"crv\": \"P-256\",\n  \"x\": \"mPUKT_bAWGHIhg0TpjjqVsP1rXWQu_vwVOHHtNkdYoA\",\n  \"y\": \"8BQAsImGeAS46fyWw5MhYfGTT0IjBpFw2SS34Dv4Irs\",\n  \"key_ops\": [\"verify\"],\n  \"ext\": true\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Turvallisuushuomio:<\/strong> JWK-muotoinen yksityinen avain sis\u00e4lt\u00e4\u00e4 <code>\"d\"<\/code>-kent\u00e4n, joka on itse yksityinen avain base64url-muodossa. Tallenna yksityiset avaimet vain salattuun tietokantaan tai laitteistoon suojattuun avainvarastoon (HSM, AWS KMS, Google Cloud KMS). \u00c4l\u00e4 koskaan tallenna yksityisi\u00e4 avaimia ymp\u00e4rist\u00f6muuttujiin tuotantoymp\u00e4rist\u00f6iss\u00e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-9-aes-gcm-avainten-kaariminen-wrapkeylla\">Vaihe 9 \u2013 AES-GCM-avainten k\u00e4\u00e4riminen wrapKey():lla<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Avainten k\u00e4\u00e4riminen (key wrapping) tarkoittaa yhden kryptografisen avaimen salaamista toisella avaimella turvallista tallennusta tai siirtoa varten. WebCrytossa t\u00e4h\u00e4n on erityiset <code>wrapKey()<\/code> ja <code>unwrapKey()<\/code>-metodit. Ne ovat turvallisempia kuin avaimen vienti JWK:ksi ja salaaminen erikseen, koska raaka avainmateriaali ei joudu JavaScript-muuttujaan v\u00e4livaiheessa.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 9: Avainten k\u00e4\u00e4riminen\n\n\/\/ K\u00e4\u00e4rimisavain \u2013 hallitsee sis\u00e4lt\u00f6avaimia\nconst kaarimisAvain = await crypto.subtle.generateKey(\n  { name: 'AES-GCM', length: 256 },\n  false,   \/\/ k\u00e4\u00e4rimisavain pysyy ei-extrahoitavana\n  ['wrapKey', 'unwrapKey']\n);\n\n\/\/ Sis\u00e4lt\u00f6avain \u2013 k\u00e4ytet\u00e4\u00e4n varsinaisen datan salaamiseen\nconst sisaltoAvain = await crypto.subtle.generateKey(\n  { name: 'AES-GCM', length: 256 },\n  true,    \/\/ oltava true, jotta voidaan k\u00e4\u00e4ri\u00e4\n  ['encrypt', 'decrypt']\n);\n\nconst kaarimisIV = crypto.getRandomValues(new Uint8Array(12));\n\n\/\/ K\u00e4\u00e4ri sis\u00e4lt\u00f6avain k\u00e4\u00e4rimisavaimella\nconst kaarittyAvain = await crypto.subtle.wrapKey(\n  'jwk',\n  sisaltoAvain,\n  kaarimisAvain,\n  { name: 'AES-GCM', iv: kaarimisIV }\n);\n\nconsole.log('K\u00e4\u00e4ritty avain (tavua):', kaarittyAvain.byteLength);\n\n\/\/ Pura avain my\u00f6hemmin k\u00e4ytt\u00f6\u00e4 varten\nconst purettuAvain = await crypto.subtle.unwrapKey(\n  'jwk',\n  kaarittyAvain,\n  kaarimisAvain,\n  { name: 'AES-GCM', iv: kaarimisIV },       \/\/ purkamisparametrit\n  { name: 'AES-GCM', length: 256 },           \/\/ purettavan avaimen tyyppi\n  false,\n  ['encrypt', 'decrypt']\n);\n\nconsole.log('Avain purettu onnistuneesti');\n\n\/\/ Testaa purettua avainta\nconst testaus = await salaaData(purettuAvain, 'testi');\nconst tulos = await puraData(purettuAvain, testaus);\nconsole.log('Puretulla avaimella toimii:', tulos === 'testi');<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-10-chacha20-poly1305-node-js-v24-7-0ssa\">Vaihe 10 \u2013 ChaCha20-Poly1305 Node.js v24.7.0:ssa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js 24.7.0 lis\u00e4si ChaCha20-Poly1305:n WebCrypto-rajapintaan. ChaCha20-Poly1305 on vaihtoehto AES-GCM:lle, erityisesti laitteissa joissa ei ole AES-laitteistokiihdytyst\u00e4 (AES-NI). Mobiililaitteissa ja IoT-laitteissa ChaCha20 on usein nopeampi. WireGuard-protokolla ja TLS 1.3 tukevat molempia algoritmeja.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">ChaCha20-Poly1305:n API on l\u00e4hes identtinen AES-GCM:n kanssa. Molemmat k\u00e4ytt\u00e4v\u00e4t 12-tavun nonce\/IV-arvoa ja tuottavat 16-tavun autentikointikoodin. Tarkista tuki <code>SubtleCrypto.supports()<\/code>:lla ennen k\u00e4ytt\u00f6\u00e4:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 10: ChaCha20-Poly1305 (vaatii Node.js 24.7.0+)\n\nconst onNodejs247Plus = typeof crypto.subtle.supports === 'function';\n\nif (onNodejs247Plus) {\n  const chacha20Tuettu = await crypto.subtle.supports({\n    name: 'ChaCha20-Poly1305',\n  });\n  console.log('ChaCha20-Poly1305 tuettu:', chacha20Tuettu);\n\n  if (chacha20Tuettu) {\n    const chachaAvain = await crypto.subtle.generateKey(\n      { name: 'ChaCha20-Poly1305', length: 256 },\n      true,\n      ['encrypt', 'decrypt']\n    );\n\n    const nonce = crypto.getRandomValues(new Uint8Array(12));\n    const enc = new TextEncoder();\n    const plaintext = enc.encode('Salattu viesti ChaCha20-Poly1305:lla');\n\n    const salattu = await crypto.subtle.encrypt(\n      { name: 'ChaCha20-Poly1305', iv: nonce },\n      chachaAvain,\n      plaintext\n    );\n\n    const purettu = await crypto.subtle.decrypt(\n      { name: 'ChaCha20-Poly1305', iv: nonce },\n      chachaAvain,\n      salattu\n    );\n\n    console.log('ChaCha20 tulos:', new TextDecoder().decode(purettu));\n  }\n} else {\n  console.log('Node.js < 24.7.0 \u2013 ChaCha20-Poly1305 ei saatavilla WebCrytossa');\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Jos sovelluksesi vaatii Node.js 22.x LTS -yhteensopivuuden, pysy AES-GCM:ss\u00e4. Node.js 24.7.0+ -ymp\u00e4rist\u00f6iss\u00e4 voit tarjota molempia algoritmeja ja tarkistaa ajon aikana, kumpi on parempi kyseiselle laitteistolle.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-11-subtlecrypto-supports-ominaisuustarkistus\">Vaihe 11 \u2013 SubtleCrypto.supports()-ominaisuustarkistus<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>SubtleCrypto.supports()<\/code> lis\u00e4ttiin Node.js 24.7.0:ssa ja on my\u00f6s saatavilla nykyaikaisissa selaimissa. Se mahdollistaa algoritmin tuen tarkistamisen ajon aikana ilman try\/catch-blokkeja. T\u00e4m\u00e4 on erityisen hy\u00f6dyllist\u00e4 koodissa, joka toimii useissa ymp\u00e4rist\u00f6iss\u00e4:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vaihe 11: SubtleCrypto.supports()-tarkistus\n\nasync function tarkistaKryptoTuki() {\n  if (typeof crypto.subtle.supports !== 'function') {\n    console.log('supports() ei ole saatavilla \u2013 Node.js < 24.7.0');\n    return;\n  }\n\n  const algoritmit = [\n    { name: 'AES-GCM' },\n    { name: 'AES-CBC' },\n    { name: 'RSA-OAEP', modulusLength: 2048, hash: 'SHA-256' },\n    { name: 'ECDSA', namedCurve: 'P-256' },\n    { name: 'PBKDF2' },\n    { name: 'HKDF' },\n    { name: 'SHA-256' },\n    { name: 'SHA-512' },\n    { name: 'ChaCha20-Poly1305' },\n  ];\n\n  const tuetut = [];\n  for (const algo of algoritmit) {\n    const onTuettu = await crypto.subtle.supports(algo);\n    const merkki = onTuettu ? 'OK' : 'EI';\n    console.log(`[${merkki}] ${algo.name}`);\n    if (onTuettu) tuetut.push(algo.name);\n  }\n\n  console.log(`\\nTuetut algoritmit (${tuetut.length}\/${algoritmit.length}):`, tuetut.join(', '));\n}\n\nawait tarkistaKryptoTuki();<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-12-taydellinen-projekti-salattu-muistiinpano-api\">Vaihe 12 \u2013 T\u00e4ydellinen projekti: salattu muistiinpano-API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Yhdistet\u00e4\u00e4n kaikki aiemmat vaiheet t\u00e4ydelliseksi toimivaksi projektiksi. T\u00e4m\u00e4 esimerkki rakentaa salatun muistiinpanoj\u00e4rjestelm\u00e4n, joka k\u00e4ytt\u00e4\u00e4 PBKDF2-avainjohdannaista salasanasta, AES-GCM:\u00e4\u00e4 datan salaamiseen ja ECDSA:ta datan eheyden allekirjoitukseen. Luo tiedosto <code>muistiinpano-api.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ muistiinpano-api.js \u2013 salattu muistiinpanoj\u00e4rjestelm\u00e4 WebCryptolla\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nasync function alustaSalausAvain(salasana, suola = null) {\n  const kaytettavaSuola = suola ?? crypto.getRandomValues(new Uint8Array(16));\n  const perus = await crypto.subtle.importKey(\n    'raw', enc.encode(salasana), 'PBKDF2', false, ['deriveKey']\n  );\n  const avain = await crypto.subtle.deriveKey(\n    { name: 'PBKDF2', salt: kaytettavaSuola, iterations: 310_000, hash: 'SHA-256' },\n    perus,\n    { name: 'AES-GCM', length: 256 },\n    false,\n    ['encrypt', 'decrypt']\n  );\n  return { avain, suola: kaytettavaSuola };\n}\n\nasync function salaa(avain, teksti) {\n  const iv = crypto.getRandomValues(new Uint8Array(12));\n  const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, avain, enc.encode(teksti));\n  const buf = new Uint8Array(12 + ct.byteLength);\n  buf.set(iv); buf.set(new Uint8Array(ct), 12);\n  return buf;\n}\n\nasync function pura(avain, buf) {\n  const pt = await crypto.subtle.decrypt(\n    { name: 'AES-GCM', iv: buf.slice(0, 12) },\n    avain, buf.slice(12)\n  );\n  return dec.decode(pt);\n}\n\nasync function allekirjoita(yksityinenAvain, teksti) {\n  return crypto.subtle.sign(\n    { name: 'ECDSA', hash: 'SHA-256' }, yksityinenAvain, enc.encode(teksti)\n  );\n}\n\nasync function verifioi(julkinenAvain, allekirjoitus, teksti) {\n  return crypto.subtle.verify(\n    { name: 'ECDSA', hash: 'SHA-256' }, julkinenAvain, allekirjoitus, enc.encode(teksti)\n  );\n}\n\n\/\/ --- P\u00e4\u00e4ohjelma ---\n\n\/\/ Luodaan allekirjoitusavainpari\nconst avainpari = await crypto.subtle.generateKey(\n  { name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']\n);\n\n\/\/ Alustetaan salaus salasanasta\nconst { avain: salausAvain, suola } = await alustaSalausAvain('kestava-salasana-2026');\n\n\/\/ Tallennetaan muistiinpano\nconst muistiinpano = 'T\u00e4rke\u00e4 salainen muistiinpano \u2013 p\u00e4iv\u00e4ys 18.6.2026';\nconst salattuData = await salaa(salausAvain, muistiinpano);\nconst sig = await allekirjoita(avainpari.privateKey, muistiinpano);\n\nconsole.log('Tallennettu:');\nconsole.log('  Koko:', salattuData.byteLength, 'tavua');\nconsole.log('  Suola:', Buffer.from(suola).toString('hex'));\n\n\/\/ Ladataan muistiinpano (simuloi my\u00f6hemp\u00e4\u00e4 lataamista samalla salasanalla)\nconst { avain: palautettuAvain } = await alustaSalausAvain('kestava-salasana-2026', suola);\nconst purettuTeksti = await pura(palautettuAvain, salattuData);\nconst allekirjoitusKelpaa = await verifioi(avainpari.publicKey, sig, purettuTeksti);\n\nconsole.log('\\nLadattu:');\nconsole.log('  Sis\u00e4lt\u00f6:', purettuTeksti);\nconsole.log('  Allekirjoitus kelvollinen:', allekirjoitusKelpaa);<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>node muistiinpano-api.js\n# Tallennettu:\n#   Koko: 77 tavua\n#   Suola: 3a7f2c1d9e8b5f4a...\n#\n# Ladattu:\n#   Sis\u00e4lt\u00f6: T\u00e4rke\u00e4 salainen muistiinpano \u2013 p\u00e4iv\u00e4ys 18.6.2026\n#   Allekirjoitus kelvollinen: true<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"webcrypto-api-vs-nodecrypto-milloin-valita-kumpi\">WebCrypto API vs. node:crypto: milloin valita kumpi?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Valinta riippuu k\u00e4ytt\u00f6tapauksesta. WebCrypto on parempi silloin, kun sama koodi t\u00e4ytyy toimia selaimessa ja palvelimella, tai kun haluat CryptoKey-abstraktion tuoman lis\u00e4turvallisuuden. <code>node:crypto<\/code> tarjoaa enemm\u00e4n algoritmeja ja stream-tuen suurille tiedostoille.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>K\u00e4ytt\u00f6tapaus<\/th><th>Suositus<\/th><th>Perustelu<\/th><\/tr><\/thead><tbody><tr><td>Selain + Node.js yhteinen koodi<\/td><td>WebCrypto API<\/td><td>W3C-standardi toimii molemmissa<\/td><\/tr><tr><td>Ed25519-allekirjoitukset<\/td><td>node:crypto<\/td><td>Ei WebCrypto-standardissa<\/td><\/tr><tr><td>PBKDF2 salasanoille<\/td><td>WebCrypto API<\/td><td>CryptoKey suojaa avainmateriaalin<\/td><\/tr><tr><td>Argon2 salasanoille (paras)<\/td><td>WebCrypto (Node.js 24.8+)<\/td><td>Muistivaikea, lis\u00e4tty v24.8.0:ssa<\/td><\/tr><tr><td>X.509-sertifikaatit<\/td><td>node:crypto tai tls-moduuli<\/td><td>WebCryptolla ei suoraa tukea<\/td><\/tr><tr><td>ECDH-avaintenvaihto<\/td><td>WebCrypto API<\/td><td>X25519 ja P-256 tuettu<\/td><\/tr><tr><td>AES-GCM-salaus<\/td><td>WebCrypto API<\/td><td>CryptoKey est\u00e4\u00e4 IV:n uudelleenk\u00e4yt\u00f6n<\/td><\/tr><tr><td>Stream-salaus suurille tiedostoille<\/td><td>node:crypto<\/td><td>createCipheriv() tukee Node.js-streameja<\/td><\/tr><tr><td>HMAC-viestien autentikointi<\/td><td>Molemmat<\/td><td>Lue <a href=\"\/fi\/hmac-node-js\/\">HMAC-oppaastamme<\/a> lis\u00e4tiedot<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">WebCryptolla ei ole stream-tukea, mik\u00e4 tekee suurten tiedostojen salaamisesta hankalaa: sinun t\u00e4ytyy ladata koko tiedosto muistiin ennen <code>encrypt()<\/code>-kutsua. Jos salaat tiedostoja joiden koko on yli 100 megatavua, k\u00e4yt\u00e4 <code>node:crypto<\/code>:n <code>createCipheriv()<\/code>-streamia.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"8-yleisinta-sudenkuoppaa-node-js-webcrypto-apissa\">8 yleisint\u00e4 sudenkuoppaa Node.js WebCrypto API:ssa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Seuraavat virheet ovat yleisimpi\u00e4 WebCrypto-koodin kirjoittajien joukossa. Tunnista ne ennen kuin ne p\u00e4\u00e4sev\u00e4t tuotantoon:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li><strong>IV:n uudelleenk\u00e4ytt\u00f6 AES-GCM:ss\u00e4.<\/strong> Sama IV kahdesti saman avaimen kanssa romuttaa AES-GCM:n turvallisuuden t\u00e4ysin: hy\u00f6kk\u00e4\u00e4j\u00e4 voi palauttaa plaintext-datan ja vaarantaa avaimen. Generoi aina uusi IV <code>getRandomValues(new Uint8Array(12))<\/code>:lla jokaiselle salausoperaatiolle.<\/li><li><strong>Liian pieni iteraatiom\u00e4\u00e4r\u00e4 PBKDF2:ssa.<\/strong> Vanhemmissa esimerkeiss\u00e4 n\u00e4kyy 1 000 tai 10 000 iteraatiota. OWASP suosittelee 2026:lle v\u00e4hint\u00e4\u00e4n 310 000 iteraatiota SHA-256:n kanssa. Pienemm\u00e4t arvot tekev\u00e4t raa'an voiman hy\u00f6kk\u00e4yksest\u00e4 triviaalia GPU-kiihdytyksell\u00e4.<\/li><li><strong>Suolan uudelleenk\u00e4ytt\u00f6 eri salasanoille.<\/strong> Jokainen k\u00e4ytt\u00e4j\u00e4 tarvitsee ainutlaatuisen suolan. Yhteinen tai tyhj\u00e4 suola mahdollistaa sateenkaaritaulukkohy\u00f6kk\u00e4yksen.<\/li><li><strong>extractable: true tuotantoavaimille.<\/strong> Aseta <code>extractable: false<\/code> kaikille avaimille joita ei tarvitse vied\u00e4. Ei-extrahoitavat avaimet eiv\u00e4t voi vuotaa muistidumpissa tai virheenk\u00e4sittelykoodissa.<\/li><li><strong>Ed25519:n odottaminen WebCryptolta.<\/strong> Ed25519 ei ole W3C WebCrypto -standardissa. <code>generateKey({name:'Ed25519',...})<\/code> heitt\u00e4\u00e4 <code>DOMException: Unrecognized algorithm name<\/code>. K\u00e4yt\u00e4 <code>node:crypto<\/code>:a.<\/li><li><strong>TextEncoder puuttuu hyvin vanhassa Node.js:ss\u00e4.<\/strong> <code>TextEncoder<\/code> on globaali Node.js 11+:ssa. Versioissa 10 ja vanhemmissa se on tuotava: <code>const {TextEncoder} = require('util')<\/code>. Versiossa 18+ t\u00e4m\u00e4 ei ole ongelma.<\/li><li><strong>ArrayBuffer vs Buffer -sekaannus.<\/strong> WebCrypto palauttaa <code>ArrayBuffer<\/code>-objekteja, ei Node.js:n omia <code>Buffer<\/code>-objekteja. Muunna tarvittaessa <code>Buffer.from(arrayBuffer)<\/code>:lla.<\/li><li><strong>Unohdettu IV tai suola tallennuksessa.<\/strong> Salauksen purku on mahdotonta ilman oikeaa IV:t\u00e4. Tallenna IV salatun datan yhteydess\u00e4, esimerkiksi pakettimalla se alkuun. Sama koskee PBKDF2:n suolaa.<\/li><\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vianetsinta-10-yleisinta-ongelmaa-ratkaisuineen\">Vianetsint\u00e4: 10 yleisint\u00e4 ongelmaa ratkaisuineen<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Virhe tai ongelma<\/th><th>Syy<\/th><th>Ratkaisu<\/th><\/tr><\/thead><tbody><tr><td><code>DOMException: Unrecognized algorithm name<\/code><\/td><td>Algoritmi ei tuettu tai kirjoitusvirhe<\/td><td>Tarkista nimi \u2013 se on merkkikokosensittiivinen: <code>'AES-GCM'<\/code> ei <code>'aes-gcm'<\/code>. Tarkista Node.js-versio.<\/td><\/tr><tr><td><code>DOMException: The requested operation is not valid for the provided key<\/code><\/td><td>Avain ei tue pyydett\u00e4v\u00e4\u00e4 operaatiota<\/td><td>Tarkista <code>keyUsages<\/code>-taulukko avaingeneroinnissa. AES-GCM vaatii <code>['encrypt', 'decrypt']<\/code>.<\/td><\/tr><tr><td><code>DOMException: Cannot extract a non-extractable key<\/code><\/td><td>Avain luotu <code>extractable: false<\/code>:lla<\/td><td>Luo avain <code>extractable: true<\/code>:lla tai \u00e4l\u00e4 yrit\u00e4 vied\u00e4 sit\u00e4. Suunnittele elinkaari etuk\u00e4teen.<\/td><\/tr><tr><td><code>DOMException: The provided data is too small<\/code><\/td><td>IV on v\u00e4\u00e4r\u00e4n kokoinen tai tyhj\u00e4<\/td><td>AES-GCM vaatii t\u00e4sm\u00e4lleen 12 tavun IV:n. Tarkista <code>Uint8Array(12)<\/code>.<\/td><\/tr><tr><td>Decrypt heitt\u00e4\u00e4 virheen<\/td><td>IV v\u00e4\u00e4r\u00e4, data muuttunut tai avain ei t\u00e4sm\u00e4\u00e4<\/td><td>AES-GCM:n autentikointi ep\u00e4onnistuu, jos data on muuttunut. Tarkista IV:n tallennus ja palautus.<\/td><\/tr><tr><td><code>TypeError: crypto.subtle is undefined<\/code><\/td><td>Node.js versio alle 15.0.0<\/td><td>P\u00e4ivit\u00e4 Node.js 18+:aan tai k\u00e4yt\u00e4 <code>require('crypto').webcrypto.subtle<\/code>.<\/td><\/tr><tr><td>PBKDF2 on hidas<\/td><td>310 000 iteraatiota tarkoituksellisesti<\/td><td>T\u00e4m\u00e4 on normaalia. Johda avain kerran kirjautumisessa ja pid\u00e4 v\u00e4limuistissa istunnon ajan.<\/td><\/tr><tr><td><code>RangeError: ArrayBufferView's byte length exceeds limit of 65536<\/code><\/td><td><code>getRandomValues()<\/code>:lle liian iso taulukko<\/td><td>Maksimi on 65 536 tavua per kutsu. Jaa useampaan kutsun.<\/td><\/tr><tr><td>Sama ECDSA-allekirjoitus toistuu eri ajokerroilla<\/td><td>Odotettu \u2013 ECDSA ei ole deterministinen<\/td><td>ECDSA k\u00e4ytt\u00e4\u00e4 satunnaista k-arvoa jokaisessa allekirjoituksessa. Verifiointi toimii silti.<\/td><\/tr><tr><td><code>SyntaxError: Cannot use import statement<\/code><\/td><td><code>package.json<\/code> puuttuu <code>\"type\": \"module\"<\/code><\/td><td>Lis\u00e4\u00e4 <code>\"type\": \"module\"<\/code> tai k\u00e4yt\u00e4 <code>.mjs<\/code>-p\u00e4\u00e4tett\u00e4 tiedostolle.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"edistyneet-vinkit-tuotantokayttoon\">Edistyneet vinkit tuotantok\u00e4ytt\u00f6\u00f6n<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Avainten kierr\u00e4tys.<\/strong> Sis\u00e4llyt\u00e4 HKDF:n <code>info<\/code>-parametriin versionumero (<code>'salausavain-v1'<\/code>, <code>'salausavain-v2'<\/code>), jotta vanhoilla avaimilla salattu data voidaan purkaa uuden avaimen k\u00e4ytt\u00f6\u00f6noton j\u00e4lkeenkin. Merkitse salattu data versionumerolla, jotta tied\u00e4t, mill\u00e4 avaimella se on salattu.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Avainten s\u00e4ilytys palvelimilla.<\/strong> \u00c4l\u00e4 tallenna salausavaimia tietokantaan selkokielisin\u00e4. K\u00e4yt\u00e4 AWS KMS:\u00e4\u00e4, Google Cloud KMS:\u00e4\u00e4 tai HashiCorp Vaultia. Jos sinun on tallennettava avain itse, k\u00e4yt\u00e4 <code>wrapKey()<\/code>:ta ja pid\u00e4 k\u00e4\u00e4rimisavain erillisess\u00e4 paikassa kuin salattu data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Vakioaikainen allekirjoitusten vertailu.<\/strong> \u00c4l\u00e4 vertaa allekirjoituksia tai HMAC-koodeja suoraan <code>===<\/code>-operaattorilla. K\u00e4yt\u00e4 <code>crypto.subtle.verify()<\/code>:a, joka suorittaa vakioaikaisen vertailun. Vaihtoehtoisesti k\u00e4yt\u00e4 <code>node:crypto<\/code>:n <code>crypto.timingSafeEqual()<\/code>-funktiota.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>EU:n Cyber Resilience Act 2026.<\/strong> Kyberresilienssiasetus (CRA) asettaa syyskuussa 2026 pakollisiksi raportoitavaksi 24 tunnin ja 72 tunnin haavoittuvuusilmoitusvelvoitteet. K\u00e4ytt\u00e4m\u00e4ll\u00e4 standardoituja algoritmeja (AES-GCM, ECDSA, PBKDF2) WebCrypto-rajapinnan kautta ja dokumentoimalla algoritmivalintasi SBOM:iin olet hyv\u00e4ss\u00e4 asemassa vaatimustenmukaisuuden suhteen. <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/SubtleCrypto\" rel=\"noopener noreferrer\" target=\"_blank\">MDN SubtleCrypto -dokumentaatio<\/a> on paras yksitt\u00e4inen viiteresurssi algoritmiparametreille.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Post-kvantti-valmius.<\/strong> NIST viimeisteli vuonna 2024 ensimm\u00e4iset post-kvantti-kryptografisstandardit (ML-KEM eli Kyber, ML-DSA eli Dilithium). Node.js WebCrypto ei viel\u00e4 tue n\u00e4it\u00e4 algoritmeja, mutta tuki on odotettavissa l\u00e4hivuosien versioissa. T\u00e4ll\u00e4 hetkell\u00e4 parhaat k\u00e4yt\u00e4nn\u00f6t suosittelevat hybridil\u00e4hestymistapaa: k\u00e4yt\u00e4 sek\u00e4 klassisia algoritmeja (ECDH, ECDSA) ett\u00e4 post-kvantti-algoritmeja rinnakkain, jolloin turvallisuus on taattu molemmilla kentill\u00e4. <code>SubtleCrypto.supports()<\/code>-tarkistus mahdollistaa koodin, joka hy\u00f6dynt\u00e4\u00e4 uusia algoritmeja heti kun Node.js lis\u00e4\u00e4 ne tuen piiriin. Lue lis\u00e4\u00e4 post-kvantti-kryptografiasta erillisest\u00e4 artikkelistamme.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Virhelokin rakenne.<\/strong> Kun WebCrypto-metodi heitt\u00e4\u00e4 poikkeuksen, varmista ettei loki paljasta arkaluonteisia tietoja. <code>DOMException<\/code>-virheen viesti on yleens\u00e4 geneerinen (\"The operation failed for an operation-specific reason\"), mik\u00e4 on hyv\u00e4: se ei paljasta yksityisavaimen sis\u00e4lt\u00f6\u00e4 tai salausparametreja. V\u00e4lt\u00e4 catch-blokeissa arkaluonteisen datan tulostamista tai tallentamista logiin, my\u00f6s debug-tarkoituksessa. Salausoperaatioiden ep\u00e4onnistuminen on merkki joko ohjelmointivirheest\u00e4 tai hy\u00f6kk\u00e4ysyrityksest\u00e4: molemmat ansaitsevat selke\u00e4n lokimerkinn\u00e4n ilman herkk\u00e4\u00e4 dataa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Testaus eri Node.js-versioilla.<\/strong> Koska WebCrypto on laajentunut merkitt\u00e4v\u00e4sti versiosta 15 versioon 24, testaa koodi v\u00e4hint\u00e4\u00e4n minimiversiollasi (Node.js 18 LTS) ja uusimmalla versiolla. K\u00e4yt\u00e4 <code>nvm<\/code>:\u00e4\u00e4 (Node Version Manager) versioiden hallintaan kehitysymp\u00e4rist\u00f6iss\u00e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"usein-kysytyt-kysymykset\">Usein kysytyt kysymykset<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mist\u00e4 Node.js-versiosta WebCrypto API on vakaa tuotantok\u00e4ytt\u00f6\u00f6n?<\/strong><br>Node.js 18.x LTS on ensimm\u00e4inen versio, jossa WebCrypto API on t\u00e4ysin vakaa. SubtleCrypto lis\u00e4ttiin teknisesti versiossa 15.0.0, mutta sen toteutuksessa oli viel\u00e4 puutteita. Node.js 18 on minimisuositus uusille projekteille.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Voiko WebCrypto-koodia k\u00e4ytt\u00e4\u00e4 suoraan selaimessa?<\/strong><br>Kyll\u00e4, suurimmaksi osaksi. WebCrypto on W3C-standardi, jota kaikki nykyaikaiset selaimet tukevat. Selaimessa k\u00e4yt\u00e4t <code>window.crypto.subtle<\/code> tai pelkk\u00e4\u00e4 <code>crypto.subtle<\/code>:a, Node.js:ss\u00e4 sama rakenne toimii versiosta 18 alkaen. Jotkut algoritmit (kuten ChaCha20-Poly1305) ovat tulleet selaimiin ennen Node.js:\u00e4\u00e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Miksi PBKDF2 on niin hidas, onko se virhe?<\/strong><br>Ei. Hidas toiminta on tarkoituksellinen turvaominaisuus: 310 000 iteraatiota hidastaa raa'an voiman hy\u00f6kk\u00e4yst\u00e4 niin, ett\u00e4 GPU:lla voidaan testata vain muutamia tuhansia salasanoja sekunnissa miljoonien sijaan. Hidas PBKDF2 on merkki oikeasta toteutuksesta. Johda avain kerran kirjautumisen yhteydess\u00e4 ja pid\u00e4 se v\u00e4limuistissa istunnon ajan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>AES-GCM vai ChaCha20-Poly1305?<\/strong><br>AES-GCM on parempi palvelimilla, joissa on AES-laitteistokiihdytys (kaikki nykyiset x86_64-prosessorit). ChaCha20-Poly1305 on parempi ilman AES-NI:t\u00e4. Turvallisuustasoltaan molemmat ovat vastaavia. ChaCha20-Poly1305:n WebCrypto-tuki vaatii Node.js 24.7.0+.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Miten WebCrypto API eroaa node:crypto:sta suorituskyvyn suhteen?<\/strong><br>Yleinen havainto on, ett\u00e4 WebCrypto on hieman hitaampi yksinkertaisissa symmetrisiss\u00e4 operaatioissa CryptoKey-abstraktion takia. Ep\u00e4symmetrisiss\u00e4 operaatioissa (RSA, ECDSA) ero on marginaalinen. Suurten volyymien skenaariot suosivat <code>node:crypto<\/code>:n stream-rajapintaa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tarvitseeko lis\u00e4t\u00e4 npm-paketteja WebCryptoa varten?<\/strong><br>Ei. WebCrypto on sis\u00e4\u00e4nrakennettu Node.js 15+:ssa. Poikkeus on Argon2, joka vaatii Node.js 24.8.0+ WebCrypton kautta tai npm-paketin vanhemmissa versioissa. PBKDF2, AES-GCM ja ECDSA eiv\u00e4t vaadi yht\u00e4\u00e4n lis\u00e4pakettia.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Onko Node.js WebCrypto yhteensopiva EU:n NIS2- ja CRA-vaatimusten kanssa?<\/strong><br>WebCrypto k\u00e4ytt\u00e4\u00e4 NIST-sertifioituja algoritmeja (AES-GCM, ECDSA, RSA-OAEP), jotka t\u00e4ytt\u00e4v\u00e4t NIS2-direktiivin ja CRA:n kryptografiasuositukset. Suomen kyberturvallisuuslaki (124\/2025) perustuu NIS2:een. Dokumentoi algoritmivalintasi ja parametrisi SBOM:iin CRA-vaatimustenmukaisuuden osoittamiseksi. Lis\u00e4tiedot <a href=\"https:\/\/nodejs.org\/api\/webcrypto.html\" rel=\"noopener noreferrer\" target=\"_blank\">Node.js WebCrypto-dokumentaatiosta<\/a> ja <a href=\"https:\/\/www.w3.org\/TR\/WebCryptoAPI\/\" rel=\"noopener noreferrer\" target=\"_blank\">W3C WebCrypto-standardista<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mik\u00e4 on paras tapa s\u00e4ilytt\u00e4\u00e4 CryptoKey-avaimet tietokantaan?<\/strong><br>\u00c4l\u00e4 tallenna avaimia suoraan. Johda avaimet tarvittaessa PBKDF2:lla tai HKDF:ll\u00e4 k\u00e4ytt\u00e4j\u00e4n salasanasta, tai k\u00e4yt\u00e4 <code>wrapKey()<\/code>:ta salataksesi avaimet master key:lla. Master key:t kuuluvat laitteistosuojattuun avainvarastoon (HSM, AWS KMS, Google Cloud KMS), ei tietokantaan.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"aiheeseen-liittyvaa\">Aiheeseen liittyv\u00e4\u00e4<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Syvenn\u00e4 Node.js-kryptografiaosaamistasi:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/fi\/blake3-hash-nodejs\/\">BLAKE3-hajautus Node.js:ss\u00e4: 10 vaihetta, 30 min [2026]<\/a> \u2013 3,5x SHA-256:ta nopeampi hajautusalgoritmi, saatavilla WebCryton ulkopuolella<\/li><li><a href=\"\/fi\/ed25519-allekirjoitus-nodejs\/\">Ed25519 Node.js:ss\u00e4: 10 vaihetta, 30 min [2026]<\/a> \u2013 Nopeat allekirjoitukset <code>node:crypto<\/code>:lla, koska Ed25519 ei kuulu WebCrypto-standardiin<\/li><li><a href=\"\/fi\/hmac-node-js\/\">HMAC Node.js:ss\u00e4: 10 vaihetta, 30 min [2026]<\/a> \u2013 HMAC-SHA256-viestien autentikointi, saatavilla my\u00f6s WebCryton kautta<\/li><li><a href=\"\/fi\/jwt-todennus-nodejs\/\">JWT-todennus Node.js:ss\u00e4: 12 vaihetta, 40 min [2026]<\/a> \u2013 JSON Web Token -todennus ECDSA- ja RSA-allekirjoitusten p\u00e4\u00e4ll\u00e4<\/li><li><a href=\"\/fi\/turvalliset-sessiot-nodejs\/\">Turvalliset sessiot Node.js:ss\u00e4: 10 vaihetta [2026]<\/a> \u2013 Istunnonhallinta turvallisesti, mukaan lukien avainjohdannaiset<\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Node.js WebCrypto API muutti turvallisen kryptografian kirjoittamisen palvelimella vuodesta 2020 alkaen. globalThis.crypto.subtle-rajapinta noudattaa W3C-standardia, joten sama koodi toimii sek\u00e4 selaimessa ett\u00e4 Node.js:ss\u00e4. Node.js 24.7.0 lis\u00e4si ChaCha20-Poly1305-salauksen ja SubtleCrypto.supports()-metodin, ja Node.js\u2026<\/p>\n","protected":false},"author":9,"featured_media":108,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,2],"tags":[],"class_list":["post-107","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\/107","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\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/comments?post=107"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/107\/revisions"}],"predecessor-version":[{"id":109,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/107\/revisions\/109"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/media\/108"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/media?parent=107"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/categories?post=107"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/tags?post=107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}