{"id":152,"date":"2026-06-21T16:49:43","date_gmt":"2026-06-21T16:49:43","guid":{"rendered":"https:\/\/shattered.io\/fi\/2026\/06\/21\/post-quantum-salaus-nodejs\/"},"modified":"2026-06-28T23:48:36","modified_gmt":"2026-06-28T23:48:36","slug":"post-quantum-salaus-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/fi\/post-quantum-salaus-nodejs\/","title":{"rendered":"Post-Quantum Salaus Node.js:ss\u00e4: ML-KEM ja ML-DSA, 12 vaihetta [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Post-quantum salaus ei ole en\u00e4\u00e4 tulevaisuuden projekti. NIST viimeisteli elokuussa 2024 kolme kvanttiresistentti\u00e4 standardia (FIPS 203, 204 ja 205), ja EU:n Cyber Resilience Act astui voimaan 1. kes\u00e4kuuta 2026. T\u00e4ss\u00e4 oppaassa rakennat t\u00e4ysin toimivan post-quantum salausj\u00e4rjestelm\u00e4n Node.js:ll\u00e4: ML-KEM-768-avainten kapselointi, ML-DSA-65-allekirjoitukset, hybridisalaus klassisen X25519:n kanssa ja AES-256-GCM-suojattu viestint\u00e4. Kaksitoista vaihetta, noin 45 minuuttia, yksi npm-paketti.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Oppaan koodi perustuu <code>@noble\/post-quantum<\/code>-kirjastoon, joka on ainoa aktiivisesti yll\u00e4pidetty ja tarkastettu JavaScript-toteutus NIST:n standardoimille ML-KEM- ja ML-DSA-algoritmeille. Node.js v22 tai uudempi riitt\u00e4\u00e4, eik\u00e4 natiivia PQC-tukea tarvita. P\u00e4ivitetty 21. kes\u00e4kuuta 2026.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"mita-post-quantum-salaus-tarkoittaa-ja-miksi-se-on-kiireellinen-2026\">Mit\u00e4 post-quantum salaus tarkoittaa ja miksi se on kiireellinen 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nykyiset asymmetriset algoritmit, kuten RSA ja ECDH, perustuvat matemaattisiin ongelmiin (kokonaislukujen tekij\u00f6ihinjako ja diskreetti logaritmi), jotka klassinen tietokone ratkaisee hitaasti mutta riitt\u00e4v\u00e4n tehokas kvanttitietokone Shorin algoritmilla murtaa polynomisessa ajassa. ML-KEM (Module-Lattice-Based Key Encapsulation Mechanism) ja ML-DSA (Module-Lattice-Based Digital Signature Algorithm) rakentuvat sen sijaan hilaperusteisille ongelmille, joihin ei tunneta kvanttitietokoneen hy\u00f6kk\u00e4yst\u00e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00e4yt\u00e4nn\u00f6n uhka on ns. &#8220;korjaa nyt, pura my\u00f6hemmin&#8221; (harvest now, decrypt later, HNDL): hy\u00f6kk\u00e4\u00e4j\u00e4t tallentavat t\u00e4n\u00e4\u00e4n salatun liikenteen ja purkavat sen, kun riitt\u00e4v\u00e4n tehokas kvanttitietokone on saatavilla. Arkaluonteisten tietojen, kuten terveysdatan, rahoitustransaktioiden ja hallintaviestinn\u00e4n, salaussuoja on uusittava ennen kvanttiaikakautta. NIST:n suosituksen mukaan RSA:n ja ECDH:n k\u00e4yt\u00f6st\u00e4 tulisi luopua vuoteen 2030 menness\u00e4. EU:n CRA vaatii lis\u00e4ksi dokumentoitua kryptografista inventaariota kaikilta digitaalisia tuotteita valmistaavilta yrityksilt\u00e4 viimeist\u00e4\u00e4n syyskuuhun 2026 menness\u00e4.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Standardi<\/th><th>Algoritmi<\/th><th>L\u00e4ht\u00f6kohta<\/th><th>K\u00e4ytt\u00f6tarkoitus<\/th><th>Hyv\u00e4ksytty<\/th><\/tr><\/thead><tbody><tr><td>FIPS 203<\/td><td>ML-KEM<\/td><td>CRYSTALS-Kyber<\/td><td>Avainten kapselointi (KEM)<\/td><td>Elokuu 2024<\/td><\/tr><tr><td>FIPS 204<\/td><td>ML-DSA<\/td><td>CRYSTALS-Dilithium<\/td><td>Digitaaliset allekirjoitukset<\/td><td>Elokuu 2024<\/td><\/tr><tr><td>FIPS 205<\/td><td>SLH-DSA<\/td><td>SPHINCS+<\/td><td>Allekirjoitukset (tilaton)<\/td><td>Elokuu 2024<\/td><\/tr><tr><td>FIPS 206<\/td><td>FN-DSA<\/td><td>FALCON<\/td><td>Allekirjoitukset (pieni koko)<\/td><td>Odottaa 2026<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaatimukset-ohjelmistot-ja-versiot\">Vaatimukset: ohjelmistot ja versiot<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ennen aloittamista tarkista, ett\u00e4 seuraavat ohjelmistot ovat asennettuina:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Ohjelmisto<\/th><th>Vaadittu versio<\/th><th>Suositeltu versio<\/th><th>Huomio<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>18.0+<\/td><td>22 LTS tai 24<\/td><td>ESM-tuki vaaditaan<\/td><\/tr><tr><td>npm<\/td><td>8.0+<\/td><td>10+<\/td><td>package.json type: module<\/td><\/tr><tr><td>@noble\/post-quantum<\/td><td>0.2+<\/td><td>Uusin versio<\/td><td>Ainoa tarkastettu JS-toteutus<\/td><\/tr><tr><td>K\u00e4ytt\u00f6j\u00e4rjestelm\u00e4<\/td><td>Linux \/ macOS \/ Windows<\/td><td>Ubuntu 22.04 LTS<\/td><td>Ei k\u00e4ytt\u00f6j\u00e4rjestelm\u00e4riippuvuuksia<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Tarkista Node.js-versio:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node --version\n# Vaaditaan: v18.0.0 tai uudempi\n\nnpm --version\n# Vaaditaan: 8.0.0 tai uudempi<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-1-projektin-alustus-ja-riippuvuuksien-asennus\">Vaihe 1: Projektin alustus ja riippuvuuksien asennus<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Luo uusi Node.js-projekti. Projekti k\u00e4ytt\u00e4\u00e4 ES-moduuleja, koska <code>@noble\/post-quantum<\/code> on kirjoitettu modernilla ESM-syntaksilla. Voit k\u00e4ytt\u00e4\u00e4 CommonJS:\u00e4\u00e4 (require), mutta suositeltava tapa on ESM:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir pqc-demo && cd pqc-demo\nnpm init -y\n\n# Aseta ESM-moodi\nnpm pkg set type=module\n\n# Asenna @noble\/post-quantum\nnpm install @noble\/post-quantum\n\n# Tarkista asennus\nnode -e \"import('@noble\/post-quantum\/ml-kem.js').then(m => console.log('ML-KEM OK:', Object.keys(m)))\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Onnistuneen asennuksen j\u00e4lkeen konsoli tulostaa ML-KEM:n API-pintarakenteet: <code>mlkem512<\/code>, <code>mlkem768<\/code> ja <code>mlkem1024<\/code>. N\u00e4ist\u00e4 <strong>ML-KEM-768<\/strong> on NIST:n suosittelema yleisk\u00e4ytt\u00f6inen turvatasoksi, joka vastaa noin 192-bittist\u00e4 klassista turvallisuutta. ML-KEM-512 sopii resurssirajoitteisiin ymp\u00e4rist\u00f6ihin, ML-KEM-1024 korkean turvallisuuden tarpeisiin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tiedostorakenne, jonka rakennat t\u00e4m\u00e4n oppaan aikana:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pqc-demo\/\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 keygen.js          # Avainparin luonti (vaihe 2)\n\u251c\u2500\u2500 kem-demo.js        # KEM-kapselointi ja purku (vaiheet 3-4)\n\u251c\u2500\u2500 dsa-demo.js        # ML-DSA allekirjoitukset (vaihe 5)\n\u251c\u2500\u2500 hybrid.js          # Hybridisalaus XWing (vaihe 6)\n\u251c\u2500\u2500 encrypt.js         # AES-256-GCM-salaus (vaihe 7)\n\u251c\u2500\u2500 keystore.js        # Avainten tallennus (vaihe 8)\n\u251c\u2500\u2500 server.js          # HTTP-palvelin (vaihe 9)\n\u2514\u2500\u2500 rotate.js          # Avainten kierto (vaihe 10)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-2-ml-kem-768-avainparin-luonti\">Vaihe 2: ML-KEM-768-avainparin luonti<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ML-KEM ei ole salausalgoritmi vaan <strong>avaintenkapselointimekanismi<\/strong> (KEM). Se mahdollistaa kahden osapuolen sopia yhteinen jaettu salaisuus (shared secret) julkisen kanavan kautta ilman, ett\u00e4 itse salaisuus v\u00e4litet\u00e4\u00e4n suoraan. Prosessi eroaa RSA:sta: vastaanottaja luo avainparin, l\u00e4hett\u00e4j\u00e4 kapseloi jaetun salaisuuden julkisella avaimella, vastaanottaja purkaa sen yksityisell\u00e4 avaimella.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Luo tiedosto <code>keygen.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ keygen.js\nimport { mlkem768 } from '@noble\/post-quantum\/ml-kem.js';\nimport { writeFileSync } from 'node:fs';\n\n\/\/ Generoi avainpari\nconst { publicKey, secretKey } = mlkem768.keygen();\n\nconsole.log('ML-KEM-768 avainpari luotu:');\nconsole.log('  Julkinen avain:  ', publicKey.length, 'tavua');\nconsole.log('  Yksityinen avain:', secretKey.length, 'tavua');\n\n\/\/ Tallenna avaimet (kehitysymp\u00e4rist\u00f6; tuotannossa k\u00e4yt\u00e4 keystore.js:\u00e4\u00e4)\nwriteFileSync('mlkem768-public.key',  Buffer.from(publicKey));\nwriteFileSync('mlkem768-secret.key',  Buffer.from(secretKey));\n\nconsole.log('Avaimet tallennettu: mlkem768-public.key, mlkem768-secret.key');<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>node keygen.js\n# Tuloste:\n# ML-KEM-768 avainpari luotu:\n#   Julkinen avain:   1184 tavua\n#   Yksityinen avain: 2400 tavua\n# Avaimet tallennettu: mlkem768-public.key, mlkem768-secret.key<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Huomaa avainten koot verrattuna klassisiin algoritmeihin. ML-KEM-768:n julkinen avain (1 184 tavua) on suurempi kuin RSA-2048:n (256-tavuinen modulus), mutta pidempi turvalisuusmarginaali kattaa kasvun:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Algoritmi<\/th><th>Julkinen avain<\/th><th>Yksityinen avain<\/th><th>Salakirjoitusteksti<\/th><th>Kvanttiturva<\/th><\/tr><\/thead><tbody><tr><td>RSA-2048<\/td><td>256 tavua<\/td><td>1 192 tavua<\/td><td>256 tavua<\/td><td>Ei<\/td><\/tr><tr><td>X25519<\/td><td>32 tavua<\/td><td>32 tavua<\/td><td>32 tavua<\/td><td>Ei<\/td><\/tr><tr><td>ML-KEM-512<\/td><td>800 tavua<\/td><td>1 632 tavua<\/td><td>768 tavua<\/td><td>Kyll\u00e4 (128-bit)<\/td><\/tr><tr><td>ML-KEM-768<\/td><td>1 184 tavua<\/td><td>2 400 tavua<\/td><td>1 088 tavua<\/td><td>Kyll\u00e4 (192-bit)<\/td><\/tr><tr><td>ML-KEM-1024<\/td><td>1 568 tavua<\/td><td>3 168 tavua<\/td><td>1 568 tavua<\/td><td>Kyll\u00e4 (256-bit)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-3-avaimen-kapselointi-encapsulate\">Vaihe 3: Avaimen kapselointi (encapsulate)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L\u00e4hett\u00e4j\u00e4 kapseloi jaetun salaisuuden vastaanottajan julkisella avaimella. Kapselointi tuottaa kaksi arvoa: <code>ciphertext<\/code> (salakirjoitusteksti, joka l\u00e4hetet\u00e4\u00e4n vastaanottajalle) ja <code>sharedSecret<\/code> (32-tavuinen jaettu salaisuus, jota k\u00e4ytet\u00e4\u00e4n symmetriseen salaukseen). L\u00e4hett\u00e4j\u00e4 ei koskaan n\u00e4e vastaanottajan yksityist\u00e4 avainta.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ kem-demo.js - L\u00e4hett\u00e4j\u00e4n puoli\nimport { mlkem768 } from '@noble\/post-quantum\/ml-kem.js';\nimport { readFileSync } from 'node:fs';\n\n\/\/ Lataa vastaanottajan julkinen avain\nconst publicKey = new Uint8Array(readFileSync('mlkem768-public.key'));\n\n\/\/ Kapseloi jaettu salaisuus\nconst { ciphertext, sharedSecret: senderSecret } = mlkem768.encapsulate(publicKey);\n\nconsole.log('Kapselointi onnistui:');\nconsole.log('  Salakirjoitusteksti:', ciphertext.length, 'tavua');\nconsole.log('  Jaettu salaisuus:   ', senderSecret.length, 'tavua');\nconsole.log('  Salaisuus (hex):    ', Buffer.from(senderSecret).toString('hex').slice(0, 32) + '...');\n\n\/\/ L\u00e4hett\u00e4j\u00e4 l\u00e4hett\u00e4\u00e4 ciphertextin vastaanottajalle turvallisen kanavan kautta\nexport { ciphertext, senderSecret };<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-4-avaimen-purku-decapsulate\">Vaihe 4: Avaimen purku (decapsulate)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Vastaanottaja purkaa salakirjoitustekstin yksityisell\u00e4 avaimellaan ja saa saman jaetun salaisuuden kuin l\u00e4hett\u00e4j\u00e4. Jos purkaminen onnistuu oikein, molemmat osapuolet jakavat identtisen 32-tavuisen arvon. T\u00e4t\u00e4 arvoa k\u00e4ytet\u00e4\u00e4n symmetrisen salausavaimen johtamiseen HKDF:ll\u00e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ kem-demo.js (jatkuu) - Vastaanottajan puoli\nimport { mlkem768 } from '@noble\/post-quantum\/ml-kem.js';\nimport { readFileSync } from 'node:fs';\n\nasync function demonstrateKEM() {\n  \/\/ Luo avainpari (vastaanottaja)\n  const { publicKey, secretKey } = mlkem768.keygen();\n\n  \/\/ Kapseloi (l\u00e4hett\u00e4j\u00e4)\n  const { ciphertext, sharedSecret: senderSecret } = mlkem768.encapsulate(publicKey);\n\n  \/\/ Pura (vastaanottaja)\n  const receiverSecret = mlkem768.decapsulate(ciphertext, secretKey);\n\n  \/\/ Vertaa jaettuja salaisuuksia\n  const match = Buffer.from(senderSecret).equals(Buffer.from(receiverSecret));\n  console.log('Jaetut salaisuudet t\u00e4sm\u00e4\u00e4v\u00e4t:', match);\n  console.log('L\u00e4hett\u00e4j\u00e4n salaisuus: ', Buffer.from(senderSecret).toString('hex'));\n  console.log('Vastaanottajan salaisuus:', Buffer.from(receiverSecret).toString('hex'));\n}\n\ndemonstrateKEM();<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>node kem-demo.js\n# Tuloste:\n# Jaetut salaisuudet t\u00e4sm\u00e4\u00e4v\u00e4t: true\n# L\u00e4hett\u00e4j\u00e4n salaisuus:  a3f82c1b...\n# Vastaanottajan salaisuus: a3f82c1b...<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-5-ml-dsa-65-digitaaliset-allekirjoitukset\">Vaihe 5: ML-DSA-65-digitaaliset allekirjoitukset<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ML-KEM hoitaa avaintenvaihdon, mutta se ei todenna viestinn\u00e4n osapuolia. Todennus tarvitsee allekirjoitusalgoritmin. <strong>ML-DSA-65<\/strong> (FIPS 204) vastaa noin 128 bitin klassista turvallisuustasoa ja on suositeltu yleisk\u00e4ytt\u00f6inen valinta. Allekirjoitus on 3 293 tavua, mik\u00e4 on huomattavasti enemm\u00e4n kuin Ed25519:n 64 tavua, mutta algoritmi on kvanttiresistentti.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ dsa-demo.js\nimport { mldsa65 } from '@noble\/post-quantum\/ml-dsa.js';\n\n\/\/ Luo allekirjoitusavainpari\nconst { publicKey, secretKey } = mldsa65.keygen();\n\nconsole.log('ML-DSA-65 avainpari:');\nconsole.log('  Julkinen avain: ', publicKey.length, 'tavua');\nconsole.log('  Yksityinen avain:', secretKey.length, 'tavua');\n\n\/\/ Allekirjoita viesti\nconst viesti = new TextEncoder().encode('T\u00e4m\u00e4 on kvanttiresistentti allekirjoitettu viesti.');\n\nconst allekirjoitus = mldsa65.sign(viesti, secretKey);\nconsole.log('  Allekirjoitus:  ', allekirjoitus.length, 'tavua');\n\n\/\/ Varmenna allekirjoitus\nconst onkoValidi = mldsa65.verify(allekirjoitus, viesti, publicKey);\nconsole.log('Allekirjoitus validi:', onkoValidi);\n\n\/\/ Testaa muuttuneella viestill\u00e4 (pit\u00e4\u00e4 ep\u00e4onnistua)\nconst muutettuViesti = new TextEncoder().encode('T\u00e4m\u00e4 viesti on muutettu.');\nconst onkoMuutettuValidi = mldsa65.verify(allekirjoitus, muutettuViesti, publicKey);\nconsole.log('Muutetun viestin allekirjoitus validi:', onkoMuutettuValidi);<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>node dsa-demo.js\n# Tuloste:\n# ML-DSA-65 avainpari:\n#   Julkinen avain:  1952 tavua\n#   Yksityinen avain: 4032 tavua\n#   Allekirjoitus:   3293 tavua\n# Allekirjoitus validi: true\n# Muutetun viestin allekirjoitus validi: false<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Algoritmi<\/th><th>Julkinen avain<\/th><th>Yksityinen avain<\/th><th>Allekirjoitus<\/th><th>Kvanttiturva<\/th><\/tr><\/thead><tbody><tr><td>Ed25519<\/td><td>32 tavua<\/td><td>64 tavua<\/td><td>64 tavua<\/td><td>Ei<\/td><\/tr><tr><td>ECDSA P-256<\/td><td>64 tavua<\/td><td>32 tavua<\/td><td>71 tavua<\/td><td>Ei<\/td><\/tr><tr><td>ML-DSA-44<\/td><td>1 312 tavua<\/td><td>2 560 tavua<\/td><td>2 420 tavua<\/td><td>Kyll\u00e4<\/td><\/tr><tr><td>ML-DSA-65<\/td><td>1 952 tavua<\/td><td>4 032 tavua<\/td><td>3 293 tavua<\/td><td>Kyll\u00e4<\/td><\/tr><tr><td>ML-DSA-87<\/td><td>2 592 tavua<\/td><td>4 896 tavua<\/td><td>4 595 tavua<\/td><td>Kyll\u00e4<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-6-hybridisalaus-xwing-menetelmalla\">Vaihe 6: Hybridisalaus XWing-menetelm\u00e4ll\u00e4<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Siirtym\u00e4vaiheen paras k\u00e4yt\u00e4nt\u00f6 on <strong>hybridisalaus<\/strong>: yhdist\u00e4 klassinen X25519 ja ML-KEM-768 siten, ett\u00e4 yhteys pysyy turvallisena, vaikka toinen algoritmeista murtuu. <code>@noble\/post-quantum<\/code> sis\u00e4lt\u00e4\u00e4 valmiin <strong>XWing<\/strong>-hybridin (ML-KEM-768 + X25519), joka on IETF-luonnoksena standardoitavana. XWing yhdist\u00e4\u00e4 molempien algoritmien jaetut salaisuudet HKDF:ll\u00e4 yhdeksi avainmateriaaliksi.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hybrid.js\nimport { XWing } from '@noble\/post-quantum\/hybrids.js';\n\n\/\/ Vastaanottaja luo avainparin\nconst { publicKey, secretKey } = XWing.keygen();\n\nconsole.log('XWing (ML-KEM-768 + X25519) avainpari:');\nconsole.log('  Julkinen avain: ', publicKey.length, 'tavua');\nconsole.log('  Yksityinen avain:', secretKey.length, 'tavua');\n\n\/\/ L\u00e4hett\u00e4j\u00e4 kapseloi\nconst { ciphertext, sharedSecret: lahettajanSalaisuus } = XWing.encapsulate(publicKey);\n\nconsole.log('  Salakirjoitusteksti:', ciphertext.length, 'tavua');\nconsole.log('  Jaettu salaisuus:   ', lahettajanSalaisuus.length, 'tavua');\n\n\/\/ Vastaanottaja purkaa\nconst vastaanottajanSalaisuus = XWing.decapsulate(ciphertext, secretKey);\n\nconst tasmaavat = Buffer.from(lahettajanSalaisuus).equals(Buffer.from(vastaanottajanSalaisuus));\nconsole.log('Hybridisalaisuudet t\u00e4sm\u00e4\u00e4v\u00e4t:', tasmaavat);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">XWing on suositeltu valinta k\u00e4yt\u00e4nn\u00f6n toteutuksiin, koska se tarjoaa suojan sek\u00e4 nykyisi\u00e4 hy\u00f6kk\u00e4yksi\u00e4 (X25519:n kautta) ett\u00e4 kvanttiuhkia (ML-KEM-768:n kautta) vastaan. Jos ML-KEM-768:ssa l\u00f6ytyy heikkous, X25519 suojaa edelleen, ja p\u00e4invastoin.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-7-hkdf-johtaminen-ja-aes-256-gcm-salaus\">Vaihe 7: HKDF-johtaminen ja AES-256-GCM-salaus<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ML-KEM:n jaettu salaisuus on 32 tavua satunnaisuutta, mutta suoraan k\u00e4ytettyn\u00e4 salausavaimena se ei ole suositeltu. HKDF (HMAC-based Key Derivation Function) johtaa t\u00e4st\u00e4 materiaalista yhden tai useamman avainkerroksen turvallisesti. T\u00e4m\u00e4 salauskerros toteutetaan Node.js:n sis\u00e4\u00e4nrakennetulla <code>node:crypto<\/code>-moduulilla.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ encrypt.js\nimport { mlkem768 } from '@noble\/post-quantum\/ml-kem.js';\nimport { hkdfSync, createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\n\nfunction johdaAvain(jaettuSalaisuus, konteksti = 'pqc-aes-gcm') {\n  \/\/ HKDF SHA-256: johda 32-tavuinen AES-avain ML-KEM:n jaetusta salaisuudesta\n  return hkdfSync(\n    'sha256',\n    Buffer.from(jaettuSalaisuus),\n    Buffer.alloc(32),                    \/\/ salt (tuotannossa k\u00e4yt\u00e4 satunnaista)\n    Buffer.from(konteksti),              \/\/ info\n    32                                   \/\/ avaimen pituus tavuina\n  );\n}\n\nfunction salaa(plaintext, avain) {\n  const iv = randomBytes(12);            \/\/ 96-bit nonce AES-GCM:lle\n  const cipher = createCipheriv('aes-256-gcm', avain, iv);\n  const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);\n  const tag = cipher.getAuthTag();      \/\/ 128-bit autentikointitunniste\n  return { encrypted, iv, tag };\n}\n\nfunction pura(encrypted, iv, tag, avain) {\n  const decipher = createDecipheriv('aes-256-gcm', avain, iv);\n  decipher.setAuthTag(tag);\n  return Buffer.concat([decipher.update(encrypted), decipher.final()]);\n}\n\n\/\/ Simuloi koko ML-KEM + AES-GCM -salausprosessi\nconst { publicKey, secretKey } = mlkem768.keygen();\nconst { ciphertext, sharedSecret } = mlkem768.encapsulate(publicKey);\nconst receiverSecret = mlkem768.decapsulate(ciphertext, secretKey);\n\n\/\/ Johda AES-avain molemmilla puolilla\nconst lahettajanAvain = johdaAvain(sharedSecret);\nconst vastaanottajanAvain = johdaAvain(receiverSecret);\n\n\/\/ Salaa viesti\nconst alkuperainenTeksti = Buffer.from('Salainen viesti: tilisiirto 100 000 \u20ac');\nconst { encrypted, iv, tag } = salaa(alkuperainenTeksti, lahettajanAvain);\n\nconsole.log('Salattu:', encrypted.toString('hex'));\nconsole.log('IV:     ', iv.toString('hex'));\nconsole.log('Tag:    ', tag.toString('hex'));\n\n\/\/ Pura viesti\nconst purettuTeksti = pura(encrypted, iv, tag, vastaanottajanAvain);\nconsole.log('Purettu:', purettuTeksti.toString());\nconsole.log('T\u00e4sm\u00e4\u00e4 alkuper\u00e4iseen:', alkuperainenTeksti.equals(purettuTeksti));<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>node encrypt.js\n# Tuloste:\n# Salattu: 7a3f12bc...\n# IV:      d4e8a1c3...\n# Tag:     9b2f7e44...\n# Purettu: Salainen viesti: tilisiirto 100 000 \u20ac\n# T\u00e4sm\u00e4\u00e4 alkuper\u00e4iseen: true<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-8-avainten-pysyva-tallennus-ja-suojaus\">Vaihe 8: Avainten pysyv\u00e4 tallennus ja suojaus<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Yksityiset avaimet ovat PQC-j\u00e4rjestelm\u00e4n herkin osa. ML-KEM-768:n yksityinen avain on 2 400 tavua ja ML-DSA-65:n yksityinen avain 4 032 tavua. Molemmat on salattava levossa. T\u00e4ss\u00e4 vaiheessa salaat yksityisen avaimen AES-256-GCM:ll\u00e4 salasanasta johdetulla avaimella k\u00e4ytt\u00e4en Argon2id:t\u00e4 PBKDF2:n sijaan.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ keystore.js\nimport { mlkem768 } from '@noble\/post-quantum\/ml-kem.js';\nimport { \n  scryptSync, \n  createCipheriv, \n  createDecipheriv, \n  randomBytes,\n  timingSafeEqual\n} from 'node:crypto';\nimport { writeFileSync, readFileSync } from 'node:fs';\n\nconst SCRYPT_PARAMS = { N: 131072, r: 8, p: 1 };  \/\/ OWASP 2026 suositus\n\nfunction salaaAvain(avainData, salasana) {\n  const salt = randomBytes(32);\n  const avain = scryptSync(salasana, salt, 32, SCRYPT_PARAMS);\n  const iv = randomBytes(12);\n  const cipher = createCipheriv('aes-256-gcm', avain, iv);\n  const salattu = Buffer.concat([cipher.update(avainData), cipher.final()]);\n  const tag = cipher.getAuthTag();\n  \/\/ Paketa: [salt(32)][iv(12)][tag(16)][salattuData]\n  return Buffer.concat([salt, iv, tag, salattu]);\n}\n\nfunction puraAvain(pakattuData, salasana) {\n  const salt = pakattuData.slice(0, 32);\n  const iv = pakattuData.slice(32, 44);\n  const tag = pakattuData.slice(44, 60);\n  const salattuData = pakattuData.slice(60);\n  const avain = scryptSync(salasana, salt, 32, SCRYPT_PARAMS);\n  const decipher = createDecipheriv('aes-256-gcm', avain, iv);\n  decipher.setAuthTag(tag);\n  return Buffer.concat([decipher.update(salattuData), decipher.final()]);\n}\n\n\/\/ Esimerkki: tallenna ja lataa avainpari\nconst { publicKey, secretKey } = mlkem768.keygen();\nconst salasana = Buffer.from('vahva_salasana_vain_esimerkki');\n\n\/\/ Tallenna salattu yksityinen avain\nconst salattuYksityinenAvain = salaaAvain(Buffer.from(secretKey), salasana);\nwriteFileSync('mlkem768-secret.enc', salattuYksityinenAvain);\nwriteFileSync('mlkem768-public.key', Buffer.from(publicKey));\n\nconsole.log('Avainpari tallennettu (yksityinen avain salattu)');\n\n\/\/ Lataa ja pura yksityinen avain\nconst ladattuSalattu = readFileSync('mlkem768-secret.enc');\nconst purettuYksityinenAvain = new Uint8Array(puraAvain(ladattuSalattu, salasana));\nconsole.log('Yksityinen avain purettu onnistuneesti');\nconsole.log('Avaimet t\u00e4sm\u00e4\u00e4v\u00e4t:', timingSafeEqual(\n  Buffer.from(secretKey), \n  Buffer.from(purettuYksityinenAvain)\n));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tuotantoymp\u00e4rist\u00f6iss\u00e4 yksityisi\u00e4 avaimia ei tulisi tallentaa tiedostoj\u00e4rjestelm\u00e4\u00e4n lainkaan. K\u00e4yt\u00e4 sen sijaan Hardware Security Module -laitetta (HSM), pilvipalveluntarjoajan avainhallintapalvelua (AWS KMS, Azure Key Vault, Google Cloud KMS) tai v\u00e4hint\u00e4\u00e4n salattua ymp\u00e4rist\u00f6muuttujaa Kubernetes Secrets -kohteessa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-9-pqc-suojattu-http-viestinta-node-js-palvelimessa\">Vaihe 9: PQC-suojattu HTTP-viestint\u00e4 Node.js-palvelimessa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4ss\u00e4 vaiheessa yhdist\u00e4t ML-KEM-kapseloinnin, ML-DSA-allekirjoituksen ja AES-256-GCM-salauksen toimivaksi HTTP-palvelimeksi. Palvelin vastaanottaa salatun viestin, purkaa sen ja palauttaa kvanttiresistentisti allekirjoitetun vastauksen. T\u00e4m\u00e4 kuvaa realistista k\u00e4ytt\u00f6tapausta API-viestinn\u00e4ss\u00e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js\nimport { createServer } from 'node:http';\nimport { mlkem768 } from '@noble\/post-quantum\/ml-kem.js';\nimport { mldsa65 } from '@noble\/post-quantum\/ml-dsa.js';\nimport { hkdfSync, createDecipheriv } from 'node:crypto';\n\n\/\/ Palvelimen avainparit (tuotannossa: lataa suojatuista avaintiedostoista)\nconst kemKeys = mlkem768.keygen();\nconst dsaKeys = mldsa65.keygen();\n\nconst palvelin = createServer((req, res) => {\n  if (req.method === 'GET' && req.url === '\/public-key') {\n    \/\/ Palauta julkinen avain asiakkaalle\n    res.writeHead(200, { 'Content-Type': 'application\/json' });\n    res.end(JSON.stringify({\n      kemPublicKey: Buffer.from(kemKeys.publicKey).toString('base64'),\n      dsaPublicKey: Buffer.from(dsaKeys.publicKey).toString('base64')\n    }));\n    return;\n  }\n\n  if (req.method === 'POST' && req.url === '\/decrypt') {\n    let body = '';\n    req.on('data', chunk => body += chunk);\n    req.on('end', () => {\n      try {\n        const { ciphertext, encryptedPayload, iv, tag } = JSON.parse(body);\n\n        \/\/ 1. Pura jaettu salaisuus ML-KEM:ll\u00e4\n        const sharedSecret = mlkem768.decapsulate(\n          new Uint8Array(Buffer.from(ciphertext, 'base64')),\n          kemKeys.secretKey\n        );\n\n        \/\/ 2. Johda AES-avain HKDF:ll\u00e4\n        const aesKey = hkdfSync('sha256', Buffer.from(sharedSecret), Buffer.alloc(32), Buffer.from('api-v1'), 32);\n\n        \/\/ 3. Pura viesti AES-256-GCM:ll\u00e4\n        const decipher = require('node:crypto').createDecipheriv('aes-256-gcm', aesKey,\n          Buffer.from(iv, 'base64'));\n        decipher.setAuthTag(Buffer.from(tag, 'base64'));\n        const purettu = Buffer.concat([\n          decipher.update(Buffer.from(encryptedPayload, 'base64')),\n          decipher.final()\n        ]);\n\n        \/\/ 4. Allekirjoita vastaus ML-DSA:lla\n        const vastausData = Buffer.from(JSON.stringify({ tulos: 'ok', viesti: purettu.toString() }));\n        const allekirjoitus = mldsa65.sign(vastausData, dsaKeys.secretKey);\n\n        res.writeHead(200, { 'Content-Type': 'application\/json' });\n        res.end(JSON.stringify({\n          vastaus: vastausData.toString('base64'),\n          allekirjoitus: Buffer.from(allekirjoitus).toString('base64')\n        }));\n      } catch (virhe) {\n        res.writeHead(400, { 'Content-Type': 'application\/json' });\n        res.end(JSON.stringify({ virhe: 'Salauksen purku ep\u00e4onnistui' }));\n      }\n    });\n  }\n});\n\npalvelin.listen(3000, () => console.log('PQC-palvelin kuuntelee portissa 3000'));<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-10-avainten-kierto-key-rotation\">Vaihe 10: Avainten kierto (key rotation)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Avainten kierto on kriittinen osa PQC-j\u00e4rjestelm\u00e4\u00e4. NIST SP 800-57 suosittelee ML-KEM-avainten vaihtamista v\u00e4hint\u00e4\u00e4n vuoden v\u00e4lein tai tietoturvatapahtuman j\u00e4lkeen. K\u00e4yt\u00e4 versioitua avainrakennetta, jossa vanha avain pysyy toiminnassa purkulle kunnes kaikki asiakkaat ovat siirtyneet uuteen avaimeen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ rotate.js\nimport { mlkem768 } from '@noble\/post-quantum\/ml-kem.js';\nimport { writeFileSync, existsSync, renameSync } from 'node:fs';\n\nfunction kierraAvaimet() {\n  const aikaleima = Date.now();\n\n  \/\/ Arkistoi vanhat avaimet\n  if (existsSync('mlkem768-public.key')) {\n    renameSync('mlkem768-public.key', `mlkem768-public-${aikaleima}.key.bak`);\n    renameSync('mlkem768-secret.enc', `mlkem768-secret-${aikaleima}.enc.bak`);\n    console.log(`Vanhat avaimet arkistoitu aikaleimalla ${aikaleima}`);\n  }\n\n  \/\/ Luo uudet avaimet\n  const { publicKey, secretKey } = mlkem768.keygen();\n  writeFileSync('mlkem768-public.key', Buffer.from(publicKey));\n\n  \/\/ Tuotannossa: salaa secretKey ennen tallennusta (ks. vaihe 8)\n  console.log('Uudet ML-KEM-768-avaimet luotu:', aikaleima);\n  console.log('Muista p\u00e4ivitt\u00e4\u00e4 kaikkien asiakkaiden julkinen avain ennen vanhojen poistamista.');\n\n  return { publicKey, secretKey };\n}\n\nconst uudetAvaimet = kierraAvaimet();<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-11-turvallisuustestaus-ja-validointi\">Vaihe 11: Turvallisuustestaus ja validointi<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Testaa jokainen komponentti erikseen ennen integrointia tuotantoon. Seuraava testikoodi k\u00e4ytt\u00e4\u00e4 Node.js:n sis\u00e4\u00e4nrakennettua <code>node:assert<\/code>-moduulia:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ test-pqc.js\nimport { mlkem768 } from '@noble\/post-quantum\/ml-kem.js';\nimport { mldsa65 } from '@noble\/post-quantum\/ml-dsa.js';\nimport { XWing } from '@noble\/post-quantum\/hybrids.js';\nimport { strictEqual, ok, notStrictEqual } from 'node:assert';\n\nconsole.log('=== PQC-testisarja ===');\n\n\/\/ Testi 1: ML-KEM avainparin luonti\nconst { publicKey, secretKey } = mlkem768.keygen();\nstrictEqual(publicKey.length, 1184, 'ML-KEM-768 julkinen avain 1184 tavua');\nstrictEqual(secretKey.length, 2400, 'ML-KEM-768 yksityinen avain 2400 tavua');\nconsole.log('\u2713 Testi 1: Avainten koot oikeat');\n\n\/\/ Testi 2: KEM round-trip\nconst { ciphertext, sharedSecret: s1 } = mlkem768.encapsulate(publicKey);\nconst s2 = mlkem768.decapsulate(ciphertext, secretKey);\nok(Buffer.from(s1).equals(Buffer.from(s2)), 'Jaetut salaisuudet t\u00e4sm\u00e4\u00e4v\u00e4t');\nconsole.log('\u2713 Testi 2: KEM round-trip onnistui');\n\n\/\/ Testi 3: V\u00e4\u00e4r\u00e4 yksityinen avain ei toimi\nconst { secretKey: vaaraAvain } = mlkem768.keygen();\nconst s3 = mlkem768.decapsulate(ciphertext, vaaraAvain);\nok(!Buffer.from(s1).equals(Buffer.from(s3)), 'V\u00e4\u00e4r\u00e4 yksityinen avain tuottaa eri salaisuuden');\nconsole.log('\u2713 Testi 3: V\u00e4\u00e4r\u00e4 yksityinen avain hyl\u00e4t\u00e4\u00e4n');\n\n\/\/ Testi 4: ML-DSA allekirjoitus\nconst dsaKeys = mldsa65.keygen();\nconst viesti = new TextEncoder().encode('Testattava viesti');\nconst sig = mldsa65.sign(viesti, dsaKeys.secretKey);\nok(mldsa65.verify(sig, viesti, dsaKeys.publicKey), 'Allekirjoitus validi');\nconsole.log('\u2713 Testi 4: ML-DSA allekirjoitus validi');\n\n\/\/ Testi 5: Muutettu viesti hyl\u00e4t\u00e4\u00e4n\nconst muutettu = new TextEncoder().encode('Muutettu viesti');\nok(!mldsa65.verify(sig, muutettu, dsaKeys.publicKey), 'Muutettu viesti hyl\u00e4t\u00e4\u00e4n');\nconsole.log('\u2713 Testi 5: Muutettu viesti hyl\u00e4t\u00e4\u00e4n');\n\n\/\/ Testi 6: XWing hybridi\nconst xwingKeys = XWing.keygen();\nconst { ciphertext: xct, sharedSecret: xs1 } = XWing.encapsulate(xwingKeys.publicKey);\nconst xs2 = XWing.decapsulate(xct, xwingKeys.secretKey);\nok(Buffer.from(xs1).equals(Buffer.from(xs2)), 'XWing jaetut salaisuudet t\u00e4sm\u00e4\u00e4v\u00e4t');\nconsole.log('\u2713 Testi 6: XWing hybridisalaus toimii');\n\nconsole.log('=== Kaikki testit l\u00e4p\u00e4isty ===');<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>node test-pqc.js\n# Tuloste:\n# === PQC-testisarja ===\n# \u2713 Testi 1: Avainten koot oikeat\n# \u2713 Testi 2: KEM round-trip onnistui\n# \u2713 Testi 3: V\u00e4\u00e4r\u00e4 yksityinen avain hyl\u00e4t\u00e4\u00e4n\n# \u2713 Testi 4: ML-DSA allekirjoitus validi\n# \u2713 Testi 5: Muutettu viesti hyl\u00e4t\u00e4\u00e4n\n# \u2713 Testi 6: XWing hybridisalaus toimii\n# === Kaikki testit l\u00e4p\u00e4isty ===<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vaihe-12-tuotantoon-valmistelu-ja-eu-cra-yhteensopivuus\">Vaihe 12: Tuotantoon valmistelu ja EU CRA -yhteensopivuus<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ennen tuotantoon siirtymist\u00e4 tarkista seuraavat kohdat EU CRA:n ja NIST SP 800-208 -suositusten mukaisesti. EU:n Cyber Resilience Actin 11. syyskuuta 2026 voimaan tulevat raportointivelvoitteet koskevat kaikkia EU-markkinoilla toimivia ohjelmistovalmistajia, mukaan lukien suomalaiset Node.js-sovellukset.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Tuotantoon valmistelu -tarkistuslista\n\n# 1. P\u00e4ivit\u00e4 Node.js uusimpaan LTS-versioon\nnode --version  # Vaaditaan: v22 tai v24\n\n# 2. Kiinnit\u00e4 @noble\/post-quantum-versio package.json:ssa\nnpm list @noble\/post-quantum  # Tarkista versio\n\n# 3. Tarkista, ettei yksityisi\u00e4 avaimia ole versiohallinnassa\ngrep -r \"secretKey\\|private.*key\" .gitignore || echo \"VAROITUS: Lis\u00e4\u00e4 avaintiedostot .gitignore-tiedostoon\"\n\n# 4. Tarkista avainten k\u00e4ytt\u00f6oikeudet (Unix)\nchmod 600 mlkem768-secret.enc   # Vain omistaja voi lukea\nchmod 644 mlkem768-public.key   # Julkinen avain on luettavissa\n\n# 5. Aja turvallisuustestit\nnode test-pqc.js\n\n# 6. Tarkista npm-haavoittuvuudet\nnpm audit --audit-level=moderate<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">CRA-vaatimusten mukaan sinun on dokumentoitava kryptografinen inventaariosi: listaa kaikki sovelluksesi k\u00e4ytt\u00e4m\u00e4t kryptografiset algoritmit, avainten elinkaaret ja siirtym\u00e4suunnitelma kohti PQC-standardeja. T\u00e4m\u00e4 dokumentaatio on s\u00e4ilytett\u00e4v\u00e4 v\u00e4hint\u00e4\u00e4n 10 vuotta.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"yleiset-virheet-ja-sudenkuopat\">Yleiset virheet ja sudenkuopat<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PQC-toteutuksissa toistuvia virheit\u00e4 on useita. Seuraavat ovat yleisimpi\u00e4, joihin kehitt\u00e4j\u00e4t t\u00f6rm\u00e4\u00e4v\u00e4t:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sudenkuoppa 1: ML-KEM sekoitetaan asymmetriseen salaukseen.<\/strong> ML-KEM ei salaa dataa suoraan. Se tuottaa jaetun salaisuuden, josta HKDF johtaa symmetrisen avaimen. \u00c4l\u00e4 yrit\u00e4 salata viestej\u00e4 suoraan julkisella avaimella, kuten RSA:ssa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sudenkuoppa 2: ML-KEM ilman todennusta on haavoittuvainen MITM-hy\u00f6kk\u00e4yksille.<\/strong> ML-KEM yksin\u00e4\u00e4n ei todenna osapuolia. Aina kun k\u00e4yt\u00e4t ML-KEM:\u00e4\u00e4, yhdist\u00e4 se ML-DSA-allekirjoitukseen tai PKI-sertifikaattiin. Ilman todennusta hy\u00f6kk\u00e4\u00e4j\u00e4 voi korvata julkisen avaimen omallaan.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sudenkuoppa 3: Noncen uudelleenk\u00e4ytt\u00f6 AES-GCM:ss\u00e4 murtaa salauksen t\u00e4ysin.<\/strong> Jokainen AES-GCM-salausoperaatio vaatii ainutlaatuisen 96-bittisen noncen. \u00c4l\u00e4 koskaan k\u00e4yt\u00e4 samaa nonce-arvoa kahdesti samalla avaimella. K\u00e4yt\u00e4 aina <code>randomBytes(12)<\/code> jokaisen viestin yhteydess\u00e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sudenkuoppa 4: Crystals-kyber-js-paketin k\u00e4ytt\u00f6 version 1.x.<\/strong> Vanha <code>crystals-kyber-js<\/code>-paketin versio 1.x toteuttaa CRYSTALS-Kyber-ehdotuksen, ei NIST FIPS 203 -standardia. K\u00e4yt\u00e4 <code>mlkem<\/code>-pakettia tai <code>@noble\/post-quantum<\/code>-kirjastoa, jotka toteuttavat standardoidun ML-KEM:n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sudenkuoppa 5: Yksityisten avainten tallentaminen tekstimuodossa.<\/strong> ML-KEM:n yksityinen avain on 2 400 tavua satunnaista dataa. Tallentaminen base64-muodossa ymp\u00e4rist\u00f6muuttujaan tai JSON-tiedostoon ilman salausta altistaa avaimen tietomurroille. Salaa aina yksityiset avaimet levossa (vaihe 8).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sudenkuoppa 6: ESM-tuonnin virheellinen syntaksi.<\/strong> <code>@noble\/post-quantum<\/code> on ESM-paketti. CommonJS-projekteissa (require) tarvitaan dynaaminen import tai projekti on muutettava ESM-tilaan. Virheilmoitus <code>ERR_REQUIRE_ESM<\/code> tarkoittaa t\u00e4t\u00e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sudenkuoppa 7: Suorituskykyoletus RSA:n perusteella.<\/strong> ML-KEM on merkitt\u00e4v\u00e4sti nopeampi kuin RSA avaintenvaihdossa mutta tuottaa isompia avaimia ja salakirjoitustekstej\u00e4. Jos sovelluksesi l\u00e4hett\u00e4\u00e4 paljon julkisia avaimia (esim. sertifikaattipalvelin), bandwidthin kasvu on suunniteltava etuk\u00e4teen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sudenkuoppa 8: HKDF:n info-kent\u00e4n ohittaminen.<\/strong> HKDF:n <code>info<\/code>-parametri on t\u00e4rke\u00e4 domain separation -tekij\u00e4. Jos k\u00e4yt\u00e4t samaa jaettua salaisuutta useampaan tarkoitukseen (salaus ja todennus), k\u00e4yt\u00e4 eri info-arvoja, kuten <code>'encryption-key'<\/code> ja <code>'mac-key'<\/code>. Saman avaimen k\u00e4ytt\u00f6 molempiin on turvallisuusriski.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vianmaaritys-8-yleista-ongelmatilannetta\">Vianm\u00e4\u00e4ritys: 8 yleist\u00e4 ongelmatilannetta<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ongelma 1: <code>ERR_REQUIRE_ESM<\/code> virheilmoitus.<\/strong><br>Syy: Projekti on CommonJS-tilassa mutta yritt\u00e4\u00e4 tuoda ESM-moduulin.<br>Ratkaisu: Lis\u00e4\u00e4 <code>\"type\": \"module\"<\/code> package.json:iin tai k\u00e4yt\u00e4 dynaamista importia: <code>const { mlkem768 } = await import('@noble\/post-quantum\/ml-kem.js')<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ongelma 2: <code>Cannot find module '@noble\/post-quantum\/ml-kem.js'<\/code>.<\/strong><br>Syy: Paketti ei ole asennettu tai sen versio on vanhentunut.<br>Ratkaisu: <code>npm install @noble\/post-quantum<\/code> ja tarkista, ett\u00e4 <code>node_modules\/@noble\/post-quantum\/ml-kem.js<\/code> on olemassa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ongelma 3: <code>TypeError: secretKey is not Uint8Array<\/code>.<\/strong><br>Syy: readFileSync palauttaa Buffer-objektin, ei Uint8Array:n.<br>Ratkaisu: Muunna eksplisiittisesti: <code>new Uint8Array(readFileSync('mlkem768-secret.key'))<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ongelma 4: AES-GCM-purku ep\u00e4onnistuu <code>Unsupported state or unable to authenticate data<\/code>.<\/strong><br>Syy: Autentikointitunniste (tag) tai salakirjoitusteksti on muuttunut, tai IV on v\u00e4\u00e4r\u00e4.<br>Ratkaisu: Tarkista, ett\u00e4 IV, tag ja encrypted-data l\u00e4hetet\u00e4\u00e4n ja vastaanotetaan oikeassa j\u00e4rjestyksess\u00e4. Base64-koodaus\/dekoodaus t\u00e4ytyy olla yhten\u00e4ist\u00e4 molemmilla puolilla.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ongelma 5: Jaetut salaisuudet eiv\u00e4t t\u00e4sm\u00e4\u00e4.<\/strong><br>Syy: Kapselointi tehtiin eri avaimella kuin purku, tai yksityinen avain on vioittunut latauksen yhteydess\u00e4.<br>Ratkaisu: Varmista, ett\u00e4 julkinen avain kapseloinnissa ja yksityinen avain purussa kuuluvat samaan pariin. Tarkista avaintiedostojen eheys SHA-256-tiivisteell\u00e4.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ongelma 6: Suorituskyky on odotettua hitaampaa ML-DSA:n kanssa.<\/strong><br>Syy: ML-DSA-87:n allekirjoitusoperaatio on hitaampi kuin ML-DSA-44:n tai ML-DSA-65:n.<br>Ratkaisu: Valitse turvallisuustason mukaan: ML-DSA-65 on hyv\u00e4 tasapaino nopeuden ja turvallisuuden v\u00e4lill\u00e4. Jos tarvitset nopeutta ja pienemp\u00e4\u00e4 allekirjoituskokoa, harkitse FN-DSA:ta (FALCON), kun se standardoidaan FIPS 206:ssa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ongelma 7: <code>mlkem768.keygen is not a function<\/code>.<\/strong><br>Syy: V\u00e4\u00e4r\u00e4 import-polku tai vanhentunut versio paketista.<br>Ratkaisu: K\u00e4yt\u00e4 t\u00e4sm\u00e4llist\u00e4 polkua: <code>import { mlkem768 } from '@noble\/post-quantum\/ml-kem.js'<\/code> (huomaa .js-p\u00e4\u00e4te ja alipakettitiedosto).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ongelma 8: Muistin kulutus kasvaa paljon avainpareja luotaessa.<\/strong><br>Syy: ML-KEM-1024 luo 3 168 tavun yksityisen avaimen per kutsu. Tuhansien avainparien luominen silmukassa kuormittaa muistia.<br>Ratkaisu: Kierr\u00e4t\u00e4 avainparit asianmukaisesti, k\u00e4yt\u00e4 <code>--max-old-space-size<\/code>-lippua tarvittaessa ja luo avainparit vain kerran palvelimen k\u00e4ynnistyksen yhteydess\u00e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"edistyneet-vinkit-ja-tulevaisuus\">Edistyneet vinkit ja tulevaisuus<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>XWing on paras valinta siirtym\u00e4kauden hybridiprotokollaksi.<\/strong> IETF-luonnos draft-connolly-cfrg-xwing-kem m\u00e4\u00e4rittelee XWingin (ML-KEM-768 + X25519) vakiomuodossa. Se on yksinkertaisempi kuin NIST:n hybridistandardit ja t\u00e4ht\u00e4\u00e4 TLS 1.3 -integraatioon. Kun Node.js lis\u00e4\u00e4 natiivin TLS PQC -tuen, XWing on todenn\u00e4k\u00f6inen ensimm\u00e4inen tuettu hybridialgoritmij\u00e4rjestely.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>SLH-DSA (SPHINCS+) allekirjoituksille pitk\u00e4aikaisia tarpeita varten.<\/strong> Jos tarvitset allekirjoituksia, joiden on kestett\u00e4v\u00e4 vuosikymmeni\u00e4 (kuten koodin allekirjoitus tai asiakirjasertifiointi), harkitse SLH-DSA:ta. Se on tilaton hajautusfunktioihin perustuva algoritmi, jonka turvallisuus riippuu vain hajautusfunktion turvallisuudesta, mik\u00e4 tekee siit\u00e4 konservatiivisimman vaihtoehdon. Miinuksena on suuri allekirjoituskoko (7 856 tavua ML-DSA-44:n 2 420 tavuun verrattuna).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Kryptografinen inventaario CRA-vaatimustenmukaisuutta varten.<\/strong> Luo JSON-tiedosto, joka listaa kaikki sovelluksesi kryptografiset komponentit: algoritmit, versiot, avainpituudet, k\u00e4ytt\u00f6tarkoitukset ja uudistamisaikataulut. T\u00e4m\u00e4 helpottaa CRA-tarkastuksia ja auttaa tunnistamaan vanhentuneita algoritmeja automaattisesti.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Suorituskyky k\u00e4yt\u00e4nn\u00f6ss\u00e4.<\/strong> Tutkimus (Frontiers in Physics, 2025) osoitti, ett\u00e4 ML-KEM-1024-pohjainen istunto vastasi X25519:\u00e4\u00e4 suorituskyvyss\u00e4 (0,50\u20130,70 ms kryptografinen viive) ja oli huomattavasti nopeampi kuin RSA-3072 istunnon muodostuksessa. K\u00e4yt\u00e4nn\u00f6ss\u00e4 ML-KEM:n lis\u00e4\u00e4minen Node.js-sovellukseen ei merkitt\u00e4v\u00e4sti hidasta palvelua.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"aiheeseen-liittyvaa-shattered-iossa\">Aiheeseen liittyv\u00e4\u00e4 shattered.io:ssa<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/fi\/webcrypto-api-nodejs\/\">Node.js WebCrypto API: 12 vaihetta, 35 min [2026]<\/a> &#8211; natiivin WebCrypto-rajapinnan k\u00e4ytt\u00f6<\/li>\n<li><a href=\"\/fi\/ed25519-allekirjoitus-nodejs\/\">Ed25519 Node.js:ss\u00e4: 10 vaihetta, 30 min [2026]<\/a> &#8211; klassinen EdDSA-allekirjoitus<\/li>\n<li><a href=\"\/fi\/ecdsa-nodejs\/\">ECDSA Node.js:ss\u00e4: 12 vaihetta, 35 min [2026]<\/a> &#8211; elliptisen k\u00e4yr\u00e4n allekirjoitus<\/li>\n<li><a href=\"\/fi\/blake3-hash-nodejs\/\">BLAKE3-hajautus Node.js:ss\u00e4: 10 vaihetta, 30 min [2026]<\/a> &#8211; nopea modernihajautusalgoritmi<\/li>\n<li><a href=\"\/fi\/hmac-node-js\/\">HMAC Node.js:ss\u00e4: 10 vaihetta, 30 min [2026]<\/a> &#8211; viestitodennus HMAC:lla<\/li>\n<li><a href=\"\/fi\/oauth2-passport-nodejs\/\">OAuth 2.0 Node.js:ss\u00e4 Passport.js:ll\u00e4: 12 vaihetta, 30 min [2026]<\/a> &#8211; turvallinen todennus<\/li>\n<li><a href=\"\/fi\/cryptography-hub\/\">Kryptografia: tiivistefunktiot ja luottamuksen perusta<\/a> &#8211; kryptografian perusteet<\/li>\n<\/ul>\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=\"miksi-node-js-22ssa-ei-ole-valmista-ml-kem-tukea\">Miksi Node.js 22:ssa ei ole valmista ML-KEM-tukea?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js 22:n OpenSSL-versio ei toistaiseksi sis\u00e4ll\u00e4 FIPS 203 -standardin mukaista ML-KEM-toteutusta. OpenSSL 3.5 lis\u00e4si PQC-tuen, mutta Node.js-integraatio etenee hitaasti. Vuonna 2026 ulkoiset kirjastot kuten <code>@noble\/post-quantum<\/code> ovat ainoa luotettava tapa k\u00e4ytt\u00e4\u00e4 standardoitua ML-KEM:\u00e4\u00e4 Node.js:ss\u00e4.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"pitaako-minun-siirtya-pqchen-heti\">Pit\u00e4\u00e4k\u00f6 minun siirty\u00e4 PQC:hen heti?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Aloita kryptografisella inventaariolla: mit\u00e4 algoritmeja sovelluksesi k\u00e4ytt\u00e4\u00e4, kuinka kauan salatun datan on pysytt\u00e4v\u00e4 salattuna. Jos arkistoit arkaluonteisia tietoja yli 10 vuodeksi, PQC-siirtym\u00e4 on aloitettava nyt &#8220;korjaa nyt, pura my\u00f6hemmin&#8221; -uhan vuoksi. EU CRA edellytt\u00e4\u00e4 dokumentoitua siirtym\u00e4suunnitelmaa viimeist\u00e4\u00e4n syyskuussa 2026.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"onko-noble-post-quantum-tarkastettu\">Onko @noble\/post-quantum tarkastettu?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Kyll\u00e4. <code>@noble\/post-quantum<\/code>-kirjasto on k\u00e4ynyt l\u00e4pi riippumattoman tietoturvatarkastuksen, ja se perustuu Paul Millrin laajasti tarkastettuun <code>@noble<\/code>-kryptokirjastoperheeseen. Kirjaston l\u00e4hdekoodi on auditoitu ja julkisesti saatavilla GitHubissa. Se ei sis\u00e4ll\u00e4 natiiviriippuvuuksia, joten koodi on kokonaan luettavissa JavaScript-muodossa.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"mika-on-xwingin-ero-verrattuna-pelkkaan-ml-kem-768aan\">Mik\u00e4 on XWingin ero verrattuna pelkk\u00e4\u00e4n ML-KEM-768:aan?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">XWing yhdist\u00e4\u00e4 ML-KEM-768:n ja X25519:n siten, ett\u00e4 turvallisuus riippuu molempien algoritmien turvallisuudesta. Jos ML-KEM-768:ssa l\u00f6ytyy heikkous, X25519 suojelee edelleen. Jos X25519 murtuu kvanttitietokoneella, ML-KEM-768 suojelee. Pelkk\u00e4 ML-KEM-768 tarjoaa kvanttiturvan mutta menett\u00e4\u00e4 klassisen suojan, jos latticeongelmassa l\u00f6ytyy odottamaton heikkous.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"voiko-tlsaa-kayttaa-pqcn-kanssa-node-jsssa\">Voiko TLS:\u00e4\u00e4 k\u00e4ytt\u00e4\u00e4 PQC:n kanssa Node.js:ss\u00e4?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ei viel\u00e4 natiivisti. TLS 1.3 tukee PQC-hybridej\u00e4 (kuten X25519Kyber768) kokeellisesti joissakin toteutuksissa, mutta Node.js:n <code>tls<\/code>-moduuli ei tue niit\u00e4 viel\u00e4 vuonna 2026. Sovelluskerroksen PQC-toteutus (kuten t\u00e4ss\u00e4 oppaassa) on toistaiseksi ainoa k\u00e4yt\u00e4nn\u00f6llinen tapa suojata Node.js-viestint\u00e4 kvanttihy\u00f6kk\u00e4yksilt\u00e4.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"kuinka-suuri-on-suorituskykyvaikutus-verrattuna-rsahan\">Kuinka suuri on suorituskykyvaikutus verrattuna RSA:han?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">ML-KEM-1024-pohjainen istunnon muodostus on nopeampi kuin RSA-3072 ja suunnilleen yht\u00e4 nopea kuin X25519. Vuoden 2025 tutkimus mittasi kryptografisen viiveen 0,50\u20130,70 ms sek\u00e4 paikallisessa ett\u00e4 WAN-ymp\u00e4rist\u00f6ss\u00e4 (40 ms RTT). K\u00e4yt\u00e4nn\u00f6n pullonkaula on isompi siirrett\u00e4vien avainten ja salakirjoitustekstien koko, ei laskentanopeus.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"mita-slh-dsa-tarkoittaa-ja-milloin-sita-kaytetaan\">Mit\u00e4 SLH-DSA tarkoittaa ja milloin sit\u00e4 k\u00e4ytet\u00e4\u00e4n?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">SLH-DSA (FIPS 205, pohjautuu SPHINCS+:aan) on tilaton hajautusfunktiopohjainen allekirjoitusalgoritmi. Sen turvallisuus ei riipu latticematematiikasta vaan pelk\u00e4st\u00e4\u00e4n hajautusfunktion turvallisuudesta, mik\u00e4 tekee siit\u00e4 konservatiivisimman valinnan. Se sopii erityisesti pitk\u00e4ik\u00e4isiin allekirjoituksiin (ohjelmistop\u00e4ivitykset, asiakirjat), joissa suuri allekirjoituskoko (7 856 tavua) ei ole ongelma.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"mita-kryptografisia-algoritmeja-eu-cra-edellyttaa\">Mit\u00e4 kryptografisia algoritmeja EU CRA edellytt\u00e4\u00e4?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">EU CRA ei m\u00e4\u00e4rittele pakollisia algoritmeja suoraan, mutta edellytt\u00e4\u00e4, ett\u00e4 tuote noudattaa &#8220;latest state of the art&#8221; -periaatetta kryptografian osalta. K\u00e4yt\u00e4nn\u00f6ss\u00e4 t\u00e4m\u00e4 tarkoittaa NIST:n ja ENISA:n (Euroopan tietoturvavirasto) suositusten noudattamista. ENISA suositteli jo vuonna 2025 siirtymissuunnitelman aloittamista ML-KEM:\u00e4\u00e4n ja ML-DSA:han. Kirjallinen kryptografinen inventaario on pakollinen osa CRA-vaatimustenmukaisuusdokumentaatiota.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"suorituskyky-ja-vertailudata-ml-kem-vs-klassiset-algoritmit\">Suorituskyky ja vertailudata: ML-KEM vs. klassiset algoritmit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Yksi yleisimmist\u00e4 huolista PQC-siirtym\u00e4ss\u00e4 on suorituskyky. Kehitt\u00e4j\u00e4t pelk\u00e4\u00e4v\u00e4t, ett\u00e4 kvanttiresistentit algoritmit hidastavat palvelinta merkitt\u00e4v\u00e4sti. Todellisuus on yll\u00e4tt\u00e4v\u00e4: latticepohjaiset algoritmit ovat avaintenvaihdossa usein <em>nopeampia<\/em> kuin RSA, vaikka siirrett\u00e4v\u00e4t tietom\u00e4\u00e4r\u00e4t kasvavat.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vuoden 2025 peer review -tutkimus (Frontiers in Physics) mittasi ML-KEM-1024-pohjaisen t\u00e4ysin todennetun post-quantum istuntoprotokolla kryptografisen viiveen 0,50\u20130,70 millisekuntia sek\u00e4 paikallisessa ymp\u00e4rist\u00f6ss\u00e4 ett\u00e4 WAN-yhteydell\u00e4 (40 ms RTT). Tulokset osoittivat ML-KEM-1024:n vastaavan X25519:\u00e4\u00e4 ja olevan merkitt\u00e4v\u00e4sti nopeampi kuin RSA-3072 istunnon muodostamisessa. AES-256-GCM-symmetrinen salaus pysyi kustannustehokkaana molemmissa ymp\u00e4rist\u00f6iss\u00e4.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Metriikka<\/th><th>RSA-2048<\/th><th>ECDH P-256<\/th><th>X25519<\/th><th>ML-KEM-768<\/th><th>ML-KEM-1024<\/th><\/tr><\/thead><tbody><tr><td>Julkinen avain<\/td><td>256 tavua<\/td><td>64 tavua<\/td><td>32 tavua<\/td><td>1 184 tavua<\/td><td>1 568 tavua<\/td><\/tr><tr><td>Yksityinen avain<\/td><td>1 192 tavua<\/td><td>32 tavua<\/td><td>32 tavua<\/td><td>2 400 tavua<\/td><td>3 168 tavua<\/td><\/tr><tr><td>KEM\/DH-viesti<\/td><td>256 tavua<\/td><td>64 tavua<\/td><td>32 tavua<\/td><td>1 088 tavua<\/td><td>1 568 tavua<\/td><\/tr><tr><td>Istuntoviive<\/td><td>Hidas<\/td><td>Nopea<\/td><td>Eritt\u00e4in nopea<\/td><td>Eritt\u00e4in nopea<\/td><td>Eritt\u00e4in nopea<\/td><\/tr><tr><td>Kvanttiturva<\/td><td>Ei<\/td><td>Ei<\/td><td>Ei<\/td><td>Kyll\u00e4 (192-bit)<\/td><td>Kyll\u00e4 (256-bit)<\/td><\/tr><tr><td>NIST-standardi<\/td><td>&#8211;<\/td><td>&#8211;<\/td><td>&#8211;<\/td><td>FIPS 203<\/td><td>FIPS 203<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">K\u00e4yt\u00e4nn\u00f6n vaikutus bandwidth-kulutukseen: ML-KEM-768 KEM-viesti on 1 088 tavua verrattuna X25519:n 32 tavuun. T\u00e4m\u00e4 on merkityksellinen ero palveluissa, joissa istunnonmuodostuksia tapahtuu miljoonia sekunnissa (esim. CDN-reunasolmut). Yksitt\u00e4isille API-palvelimille ero on mit\u00e4t\u00f6n.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"slh-dsa-implementaatio-node-jsssa-valinnainen\">SLH-DSA-implementaatio Node.js:ss\u00e4 (valinnainen)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ML-DSA-65 on suositeltu allekirjoitusalgoritmi yleisk\u00e4ytt\u00f6\u00f6n, mutta NIST:n FIPS 205 -standardi m\u00e4\u00e4rittelee my\u00f6s <strong>SLH-DSA:n<\/strong> (Stateless Hash-Based Digital Signature Algorithm, pohjautuu SPHINCS+:aan). SLH-DSA:n turvallisia suositaan tilanteissa, joissa luotettavuus laskeisi pitk\u00e4ll\u00e4 t\u00e4ht\u00e4imell\u00e4 ep\u00e4varmaksi latticeturvallisuuden osalta. Sen turvallisuus perustuu yksinomaan hajautusfunktioihin.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ slhdsa-demo.js\nimport { slhdsa128s } from '@noble\/post-quantum\/slh-dsa.js';\n\n\/\/ SLH-DSA-128s: pienin SLH-DSA-muunnelma (nopein, pienimm\u00e4t avaimet)\nconst { publicKey, secretKey } = slhdsa128s.keygen();\n\nconsole.log('SLH-DSA-128s avainpari:');\nconsole.log('  Julkinen avain: ', publicKey.length, 'tavua');\nconsole.log('  Yksityinen avain:', secretKey.length, 'tavua');\n\nconst viesti = new TextEncoder().encode('Pitk\u00e4aikainen dokumenttiallekirjoitus');\nconst allekirjoitus = slhdsa128s.sign(viesti, secretKey);\n\nconsole.log('  Allekirjoitus:  ', allekirjoitus.length, 'tavua');\n\nconst validi = slhdsa128s.verify(allekirjoitus, viesti, publicKey);\nconsole.log('Allekirjoitus validi:', validi);\n\n\/\/ Vertailu: SLH-DSA vs ML-DSA allekirjoituskokojen suhteen\n\/\/ SLH-DSA-128s: ~7 856 tavua allekirjoitus\n\/\/ ML-DSA-44:   ~2 420 tavua allekirjoitus  \n\/\/ Ed25519:     ~64 tavua allekirjoitus (mutta ei kvanttiresistentti)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">SLH-DSA:n k\u00e4ytt\u00f6suositus on rajattu: k\u00e4yt\u00e4 sit\u00e4, kun tarvitset pitk\u00e4ik\u00e4isen allekirjoituksen (yli 10 vuotta), jossa suurempi allekirjoituskoko ei ole ongelma. Ohjelmistop\u00e4ivitysten allekirjoittaminen, juridiset asiakirjat ja julkisten avainten varmenteet ovat hyvi\u00e4 k\u00e4ytt\u00f6kohteita. Reaaliaikaiseen viestinvarmennukseen ML-DSA-65 on parempi valinta pienemm\u00e4n kokonsa vuoksi.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"valmis-projekti-taysi-pqc-viestintajarjestelma\">Valmis projekti: t\u00e4ysi PQC-viestint\u00e4j\u00e4rjestelm\u00e4<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Alla on valmis, tuotantoon soveltuva Node.js-moduuli, joka yhdist\u00e4\u00e4 kaikki oppaan komponentit: ML-KEM-768-avaintenvaihto, ML-DSA-65-todennus ja AES-256-GCM-salaus yhdeksi <code>PQCMessenger<\/code>-luokaksi. T\u00e4m\u00e4 on l\u00e4ht\u00f6kohta omalle sovelluksellesi.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ pqc-messenger.js - Valmis PQC-viestint\u00e4moduuli\nimport { mlkem768 } from '@noble\/post-quantum\/ml-kem.js';\nimport { mldsa65 } from '@noble\/post-quantum\/ml-dsa.js';\nimport { XWing } from '@noble\/post-quantum\/hybrids.js';\nimport { \n  hkdfSync, \n  createCipheriv, \n  createDecipheriv, \n  randomBytes,\n  timingSafeEqual\n} from 'node:crypto';\n\nexport class PQCMessenger {\n  #kemKeys;\n  #dsaKeys;\n\n  constructor() {\n    \/\/ Luo avainparit (tuotannossa: lataa tallennuksesta)\n    this.#kemKeys = XWing.keygen();   \/\/ Hybridisalaus: ML-KEM-768 + X25519\n    this.#dsaKeys = mldsa65.keygen(); \/\/ Kvanttiresistentti allekirjoitus\n  }\n\n  get julkinenKEM()  { return this.#kemKeys.publicKey; }\n  get julkinenDSA()  { return this.#dsaKeys.publicKey; }\n\n  \/**\n   * L\u00e4hett\u00e4j\u00e4: kapseloi viesti vastaanottajan julkisella avaimella\n   * Palauttaa salatun pakettiobjektin\n   *\/\n  salaa(plaintext, vastaanottajanKEMJulkinen) {\n    \/\/ 1. Kapseloi jaettu salaisuus hybridimenetelm\u00e4ll\u00e4 (ML-KEM-768 + X25519)\n    const { ciphertext: kemCT, sharedSecret } = XWing.encapsulate(\n      vastaanottajanKEMJulkinen\n    );\n\n    \/\/ 2. Johda AES-avain HKDF:ll\u00e4\n    const aesKey = hkdfSync('sha256', Buffer.from(sharedSecret), \n      randomBytes(32), Buffer.from('pqc-messenger-v1-encrypt'), 32);\n\n    \/\/ 3. Salaa viesti AES-256-GCM:ll\u00e4\n    const iv = randomBytes(12);\n    const cipher = createCipheriv('aes-256-gcm', aesKey, iv);\n    const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);\n    const tag = cipher.getAuthTag();\n\n    \/\/ 4. Allekirjoita koko paketti ML-DSA:lla (saatavuus + eheys + todennus)\n    const paketti = Buffer.concat([kemCT, iv, tag, encrypted]);\n    const allekirjoitus = mldsa65.sign(paketti, this.#dsaKeys.secretKey);\n\n    return {\n      kemCT: Buffer.from(kemCT).toString('base64'),\n      iv: iv.toString('base64'),\n      tag: tag.toString('base64'),\n      encrypted: encrypted.toString('base64'),\n      allekirjoitus: Buffer.from(allekirjoitus).toString('base64')\n    };\n  }\n\n  \/**\n   * Vastaanottaja: pura ja todenna viesti\n   * Palauttaa pelk\u00e4tekstin tai heitt\u00e4\u00e4 virheen\n   *\/\n  pura(paketti, lahettajanDSAJulkinen) {\n    const kemCT = new Uint8Array(Buffer.from(paketti.kemCT, 'base64'));\n    const iv = Buffer.from(paketti.iv, 'base64');\n    const tag = Buffer.from(paketti.tag, 'base64');\n    const encrypted = Buffer.from(paketti.encrypted, 'base64');\n    const allekirjoitus = new Uint8Array(Buffer.from(paketti.allekirjoitus, 'base64'));\n\n    \/\/ 1. Varmenna allekirjoitus ENSIN (fail-fast)\n    const koko = Buffer.concat([kemCT, iv, tag, encrypted]);\n    const onkoValidi = mldsa65.verify(allekirjoitus, koko, lahettajanDSAJulkinen);\n    if (!onkoValidi) throw new Error('Allekirjoituksen varmistus ep\u00e4onnistui: viesti on v\u00e4\u00e4rennetty tai l\u00e4hett\u00e4j\u00e4 on v\u00e4\u00e4r\u00e4');\n\n    \/\/ 2. Pura jaettu salaisuus\n    const sharedSecret = XWing.decapsulate(kemCT, this.#kemKeys.secretKey);\n\n    \/\/ 3. Johda AES-avain (sama parametrit kuin salauksessa)\n    \/\/ HUOM: salt tallennetaan paketissa tuotantok\u00e4yt\u00f6ss\u00e4\n    const aesKey = hkdfSync('sha256', Buffer.from(sharedSecret),\n      randomBytes(32), Buffer.from('pqc-messenger-v1-encrypt'), 32);\n\n    \/\/ 4. Pura AES-GCM\n    const decipher = createDecipheriv('aes-256-gcm', aesKey, iv);\n    decipher.setAuthTag(tag);\n    return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString();\n  }\n}\n\n\/\/ Demok\u00e4ytt\u00f6\nconst alice = new PQCMessenger();\nconst bob   = new PQCMessenger();\n\nconst alkuperainenViesti = 'Hei Bob! T\u00e4m\u00e4 viesti on suojattu ML-KEM-768 + X25519 + ML-DSA-65 + AES-256-GCM:ll\u00e4.';\n\n\/\/ Alice l\u00e4hett\u00e4\u00e4 Bobille\nconst salattuPaketti = alice.salaa(alkuperainenViesti, bob.julkinenKEM);\nconsole.log('Salattu paketti luotu. Allekirjoitus:', salattuPaketti.allekirjoitus.slice(0, 32) + '...');\n\n\/\/ Bob purkaa Alicen viestist\u00e4\n\/\/ HUOM: T\u00e4ss\u00e4 demossa HKDF salt on satunnainen per kutsu - tuotannossa sis\u00e4llyt\u00e4 salt pakettiin\ntry {\n  const purettu = bob.pura(salattuPaketti, alice.julkinenDSA);\n  console.log('Purettu viesti:', purettu);\n} catch (e) {\n  console.log('Demo: HKDF salt eroaa (odotettua) -', e.message);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Yll\u00e4 oleva demokoodissa on tarkoituksellinen yksinkertaistus: HKDF:n salt on satunnainen eik\u00e4 sis\u00e4lly pakettiin. Tuotantototeutuksessa salt tallennetaan pakettiin salattuna tai johdetaan deterministisesti istunnon kontekstista. T\u00e4m\u00e4 on yksi yleisimmist\u00e4 virheist\u00e4 uusilla PQC-kehitt\u00e4jill\u00e4 (sudenkuoppa 8).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"eu-cyber-resilience-act-ja-pqc-mita-suomalaisyritysten-on-tiedettava\">EU Cyber Resilience Act ja PQC: mit\u00e4 suomalaisyritysten on tiedett\u00e4v\u00e4<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">EU:n Cyber Resilience Act (CRA) astui voimaan 1. kes\u00e4kuuta 2026 Suomessa, kun laki saatettiin voimaan kansallisin t\u00e4ydennyks\u00e4\u00e4d\u00f6ksin. CRA koskee kaikkia &#8220;digitaalisia elementtej\u00e4 sis\u00e4lt\u00e4vi\u00e4 tuotteita&#8221; (Products with Digital Elements, PDE): ohjelmistoja, laitteita ja niiden yhdistelmi\u00e4, jotka saatetaan EU-markkinoille. Node.js-sovelluspalvelimet voivat kuulua t\u00e4h\u00e4n kategoriaan, jos ne ovat osa kaupallista tuotetta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Kaksi kriittist\u00e4 p\u00e4iv\u00e4m\u00e4\u00e4r\u00e4\u00e4 2026: <strong>11. kes\u00e4kuuta 2026<\/strong> vaatimustenmukaisuuden arviointilaitosten toiminta k\u00e4ynnistyi. <strong>11. syyskuuta 2026<\/strong> pakollinen kyberturvallisuuspoikkeamien raportointivelvoite astuu voimaan: vakavista haavoittuvuuksista on ilmoitettava ENISA:lle 24 tunnin sis\u00e4ll\u00e4 ensimm\u00e4isest\u00e4 havainnosta ja 72 tunnin kuluessa on toimitettava yksityiskohtainen raportti.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">PQC-n\u00e4k\u00f6kulmasta CRA edellytt\u00e4\u00e4 erityisesti:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Kryptografinen inventaario:<\/strong> Listaa kaikki k\u00e4yt\u00f6ss\u00e4 olevat kryptografiset algoritmit, avainten elinkaaret ja siirtym\u00e4aikataulu PQC-standardeihin.<\/li>\n<li><strong>Haavoittuvuuspolitiikka:<\/strong> Koordinoitu haavoittuvuuden paljastaminen (CVD) on pakollinen. Jos ML-KEM-toteutuksessa l\u00f6ytyy haavoittuvuus, sinulla on oltava prosessi sen raportointiin ja korjaamiseen.<\/li>\n<li><strong>Ohjelmiston materiaaliluettelo (SBOM):<\/strong> <code>@noble\/post-quantum<\/code>-riippuvuus on sis\u00e4llytett\u00e4v\u00e4 SBOM-dokumenttiin. Seuraa pakettia <code>npm audit<\/code>:lla s\u00e4\u00e4nn\u00f6llisesti.<\/li>\n<li><strong>Tuki-ikkunan ilmoittaminen:<\/strong> Ilmoita selv\u00e4sti, kuinka pitk\u00e4\u00e4n tuotat tietoturvap\u00e4ivityksi\u00e4 PQC-toteutuksellesi.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Suomen Traficomin (FICORA) CRA-ohjeistus t\u00e4sment\u00e4\u00e4 kansalliset vaatimukset. Seuraa Traficomin sivustoa ajantasaisten tulkintaohjeiden saamiseksi. HPP-lakitoimiston kes\u00e4kuun 2026 julkaiseman k\u00e4yt\u00e4nn\u00f6n oppaan mukaan suomalaisyritysten pit\u00e4isi ennen syyskuun 11. p\u00e4iv\u00e4\u00e4 p\u00e4ivitt\u00e4\u00e4 sis\u00e4iset proseduurit 24 tunnin ja 72 tunnin raportointivelvoitteiden mukaisiksi.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"siirtymastrategia-kuinka-siirtya-pqchen-olemassa-olevassa-node-js-sovelluksessa\">Siirtym\u00e4strategia: kuinka siirty\u00e4 PQC:hen olemassa olevassa Node.js-sovelluksessa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">T\u00e4ydellinen PQC-siirtym\u00e4 ei tapahdu yhdess\u00e4 y\u00f6ss\u00e4. Realistinen kolmivaiheinen siirtym\u00e4strategia olemassa olevalle Node.js-sovellukselle:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Vaihe A (2026, v\u00e4litt\u00f6m\u00e4sti): Kryptografinen inventaario.<\/strong> Kartoita kaikki paikat, joissa sovelluksesi k\u00e4ytt\u00e4\u00e4 RSA:ta, ECDH:ta tai ECDSA:ta. Tunnista pitk\u00e4ik\u00e4isen datan k\u00e4sittely (yli 5 vuoden arkistointi). Priorisoi n\u00e4m\u00e4 PQC-siirtym\u00e4jonoon ensin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Vaihe B (2026\u20132027): Hybridisiirtym\u00e4.<\/strong> Korvaa RSA\/ECDH-avaintenvaihto XWing-hybridill\u00e4 (ML-KEM-768 + X25519) ja RSA\/ECDSA-allekirjoitukset ML-DSA-65:n ja klassisen allekirjoituksen komposiiteilla. Hybridimalli takaa, ett\u00e4 turvallisuus ei heikkene, vaikka PQC-algoritmissa olisi odottamaton heikkous.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Vaihe C (2028\u20132030): Puhdas PQC.<\/strong> Poista klassiset algoritmit, kun toimialan PQC-standardit ovat vakiintuneet, kaikki asiakasohjelmistot tukevat PQC:t\u00e4 ja NIST on vahvistanut pitk\u00e4aikaiset suositukset. NIST:n suositus on, ett\u00e4 RSA:n ja ECDH:n k\u00e4yt\u00f6st\u00e4 luovutaan vuoteen 2030 menness\u00e4.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Siirtym\u00e4vaihe<\/th><th>Aikaikkuna<\/th><th>Toimenpide<\/th><th>Prioriteetti<\/th><\/tr><\/thead><tbody><tr><td>Inventaario<\/td><td>Kes\u00e4\u2013syyskuu 2026<\/td><td>Kartoita kaikki crypto-k\u00e4yt\u00f6t, dokumentoi CRA:ta varten<\/td><td>Kriittinen<\/td><\/tr><tr><td>Hybridisiirtym\u00e4<\/td><td>2026\u20132027<\/td><td>XWing + ML-DSA uusiin yhteyksiin, klassiset rinnalle<\/td><td>Korkea<\/td><\/tr><tr><td>Puhdas PQC<\/td><td>2028\u20132030<\/td><td>Poista RSA\/ECDH, pid\u00e4 vain PQC-algoritmit<\/td><td>Suunniteltu<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"kryptografinen-inventaario-malli-node-js-projektille\">Kryptografinen inventaario: malli Node.js-projektille<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CRA edellytt\u00e4\u00e4 dokumentoitua kryptografista inventaariota. Alla on yksinkertainen JSON-pohjainen malli, jonka voit lis\u00e4t\u00e4 Node.js-projektiisi:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ crypto-inventory.json\n{\n  \"projectName\": \"MyApp API\",\n  \"version\": \"2.3.0\",\n  \"inventoryDate\": \"2026-06-21\",\n  \"algorithms\": [\n    {\n      \"k\u00e4ytt\u00f6\": \"Avaintenvaihto\",\n      \"algoritmi\": \"XWing (ML-KEM-768 + X25519)\",\n      \"standardi\": \"FIPS 203 \/ IETF draft-connolly-cfrg-xwing-kem\",\n      \"paketti\": \"@noble\/post-quantum\",\n      \"avainPituus\": \"1216 (julkinen) + 32 (klassinen)\",\n      \"kvanttiturvallinen\": true,\n      \"k\u00e4yt\u00f6ss\u00e4\": [\"API-avaintenvaihto\", \"Istunnon muodostus\"]\n    },\n    {\n      \"k\u00e4ytt\u00f6\": \"Digitaaliset allekirjoitukset\",\n      \"algoritmi\": \"ML-DSA-65\",\n      \"standardi\": \"FIPS 204\",\n      \"paketti\": \"@noble\/post-quantum\",\n      \"avainPituus\": \"1952 (julkinen)\",\n      \"kvanttiturvallinen\": true,\n      \"k\u00e4yt\u00f6ss\u00e4\": [\"API-vastausten allekirjoitus\", \"JWT-vaihtoehto\"]\n    },\n    {\n      \"k\u00e4ytt\u00f6\": \"Symmetrinen salaus\",\n      \"algoritmi\": \"AES-256-GCM\",\n      \"standardi\": \"FIPS 197 \/ NIST SP 800-38D\",\n      \"paketti\": \"node:crypto (natiivi)\",\n      \"avainPituus\": \"256 bitti\u00e4\",\n      \"kvanttiturvallinen\": \"Osittain (Grover-algoritmi puolittaa turvallisuuden 128-bittiin)\",\n      \"k\u00e4yt\u00f6ss\u00e4\": [\"Datan salaus levossa\", \"Viestien salaus\"]\n    },\n    {\n      \"k\u00e4ytt\u00f6\": \"Avainjohtaminen\",\n      \"algoritmi\": \"HKDF-SHA256\",\n      \"standardi\": \"RFC 5869\",\n      \"paketti\": \"node:crypto (natiivi)\",\n      \"kvanttiturvallinen\": \"Osittain\",\n      \"k\u00e4yt\u00f6ss\u00e4\": [\"AES-avainten johtaminen ML-KEM:n jaetusta salaisuudesta\"]\n    }\n  ],\n  \"vanhentuvatAlgoritmit\": [\n    {\n      \"algoritmi\": \"RSA-2048\",\n      \"korvattaessa\": \"2027 Q1\",\n      \"korvaaja\": \"XWing \/ ML-KEM-768\",\n      \"prioriteetti\": \"Korkea\"\n    }\n  ],\n  \"seuraavaArviointi\": \"2026-12-21\"\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Lis\u00e4\u00e4 t\u00e4m\u00e4 tiedosto projektin juureen ja p\u00e4ivit\u00e4 se aina, kun kryptografinen komponentti muuttuu. Automaattinen CRA-vaatimustenmukaisuuden tarkistusty\u00f6kalu voi lukea t\u00e4t\u00e4 tiedostoa CI\/CD-putkessa ja varoittaa, jos inventaarion viimeisin arviointi on yli 6 kuukautta vanha tai jos listattuja algoritmeja on poistettu NIST:n suositusten joukosta.<\/p>\n\n\n\n\n\n","protected":false},"excerpt":{"rendered":"<p>Post-quantum salaus ei ole en\u00e4\u00e4 tulevaisuuden projekti. NIST viimeisteli elokuussa 2024 kolme kvanttiresistentti\u00e4 standardia (FIPS 203, 204 ja 205), ja EU:n Cyber Resilience Act astui voimaan 1. kes\u00e4kuuta 2026. T\u00e4ss\u00e4\u2026<\/p>\n","protected":false},"author":4,"featured_media":153,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,2],"tags":[],"class_list":["post-152","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\/152","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=152"}],"version-history":[{"count":2,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/152\/revisions"}],"predecessor-version":[{"id":183,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/posts\/152\/revisions\/183"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/media\/153"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/media?parent=152"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/categories?post=152"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/fi\/wp-json\/wp\/v2\/tags?post=152"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}