Ed25519 on noussut modernin digitaalisen allekirjoituksen oletusvalinnaksi. Avain on vain 32 tavua, allekirjoitus 64 tavua, ja turvataso vastaa noin 128:aa bittiä. Tässä oppaassa rakennat täysin toimivan allekirjoitusjärjestelmän pelkällä Node.js:n sisäänrakennetulla crypto-moduulilla, ilman ulkoisia riippuvuuksia. Käymme läpi 9 konkreettista vaihetta noin 30 minuutissa: avainparin luonti, allekirjoitus, vahvistus, avainten tallennus, JWT EdDSA-algoritmilla sekä valmis tiedostojen eheyden varmistustyökalu.

Ed25519 määriteltiin RFC 8032 -standardissa osana EdDSA-allekirjoitusperhettä, ja Node.js on tukenut sitä natiivisti versiosta 12 lähtien. Vuonna 2026 vakaa valinta on Node.js 24 LTS. Oppaan koodi toimii myös Node.js 20:ssä. Päivitetty 16. kesäkuuta 2026.

Mikä Ed25519 on ja miksi se on oletusvalinta 2026

Ed25519 on EdDSA-allekirjoitusskeeman (Edwards-curve Digital Signature Algorithm) yleisimmin käytetty muunnelma. Se rakentuu Edwardsin muotoon kirjoitetulle Curve25519-elliptiselle käyrälle, jonka julkaisi Daniel J. Bernsteinin tutkimusryhmä. Standardi RFC 8032 määrittelee sekä Ed25519:n että vahvemman Ed448:n. Käytännössä lähes kaikki sovellukset käyttävät Ed25519:ää, koska se tarjoaa erinomaisen tasapainon nopeuden, koon ja turvallisuuden välillä.

Ed25519:n keskeinen ero perinteiseen ECDSA-allekirjoitukseen on determinismi. ECDSA tarvitsee jokaiselle allekirjoitukselle tuoreen satunnaisen kertaluvun (nonce). Jos sama kertaluku vuotaa tai toistuu kahdessa allekirjoituksessa, hyökkääjä voi laskea salaisen avaimen. Tämä virhe on kaatanut tuotantojärjestelmiä Sonyn PlayStation 3:sta lähtien. Ed25519 johtaa kertaluvun deterministisesti salaisesta avaimesta ja viestistä hajautusfunktion avulla, joten satunnaislukugeneraattorin heikkous ei vuoda avainta. Tämä tekee Ed25519:stä operatiivisesti turvallisemman kuin nonce-pohjaiset skeemat.

Ed25519 on jo kaikkialla. OpenSSH suosittelee sitä SSH-avaimien oletustyypiksi. TLS 1.3 tukee EdDSA-sertifikaattiallekirjoituksia. Signal-protokolla nojaa Ed25519-allekirjoituksiin avaintenvaihdossaan. Age-salaustyökalu ja lukuisat lohkoketjut käyttävät samaa käyrää. JWT- ja JOSE-maailmassa algoritmin tunniste on yksinkertaisesti EdDSA. Kun yhdistät tämän pienen avainkoon ja korkean nopeuden, ymmärrät miksi Ed25519 on vuonna 2026 modernin sovelluskehityksen oletus.

Tärkeä rajaus heti alkuun: Ed25519 tekee vain allekirjoituksia ja niiden vahvistusta. Se ei salaa dataa eikä vaihda avaimia. Jos tarvitset avaintenvaihtoa tai salausta, käytät samaa matemaattista käyrää mutta eri funktiota nimeltä X25519. Lue ero tarkasti kohdasta “Yleiset sudenkuopat”, sillä juuri tämä sekaannus on aloittelijoiden yleisin virhe.

Miksi juuri nyt, vuonna 2026? Kolme syytä ajaa siirtymää. Ensinnäkin laskentateho on halventunut niin paljon, että RSA-2048:n hidas allekirjoitus alkaa olla pullonkaula suuren liikenteen palveluissa, ja siirtymä RSA-4096:een vain pahentaa ongelmaa. Toiseksi mobiili- ja IoT-laitteet hyötyvät dramaattisesti pienemmistä avaimista ja matalammasta laskentakuormasta. Kolmanneksi koko ekosysteemi on kypsynyt: Node.js, selaimet, SSH, TLS-kirjastot ja JOSE-standardit tukevat Ed25519:ää vakaasti, joten yhteensopivuusesteet, jotka aiemmin pakottivat RSA:han, ovat suurelta osin poistuneet. Tämä opas antaa sinulle valmiudet hyödyntää tätä siirtymää käytännössä.

Ed25519 vs RSA vs ECDSA: vertailutaulukko

Ennen koodausta kannattaa ymmärtää, miksi Ed25519 voittaa useimmissa nykyaikaisissa käyttötapauksissa. Alla oleva taulukko vertaa kolmea yleisintä allekirjoitusalgoritmia ominaisuuksilla, joilla on käytännön merkitystä tuotannossa. Avain- ja allekirjoituskoot ovat tarkkoja, mittasin ne tämän oppaan esimerkkikoodilla.

OminaisuusEd25519ECDSA (P-256)RSA-2048
Julkisen avaimen koko32 tavua~65 tavua~270 tavua
Salaisen avaimen koko (raaka)32 tavua32 tavua~1190 tavua
Allekirjoituksen koko64 tavua~70-72 tavua256 tavua
Turvataso~128 bittiä~128 bittiä~112 bittiä
DeterministinenKylläEi (oletuksena)Kyllä (RSASSA-PKCS1)
Nonce-vuodon riskiEiKorkeaEi
AllekirjoitusnopeusErittäin nopeaNopeaHidas
VahvistusnopeusNopeaNopeaErittäin nopea
Node.js-natiivitukiv12+v12+v0.x+
KvanttiturvallinenEiEiEi
Koot mitattu Node.js 20:n crypto-moduulilla. RSA:n nopeudet riippuvat avainkoosta.

Taulukko kertoo selkeän tarinan. Ed25519:n julkinen avain mahtuu yhteen QR-koodiin, kun RSA-2048-avain on lähes kymmenkertainen. Allekirjoitus on 64 tavua, neljäsosa RSA:n 256 tavusta. Allekirjoittaminen on dramaattisesti nopeampaa kuin RSA:lla, ja determinismi poistaa kokonaisen luokan haavoittuvuuksia. RSA voittaa vain yhdessä mittarissa: julkisen avaimen vahvistus on hieman nopeampaa pienellä eksponentilla. Useimmissa palvelimissa allekirjoituksia syntyy enemmän kuin niitä vahvistetaan, joten Ed25519 on kokonaisuutena tehokkaampi.

Yksi rivi ansaitsee huomion: yksikään näistä ei ole kvanttiturvallinen. Riittävän tehokas kvanttitietokone, joka ajaa Shorin algoritmia, murtaisi sekä RSA:n että kaikki elliptisen käyrän skeemat, Ed25519 mukaan lukien. Tämä ei ole akuutti uhka vuonna 2026, mutta se ohjaa pitkän aikavälin suunnittelua kohti hybridiratkaisuja. Palaamme tähän edistyneissä vinkeissä.

Esivaatimukset ja versiot

Tämä opas käyttää pääosin Node.js:n sisäänrakennettua crypto-moduulia, joten asennettavaa on vähän. Vahvista versiot ennen aloitusta. Suosittelen Node.js 24 LTS:ää, joka on vuoden 2026 vakaa pitkän tuen julkaisu. Koodi toimii identtisesti myös Node.js 20:ssä ja 22:ssa.

KomponenttiVähimmäisversioSuositus 2026Tarkistuskomento
Node.js20.x24 LTSnode -v
npm10.x10.8+npm -v
OpenSSL (Noden sisäinen)3.0+3.xnode -p process.versions.openssl
jose (vain JWT-vaihe)5.05.xnpm ls jose

Tarvitset myös perustiedot komentoriviltä ja JavaScriptin moduuleista. Käytämme moderneja ES-moduuleja (import), mutta kaikki esimerkit toimivat myös CommonJS-syntaksilla (require), jos vaihdat tuontilauseet. Varmista lopuksi, että OpenSSL on versiota 3, koska vanha 1.1.1 on saavuttanut elinkaarensa lopun eikä saa enää tietoturvapäivityksiä.

Vaihe 1: Projektin alustus

Luo uusi hakemisto ja alusta projekti. Asetamme type: "module", jotta voimme käyttää ES-moduulien import-syntaksia. Tämä on vuoden 2026 suositeltu tapa kirjoittaa uutta Node.js-koodia.

mkdir ed25519-allekirjoitus && cd ed25519-allekirjoitus
npm init -y
npm pkg set type="module"
node -v   # vahvista: v20.x tai uudempi

Luo hakemistorakenne. Pidämme avaimet erillisessä keys-kansiossa ja lisäämme sen heti .gitignore-tiedostoon, jotta salaiset avaimet eivät koskaan päädy versionhallintaan. Tämä on yksi tärkeimmistä tavoista välttää avainvuoto.

mkdir keys src
echo "keys/" >> .gitignore
echo "node_modules/" >> .gitignore

Projektirakenne on nyt valmis. Seuraavissa vaiheissa täytämme src-kansion moduuleilla, joista jokainen hoitaa yhden vastuun: avainten luonti, allekirjoitus, vahvistus ja lopulta valmis CLI-työkalu. Tämä modulaarisuus tekee koodista helposti testattavaa ja uudelleenkäytettävää tuotannossa.

Vaihe 2: Ed25519-avainparin luonti

Luodaan ensimmäinen avainpari. Node.js:n crypto.generateKeyPairSync tukee tyyppiä "ed25519" suoraan. Funktio palauttaa kaksi KeyObject-oliota: julkisen ja salaisen avaimen. Toisin kuin RSA, Ed25519 ei tarvitse modulus- tai eksponenttiparametreja, koska käyrä on kiinteä.

// src/generate.js
import { generateKeyPairSync } from 'crypto';

export function luoAvainpari() {
  const { publicKey, privateKey } = generateKeyPairSync('ed25519');
  return { publicKey, privateKey };
}

// Pikatesti suoraan ajettaessa
if (import.meta.url === `file://${process.argv[1]}`) {
  const { publicKey, privateKey } = luoAvainpari();
  const pubDer = publicKey.export({ type: 'spki', format: 'der' });
  const privDer = privateKey.export({ type: 'pkcs8', format: 'der' });
  console.log('Julkinen avain (DER):', pubDer.length, 'tavua');
  console.log('Salainen avain (DER):', privDer.length, 'tavua');
}

Aja moduuli ja katso tulos. Saat tarkat tavukokomäärät, jotka vahvistavat että avaimet ovat aitoja Ed25519-avaimia.

$ node src/generate.js
Julkinen avain (DER): 44 tavua
Salainen avain (DER): 48 tavua

Numerot ovat opettavaisia. Raaka Ed25519-julkisavain on vain 32 tavua, mutta SPKI-muotoinen DER-koodaus lisää 12 tavua ASN.1-otsaketta, jolloin tulos on 44 tavua. Salainen avain PKCS#8-muodossa on 48 tavua. Nämä otsakkeet kertovat työkaluille, että kyseessä on nimenomaan Ed25519-avain, joten ne ovat hyödyllisiä yhteensopivuuden kannalta. Vaiheessa 8 näytämme, miten saat käsiisi pelkät 32 raakatavua, kun tarvitset maksimaalista tiiviyttä.

Vaihe 3: Avainten tallennus ja lataus PEM-muodossa

Avaimet ovat hyödyttömiä, jos ne katoavat prosessin sulkeutuessa. Tallennetaan ne levylle PEM-muodossa, joka on yleisin tekstipohjainen avainformaatti ja yhteensopiva OpenSSL:n, OpenSSH:n ja useimpien kielten kirjastojen kanssa. Salainen avain kirjoitetaan tiukoilla tiedosto-oikeuksilla (0600), jotta vain omistaja voi lukea sen.

// src/storage.js
import { writeFileSync, readFileSync } from 'fs';
import { createPublicKey, createPrivateKey } from 'crypto';

export function tallennaAvaimet(publicKey, privateKey, hakemisto = 'keys') {
  const pubPem = publicKey.export({ type: 'spki', format: 'pem' });
  const privPem = privateKey.export({ type: 'pkcs8', format: 'pem' });

  writeFileSync(`${hakemisto}/public.pem`, pubPem);
  // mode 0o600 = vain omistaja saa lukea ja kirjoittaa
  writeFileSync(`${hakemisto}/private.pem`, privPem, { mode: 0o600 });
}

export function lataaAvaimet(hakemisto = 'keys') {
  const publicKey = createPublicKey(readFileSync(`${hakemisto}/public.pem`));
  const privateKey = createPrivateKey(readFileSync(`${hakemisto}/private.pem`));
  return { publicKey, privateKey };
}

PEM-tiedosto on luettavaa tekstiä. Julkinen avain alkaa rivillä -----BEGIN PUBLIC KEY----- ja salainen rivillä -----BEGIN PRIVATE KEY-----. Huomaa, ettei salainen avain ole tässä vielä salasanasuojattu. Tuotannossa kannattaa joko salata se erikseen tai säilyttää avaintenhallintapalvelussa kuten HashiCorp Vaultissa tai pilvialustan KMS:ssä. Käsittelemme salasanasuojausta vianmäärityksessä.

Yksi käytännön etu PEM-muodossa on yhteistoiminta OpenSSL:n kanssa. Voit tarkistaa luomasi avaimen komentoriviltä komennolla openssl pkey -in keys/private.pem -text -noout, joka tulostaa avaimen tyypin ja raakatavut. Tämä on hyödyllistä, kun integroit Node.js-koodia muiden kielten tai järjestelmien kanssa.

Vaihe 4: Viestin allekirjoitus

Nyt pääsemme ytimeen. Ed25519:n allekirjoitus tehdään funktiolla crypto.sign. Tässä on ratkaisevan tärkeä yksityiskohta, joka kompastuttaa lähes jokaisen aloittelijan: ensimmäinen argumentti, joka on muiden algoritmien kohdalla hajautusfunktion nimi, on Ed25519:llä oltava null. Syynä on se, että Ed25519 hoitaa hajautuksen sisäisesti osana algoritmia (se käyttää SHA-512:ta). Jos annat sille esimerkiksi merkkijonon "sha256", saat virheen.

// src/sign.js
import { sign } from 'crypto';

export function allekirjoita(viesti, privateKey) {
  // viesti voi olla Buffer tai merkkijono; muunnetaan Bufferiksi
  const data = Buffer.isBuffer(viesti) ? viesti : Buffer.from(viesti, 'utf8');
  // HUOM: ensimmainen argumentti on null, ei hajautusfunktion nimi
  const allekirjoitus = sign(null, data, privateKey);
  return allekirjoitus; // 64 tavun Buffer
}

Allekirjoitus on aina tasan 64 tavua riippumatta viestin pituudesta. Allekirjoitatpa yhden tavun tai yhden gigatavun, tulos on 64 tavua. Tämä johtuu siitä, että Ed25519 hajauttaa viestin ensin kiinteäpituiseksi ja allekirjoittaa hajautuksen. Pieni demonstraatio havainnollistaa determinismin: sama viesti ja sama avain tuottavat aina täsmälleen saman allekirjoituksen. Tämä on tahallinen ominaisuus, ei vika.

$ node -e '
import("./src/generate.js").then(async (g) => {
  const { sign } = await import("crypto");
  const { privateKey } = g.luoAvainpari();
  const a = sign(null, Buffer.from("Hei Suomi"), privateKey);
  const b = sign(null, Buffer.from("Hei Suomi"), privateKey);
  console.log("Pituus:", a.length, "tavua");
  console.log("Determinismi:", a.equals(b));
})'
Pituus: 64 tavua
Determinismi: true

Tallenna allekirjoitus yleensä Base64- tai heksamuodossa, kun lähetät sen verkon yli tai tallennat tietokantaan. Buffer muuntuu Base64:ksi komennolla allekirjoitus.toString('base64'). Muista käyttää samaa koodausta vahvistuksessa, sillä koodausten sekaannus on yleisin syy siihen, että muuten oikea allekirjoitus ei vahvistu.

Vaihe 5: Allekirjoituksen vahvistus

Vahvistus on allekirjoituksen peilikuva. Funktio crypto.verify ottaa saman null-argumentin, alkuperäisen viestin, julkisen avaimen ja allekirjoituksen. Se palauttaa totuusarvon: true jos allekirjoitus on aito ja viesti on muuttumaton, muuten false. Vahvistus ei koskaan heitä poikkeusta väärän allekirjoituksen vuoksi, joten älä luota try-catch-lohkoon vaan tarkista paluuarvo.

// src/verify.js
import { verify } from 'crypto';

export function vahvista(viesti, allekirjoitus, publicKey) {
  const data = Buffer.isBuffer(viesti) ? viesti : Buffer.from(viesti, 'utf8');
  const sig = Buffer.isBuffer(allekirjoitus)
    ? allekirjoitus
    : Buffer.from(allekirjoitus, 'base64');
  return verify(null, data, publicKey, sig);
}

Testataan koko ketju yhdessä: luonti, allekirjoitus, vahvistus ja vielä peukaloidun viestin hylkääminen. Tämä on opettavaisin yksittäinen testi koko oppaassa, koska se osoittaa että allekirjoitus sitoo viestin sisällön. Jos yksikin tavu muuttuu, vahvistus epäonnistuu.

// src/demo.js
import { luoAvainpari } from './generate.js';
import { allekirjoita } from './sign.js';
import { vahvista } from './verify.js';

const { publicKey, privateKey } = luoAvainpari();
const viesti = 'Tilisiirto: 1500 EUR tilille FI21 1234 5600 0007 85';

const sig = allekirjoita(viesti, privateKey);
console.log('Aito viesti vahvistuu:', vahvista(viesti, sig, publicKey));

const vaarennos = 'Tilisiirto: 9999 EUR tilille FI21 1234 5600 0007 85';
console.log('Peukaloitu viesti vahvistuu:', vahvista(vaarennos, sig, publicKey));
$ node src/demo.js
Aito viesti vahvistuu: true
Peukaloitu viesti vahvistuu: false

Juuri tämä on digitaalisen allekirjoituksen koko arvolupaus. Vastaanottaja voi varmistua kahdesta asiasta: viesti tuli sen avaimen haltijalta, jonka julkiseen avaimeen hän luottaa, ja viestiä ei ole muutettu matkalla. Halutessasi syventää allekirjoitusten teoriaa, lue tausta-artikkelimme digitaalisista allekirjoituksista.

Vaihe 6: Komentorivityökalu avainten lataukseen

Yhdistetään edelliset palaset pieneksi komentorivityökaluksi, joka lataa levylle tallennetut avaimet ja allekirjoittaa annetun tiedoston. Tämä on realistinen kuva siitä, miten allekirjoitusta käytetään esimerkiksi julkaisuputkessa tai ohjelmistopakettien allekirjoituksessa.

// src/cli.js
import { readFileSync } from 'fs';
import { luoAvainpari } from './generate.js';
import { tallennaAvaimet, lataaAvaimet } from './storage.js';
import { allekirjoita } from './sign.js';
import { vahvista } from './verify.js';

const komento = process.argv[2];

if (komento === 'keygen') {
  const { publicKey, privateKey } = luoAvainpari();
  tallennaAvaimet(publicKey, privateKey);
  console.log('Avaimet tallennettu kansioon keys/');
} else if (komento === 'sign') {
  const tiedosto = process.argv[3];
  const { privateKey } = lataaAvaimet();
  const sig = allekirjoita(readFileSync(tiedosto), privateKey);
  console.log(sig.toString('base64'));
} else if (komento === 'verify') {
  const tiedosto = process.argv[3];
  const sigB64 = process.argv[4];
  const { publicKey } = lataaAvaimet();
  const ok = vahvista(readFileSync(tiedosto), sigB64, publicKey);
  console.log(ok ? 'KELVOLLINEN' : 'HYLATTY');
  process.exit(ok ? 0 : 1);
} else {
  console.log('Kayttö: node src/cli.js [keygen|sign <tiedosto>|verify <tiedosto> <sig>]');
}

Työnkulku on suoraviivainen. Luo avaimet kerran, allekirjoita tiedostoja tarpeen mukaan ja vahvista ne julkisella avaimella. Huomaa, että verify-komento palauttaa myös oikean poistumiskoodin (0 onnistuessa, 1 epäonnistuessa), joten voit ketjuttaa sen shell-skripteihin ja CI-putkiin.

$ node src/cli.js keygen
Avaimet tallennettu kansioon keys/

$ echo "julkaisu v1.0.0" > release.txt
$ node src/cli.js sign release.txt
8Xk2...lWfQ==   # 64 tavun allekirjoitus base64-muodossa

$ node src/cli.js verify release.txt "8Xk2...lWfQ=="
KELVOLLINEN

Vaihe 7: JWT-tokenit EdDSA-algoritmilla

Yksi suosituimmista Ed25519:n käyttötapauksista on JWT-tokenien allekirjoitus. JOSE-standardissa Ed25519-allekirjoituksen algoritmitunniste on EdDSA. Verrattuna yleiseen HMAC-pohjaiseen HS256-algoritmiin EdDSA on epäsymmetrinen: palvelin allekirjoittaa salaisella avaimella, ja kuka tahansa voi vahvistaa tokenin julkisella avaimella ilman pääsyä salaiseen avaimeen. Tämä on ihanteellista mikropalveluarkkitehtuurissa.

Node.js:n crypto ei tarjoa valmista JWT-kerrosta, joten käytämme tähän moderneja standardeja noudattavaa jose-kirjastoa (versio 5). Asenna se ensin.

npm install jose
// src/jwt.js
import { SignJWT, jwtVerify, generateKeyPair } from 'jose';

const { publicKey, privateKey } = await generateKeyPair('EdDSA', {
  crv: 'Ed25519',
});

// Allekirjoita token
const token = await new SignJWT({ rooli: 'admin', sub: 'kayttaja-42' })
  .setProtectedHeader({ alg: 'EdDSA' })
  .setIssuedAt()
  .setIssuer('shattered.io')
  .setExpirationTime('1h')
  .sign(privateKey);

console.log('Token:', token);

// Vahvista token
const { payload } = await jwtVerify(token, publicKey, {
  issuer: 'shattered.io',
});
console.log('Hyötykuorma:', payload);

Tämä tuottaa JWT:n, jonka otsakkeessa lukee "alg":"EdDSA". Token on lyhyt ja allekirjoitus nopea, mikä on etu suuren liikenteen API:ssa. Jos haluat syventyä JWT-todennuksen kokonaiskuvaan istuntojen, päättymisaikojen ja virkistystokenien kanssa, lue erillinen oppaamme JWT-todennuksesta Node.js:ssä. Tärkein turvasääntö EdDSA-tokeneissa on sama kuin aina: määritä vahvistuksessa sallittu algoritmi eksplisiittisesti, jotta hyökkääjä ei voi vaihtaa sitä esimerkiksi arvoon none.

Vaihe 8: Raakojen avainten ja JWK-muodon käsittely

Joskus tarvitset pelkät 32 raakatavua, esimerkiksi kun integroit järjestelmään, joka odottaa Ed25519-avaimen ilman ASN.1-otsaketta, tai kun siirrät avaimen selaimen WebCrypto-rajapinnalle. Helpoin tapa Node.js:ssä on viedä avain JWK-muotoon (JSON Web Key) ja lukea sieltä x-kenttä, joka sisältää raa’an julkisen avaimen base64url-koodattuna.

// src/raw.js
import { generateKeyPairSync, createPublicKey } from 'crypto';

const { publicKey } = generateKeyPairSync('ed25519');

// Vie JWK-muotoon
const jwk = publicKey.export({ format: 'jwk' });
console.log('JWK:', jwk);
// { crv: 'Ed25519', x: 'xwEQNL...V6o', kty: 'OKP' }

// Pura raaka 32 tavun julkinen avain
const raaka = Buffer.from(jwk.x, 'base64url');
console.log('Raaka julkinen avain:', raaka.length, 'tavua');

// Rakenna KeyObject takaisin raakatavuista JWK:n kautta
const palautettu = createPublicKey({
  key: { kty: 'OKP', crv: 'Ed25519', x: raaka.toString('base64url') },
  format: 'jwk',
});
console.log('Palautus onnistui:', palautettu.asymmetricKeyType);
$ node src/raw.js
JWK: { kty: 'OKP', crv: 'Ed25519', x: 'xwEQNL...V6o' }
Raaka julkinen avain: 32 tavua
Palautus onnistui: ed25519

JWK on myös JOSE-ekosysteemin oletusformaatti, joten tämä sama esitys kelpaa suoraan JWT-vahvistukseen ja avaintenjakelupisteille (JWKS). Kirjaintunniste OKP tarkoittaa “Octet Key Pair”, joka on JOSE:n nimi Edwards-käyrän avaimille. Raakatavuesitys on tiivein mahdollinen ja sopii tilanteisiin, joissa jokainen tavu on kallis, kuten lohkoketjuissa tai sulautetuissa laitteissa.

Vaihe 9: Valmis projekti, tiedostojen eheyden varmistus

Kootaan kaikki yhteen täydeksi, käytännölliseksi projektiksi: työkalu, joka allekirjoittaa kokonaisen hakemiston tiedostot ja tuottaa manifestin, jonka avulla voi myöhemmin todistaa, ettei yksikään tiedosto ole muuttunut. Tämä on sama periaate, jolla Linux-jakelut allekirjoittavat pakettinsa ja jolla ohjelmistojulkaisut suojataan peukalointia vastaan.

// src/manifest.js
import { readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
import { createHash } from 'crypto';
import { lataaAvaimet } from './storage.js';
import { allekirjoita } from './sign.js';
import { vahvista } from './verify.js';

function hajauta(polku) {
  return createHash('sha256').update(readFileSync(polku)).digest('hex');
}

function listaa(hakemisto) {
  return readdirSync(hakemisto)
    .map((nimi) => join(hakemisto, nimi))
    .filter((p) => statSync(p).isFile());
}

export function luoManifesti(kohdeHakemisto) {
  const { privateKey } = lataaAvaimet();
  const tiedostot = listaa(kohdeHakemisto).reduce((acc, p) => {
    acc[p] = hajauta(p);
    return acc;
  }, {});

  const sisalto = JSON.stringify(tiedostot);
  const sig = allekirjoita(sisalto, privateKey).toString('base64');
  writeFileSync('manifest.json', JSON.stringify({ tiedostot, sig }, null, 2));
  console.log(`Allekirjoitettu ${Object.keys(tiedostot).length} tiedostoa`);
}

export function tarkistaManifesti() {
  const { publicKey } = lataaAvaimet();
  const { tiedostot, sig } = JSON.parse(readFileSync('manifest.json'));

  // 1. Vahvista manifestin allekirjoitus
  if (!vahvista(JSON.stringify(tiedostot), sig, publicKey)) {
    throw new Error('Manifestin allekirjoitus on virheellinen');
  }
  // 2. Vertaa jokaisen tiedoston nykyinen hajautus tallennettuun
  for (const [polku, odotettu] of Object.entries(tiedostot)) {
    if (hajauta(polku) !== odotettu) {
      throw new Error(`Tiedosto muuttunut: ${polku}`);
    }
  }
  console.log('Kaikki tiedostot eheitä ja allekirjoitus kelvollinen');
}

Logiikka on kaksitasoinen ja siksi vahva. Ensin se laskee jokaisesta tiedostosta SHA-256-hajautuksen ja kokoaa ne JSON-rakenteeseen. Sitten se allekirjoittaa koko rakenteen Ed25519:llä. Tarkistuksessa allekirjoitus vahvistetaan ensin, mikä takaa että hajautuslista itsessään on aito, ja vasta sitten verrataan kunkin tiedoston nykyistä hajautusta. Yksikin muuttunut tavu missä tahansa tiedostossa tai itse manifestissa johtaa hylkäykseen. SHA-256:n roolista voit lukea lisää SHA-256-artikkelistamme.

Tämä projekti on jo tuotantokelpoinen pohja. Lisäämällä aikaleiman, version ja allekirjoittajan tunnisteen manifestiin saat täyden ohjelmistojulkaisun allekirjoitusjärjestelmän alle 200 koodirivillä, ilman ainuttakaan raskasta riippuvuutta.

Vaihe 10: Webhook-allekirjoitusten vahvistus Expressissä

Yksi yleisimmistä reaalimaailman käyttötapauksista on saapuvien webhookien aitouden tarkistus. Kun ulkoinen palvelu (maksunvälittäjä, GitHub, oma mikropalvelusi) lähettää sinulle HTTP-pyynnön, haluat varmistua että pyyntö tuli aidolta lähettäjältä eikä huijarilta. Lähettäjä allekirjoittaa pyynnön rungon Ed25519-salaisella avaimellaan ja liittää allekirjoituksen otsakkeeseen. Sinä vahvistat sen heidän julkisella avaimellaan.

Tässä on kriittinen yksityiskohta, joka kaataa monta toteutusta: allekirjoitus lasketaan tarkalleen niistä raakatavuista, jotka kulkivat verkossa. Jos jäsennät rungon JSON-objektiksi ja sarjallistat sen uudelleen, tavut muuttuvat (avainjärjestys, välilyönnit) ja vahvistus epäonnistuu. Siksi käytämme express.raw-väliohjelmistoa, joka antaa meille koskemattoman rungon Bufferina.

// src/webhook.js
import express from 'express';
import { verify, createPublicKey } from 'crypto';
import { readFileSync } from 'fs';

const app = express();
// Tarvitsemme RAA'AN rungon, ei jäsenneltyä JSON:ia
app.use(express.raw({ type: '*/*' }));

const lahettajanAvain = createPublicKey(readFileSync('keys/public.pem'));

app.post('/webhook', (req, res) => {
  const otsake = req.get('X-Signature');
  if (!otsake) return res.status(400).send('Allekirjoitus puuttuu');

  const sig = Buffer.from(otsake, 'base64');
  const aito = verify(null, req.body, lahettajanAvain, sig);

  if (!aito) {
    console.warn('Hylätty webhook: virheellinen allekirjoitus');
    return res.status(401).send('Virheellinen allekirjoitus');
  }

  const data = JSON.parse(req.body.toString('utf8'));
  console.log('Aito webhook vastaanotettu:', data.tapahtuma);
  res.status(200).send('OK');
});

app.listen(3000, () => console.log('Webhook-palvelin portissa 3000'));

Vahvistuksen jälkeen, ja vasta sitten, jäsennämme rungon JSON:iksi. Järjestys on tärkeä: älä koskaan luota dataan ennen kuin allekirjoitus on tarkistettu. Tämä malli suojaa kahdelta hyökkäykseltä kerralla. Ensinnäkin huijari ei voi väärentää pyyntöä ilman salaista avainta. Toiseksi, jos joku muuttaa rungon yhtäkin tavua matkalla, vahvistus hylkää sen. Epäsymmetrinen malli on tässä erityisen hyödyllinen: vastaanottajan ei tarvitse jakaa mitään salaisuutta lähettäjän kanssa, vaan pelkkä julkinen avain riittää.

Tuotannossa kannattaa lisätä myös aikaleima allekirjoitettuun runkoon ja hylätä liian vanhat pyynnöt, jotta hyökkääjä ei voi toistaa vanhaa kelvollista pyyntöä (replay-hyökkäys). Aikaleima sisällytetään allekirjoitettuun dataan, jolloin sitä ei voi muuttaa havaitsematta. Tämä on sama suojaperiaate, jota käytetään esimerkiksi maksurajapintojen webhookeissa.

Ed25519 vs HMAC: milloin kumpaakin käytetään

Ed25519 ja HMAC ratkaisevat osin samaa ongelmaa, viestin aitouden ja eheyden, mutta hyvin eri tavoin. Ero on symmetria. HMAC käyttää yhtä jaettua salaista avainta: sama avain sekä luo että vahvistaa tarkistussumman. Ed25519 on epäsymmetrinen: salainen avain allekirjoittaa, julkinen avain vahvistaa. Tämä yksi ero määrää lähes kaiken muun.

OminaisuusEd25519HMAC-SHA256
AvainmalliEpäsymmetrinenSymmetrinen (jaettu salaisuus)
Vahvistaja tarvitseeVain julkisen avaimenSaman salaisen avaimen
Kuka voi luoda allekirjoituksenVain salaisen avaimen haltijaKuka tahansa salaisuuden tietävä
KiistämättömyysKylläEi
Tulosteen koko64 tavua32 tavua
NopeusNopeaErittäin nopea
Paras käyttöJulkinen vahvistus, useat osapuoletSama osapuoli molemmissa päissä

Valinta on käytännössä yksinkertainen. Jos sama luotettu järjestelmä sekä luo että tarkistaa tarkistussumman (esimerkiksi istuntoeväste, jonka oma palvelimesi allekirjoittaa ja tarkistaa), HMAC on nopeampi ja yksinkertaisempi. Jos taas eri osapuolet luovat ja vahvistavat (julkiset webhookit, ohjelmistojulkaisut, hajautetut järjestelmät), Ed25519 on oikea valinta, koska sinun ei tarvitse luovuttaa salaista avainta vahvistajalle. HMAC:n yksityiskohdat löydät HMAC-oppaastamme.

Tärkeä turvaero on kiistämättömyys. Koska HMAC:n salaisuus on jaettu, kumpi tahansa osapuoli on voinut luoda tarkistussumman, joten et voi todistaa kolmannelle osapuolelle kuka sen teki. Ed25519:llä vain salaisen avaimen haltija on voinut allekirjoittaa, joten allekirjoitus on todiste alkuperästä. Tämä on syy siihen, miksi lailliset asiakirjat, ohjelmistopaketit ja lohkoketjutransaktiot käyttävät nimenomaan epäsymmetrisiä allekirjoituksia, ei HMAC:ia.

Ed25519 reaalimaailmassa: SSH, TLS ja lohkoketjut

Kun ymmärrät miten Ed25519 toimii koodissa, kannattaa nähdä missä kohtaamasi järjestelmät jo käyttävät sitä. Tämä auttaa hahmottamaan, miksi algoritmi on niin keskeinen modernissa tietoturvassa.

SSH on selkein esimerkki. Kun luot uuden SSH-avaimen komennolla ssh-keygen -t ed25519, saat täsmälleen saman 32 tavun julkisen avaimen, jonka tämän oppaan koodi tuottaa. OpenSSH on suositellut Ed25519:ää RSA:n sijaan jo vuosia, koska se on nopeampi, lyhyempi ja turvallisempi oletusarvoilla. Jos avaat tiedoston ~/.ssh/id_ed25519.pub, näet saman julkisen avaimen base64-koodattuna. Voit jopa allekirjoittaa tiedostoja SSH-avaimellasi komennolla ssh-keygen -Y sign, mikä käyttää sisäisesti samaa Ed25519-operaatiota kuin crypto.sign.

TLS 1.3 -kättelyssä palvelin voi esittää EdDSA-allekirjoitetun sertifikaatin. Vaikka useimmat julkiset varmenneviranomaiset käyttävät yhä ECDSA:ta tai RSA:ta yhteensopivuussyistä, sisäisissä PKI-järjestelmissä ja modernissa palveluverkossa Ed25519-sertifikaatit yleistyvät. Niiden etu on pieni koko ja nopea kättely, mikä vähentää viivettä jokaisessa yhteydessä. HTTPS:n ja TLS:n kokonaiskuvasta voit lukea HTTPS- ja TLS-artikkelistamme.

Lohkoketjut ovat ehkä näkyvin Ed25519:n käyttöalue. Useat suuret verkot, kuten Solana, Cardano, Stellar ja Monero, käyttävät Ed25519:ää transaktioiden allekirjoitukseen. Syy on sama kuin aina: pieni avain ja allekirjoitus säästävät arvokasta lohkotilaa, deterministisyys poistaa nonce-vuotoriskin, ja vahvistus on nopeaa kun tuhansia transaktioita pitää tarkistaa sekunnissa. Kun siirrät varoja tällaisessa verkossa, lompakkosi tekee tarkalleen saman sign-operaation kuin vaiheessa 4 rakentamasi funktio.

Myös Signal-protokolla, age-salaustyökalu, Tor-verkon uudemmat palvelutunnisteet ja lukuisat pakettienhallintajärjestelmät nojaavat Ed25519:ään. Kun hallitset tämän oppaan koodin, ymmärrät siis suuren osan modernin tietoturvainfrastruktuurin allekirjoituskerroksesta. Sama 64 tavun allekirjoitus suojaa niin SSH-kirjautumistasi, ohjelmistopäivityksiäsi kuin kryptolompakkoasi.

Yleiset sudenkuopat ja miten vältät ne

Ed25519 on yksinkertainen käyttää, mutta muutama virhe toistuu jatkuvasti. Tässä viisi yleisintä sudenkuoppaa ja niiden korjaukset.

1. Ed25519:n käyttö salaukseen. Tämä on ehdottomasti yleisin virhe. Ed25519 tekee vain allekirjoituksia, se ei salaa mitään. Jos haluat salata dataa tai vaihtaa avaimia, käytät X25519:ää (samaa käyrää eri funktiolla) yhdessä symmetrisen salaajan kuten AES-256-GCM:n tai ChaCha20-Poly1305:n kanssa. Älä koskaan yritä “salata” julkisella allekirjoitusavaimella.

2. Ed25519:n ja Curve25519/X25519:n sekoittaminen. Ne pohjautuvat samaan matemaattiseen käyrään, mutta ovat eri tarkoituksiin: Ed25519 allekirjoituksiin, X25519 avaintenvaihtoon. Et voi käyttää Ed25519-avainta avaintenvaihtoon suoraan. Tunnista oikea työkalu nimellä ennen kuin valitset.

3. Hajautusfunktion nimen antaminen sign-funktiolle. Kuten vaiheessa 4 korostettiin, crypto.sign('sha256', ...) heittää virheen Ed25519:lle. Ensimmäisen argumentin on oltava null, koska algoritmi hajauttaa viestin sisäisesti SHA-512:lla.

4. Salaisen avaimen tallennus versionhallintaan. Salainen avain on koko järjestelmän turvallisuuden perusta. Jos se vuotaa Git-historiaan tai lokitiedostoon, hyökkääjä voi väärentää minkä tahansa allekirjoituksen. Lisää keys/ heti .gitignore-tiedostoon ja harkitse salaisen avaimen salasanasuojausta tai KMS-säilytystä.

5. Koodausten sekaannus allekirjoituksessa. Jos allekirjoitat raakatavuista mutta vahvistat merkkijonosta (tai sekoitat base64:n ja heksan), vahvistus epäonnistuu vaikka avaimet olisivat oikein. Päätä yksi koodaus (suosittelen base64:ää tai base64url:ää) ja käytä sitä johdonmukaisesti koko ketjussa. Muista myös, että UTF-8-merkkijono pitää koodata samalla tavalla molemmissa päissä.

Vianmääritys: 8 yleistä virhettä ja ratkaisut

Kun jokin menee pieleen, virheilmoitus on harvoin itsestään selvä. Tämä taulukko kokoaa kahdeksan yleisintä Ed25519-virhettä Node.js:ssä ja niiden korjaukset.

Oire tai virheSyyRatkaisu
ERR_OSSL_EVP_UNSUPPORTEDOpenSSL-versio ei tue Ed25519:ää tai avain on väärässä muodossaPäivitä Node.js 20+ (OpenSSL 3) ja varmista avaimen tyyppi
verify palauttaa aina falseViestin koodaus eroaa allekirjoituksen ja vahvistuksen välilläKäytä samaa Buffer-koodausta (utf8) molemmissa päissä
error:... wrong tag avainta ladatessaYritetään ladata julkista avainta salaisena tai päinvastoinKäytä createPublicKey julkiselle, createPrivateKey salaiselle
sign heittää virheen algoritmistaAnnettu hajautusfunktion nimi nullin sijaanKäytä sign(null, data, key)
ERR_INVALID_ARG_TYPEViesti tai allekirjoitus ei ole Buffer tai TypedArrayMuunna Buffer.from(...) ennen kutsua
JWT-vahvistus epäonnistuu “alg” virheelläSallittua algoritmia ei määritetty tai se on vääräAseta otsake alg: 'EdDSA' ja salli se vahvistuksessa
PEM-tiedosto ei latauduRivinvaihdot tai otsakerivit korruptoituneetVarmista BEGIN/END-rivit ja LF-rivinvaihdot
Salainen avain pyytää salasanaaAvain on salasanasuojattu mutta lataus ei anna salasanaaLisää passphrase-parametri createPrivateKey-kutsuun

Useimmat ongelmat juontavat kahdesta lähteestä: koodausten epäjohdonmukaisuudesta tai avaimen muodon sekaannuksesta. Jos verify palauttaa odottamatta false, tulosta sekä viesti että allekirjoitus heksamuodossa molemmissa päissä ja vertaa ne tavu tavulta. Yhdeksässä tapauksessa kymmenestä ongelma löytyy sieltä. Jos epäilet OpenSSL-tukea, aja node -p process.versions.openssl ja varmista että tulos alkaa numerolla 3.

Salasanasuojattu salainen avain ladataan antamalla passphrase-arvo. Esimerkiksi createPrivateKey({ key: pem, format: 'pem', passphrase: 'salasana' }). Jos saat virheen “bad decrypt”, salasana on väärä. Tämä on yleinen kompastuskivi siirryttäessä suojaamattomista avaimista tuotantokelpoisiin salattuihin avaimiin.

Edistyneet vinkit tuotantokäyttöön

Ed25519ph suurille tiedostoille

Perus-Ed25519 (kutsutaan myös PureEdDSA) lukee koko viestin kahdesti allekirjoituksen aikana. Erittäin suurille tiedostoille tämä voi olla tehotonta. RFC 8032 määrittelee myös esihajautusvariantin Ed25519ph, joka allekirjoittaa viestin hajautuksen koko viestin sijaan. Node.js:n ydin-API ei suoraan paljasta ph-tilaa, joten käytännössä hajautat tiedoston itse SHA-512:lla ja allekirjoitat hajautuksen, tai siirryt libsodiumiin, joka tarjoaa moniosaisen allekirjoitusrajapinnan.

libsodium selaimeen ja eri alustoille

Jos tarvitset saman koodin selaimessa, palvelimella ja mobiilissa, libsodium-wrappers on kypsä ja laajalti auditoitu valinta. Se tarjoaa funktiot crypto_sign_keypair, crypto_sign_detached ja crypto_sign_verify_detached, jotka vastaavat tämän oppaan operaatioita. libsodium käyttää aina raakoja 32- ja 64-tavuisia esityksiä, joten se sopii hyvin yhteen vaiheen 8 raakatavujen kanssa. Node.js:n ydin-crypto on silti suositeltava palvelinpuolella, koska se on riippuvuusvapaa ja OpenSSL-pohjainen.

Avainten kierto ja kvanttisiirtymä

Suunnittele avainten kierto alusta alkaen. Lisää julkiseen avaimeen tunniste (key ID), jotta voit pyörittää useita avaimia rinnakkain ja vanhentaa vanhat hallitusti. Pitkän aikavälin riski on kvanttitietokoneet: Ed25519 ei ole kvanttiturvallinen. NIST julkaisi vuonna 2024 standardin ML-DSA (entinen Dilithium) kvanttiturvalliselle allekirjoitukselle. Vuonna 2026 käytännön suositus on hybridiallekirjoitus, jossa sama data allekirjoitetaan sekä Ed25519:llä että ML-DSA:lla, jolloin järjestelmä on turvallinen niin kauan kuin kumpikaan ei ole murrettu. Lisää aiheesta löydät kryptografian teemasivultamme.

Usein kysytyt kysymykset

Onko Ed25519 turvallisempi kuin RSA?

Käytännössä kyllä, useimmissa skenaarioissa. Ed25519 tarjoaa noin 128 bitin turvatason 32 tavun avaimella, kun RSA-2048 antaa noin 112 bittiä paljon suuremmalla avaimella. Ed25519:n determinismi poistaa myös nonce-vuotoriskin, joka on kaatanut ECDSA-toteutuksia. RSA ei ole rikki, mutta uusiin järjestelmiin Ed25519 on yleensä parempi valinta.

Voiko Ed25519:llä salata dataa?

Ei. Ed25519 tekee vain digitaalisia allekirjoituksia. Salaukseen ja avaintenvaihtoon käytetään X25519:ää, joka pohjautuu samaan käyrään mutta on eri operaatio. Datan salaukseen yhdistät X25519-avaintenvaihdon symmetriseen salaajaan kuten AES-256-GCM:ään.

Mistä Node.js-versiosta Ed25519 on tuettu?

Node.js on tukenut Ed25519:ää natiivisti versiosta 12 lähtien. Vuonna 2026 suosittelemme Node.js 24 LTS:ää, joka käyttää OpenSSL 3:a. Tämän oppaan koodi toimii muuttumattomana myös Node.js 20:ssa ja 22:ssa.

Miksi sama viesti tuottaa aina saman allekirjoituksen?

Tämä on tarkoituksellinen ominaisuus, ei vika. Ed25519 on deterministinen: se johtaa allekirjoituksen sisäisen kertaluvun salaisesta avaimesta ja viestistä eikä satunnaisluvusta. Tämä poistaa satunnaislukugeneraattorin heikkouksiin liittyvät avainvuodot, jotka vaivaavat ECDSA:ta.

Kuinka suuri Ed25519-allekirjoitus on?

Aina tasan 64 tavua, viestin pituudesta riippumatta. Julkinen avain on 32 raakatavua (44 tavua SPKI-DER-muodossa) ja salainen avain 32 raakatavua (48 tavua PKCS#8-DER-muodossa). Pieni koko on yksi Ed25519:n suurimmista eduista.

Onko Ed25519 kvanttiturvallinen?

Ei. Kuten kaikki elliptisen käyrän ja RSA-pohjaiset skeemat, Ed25519 olisi haavoittuvainen riittävän tehokkaalle kvanttitietokoneelle, joka ajaa Shorin algoritmia. Tämä ei ole akuutti uhka vuonna 2026, mutta pitkän aikavälin järjestelmissä kannattaa suunnitella hybridiratkaisu NIST:n standardoiman ML-DSA:n kanssa.

Tarvitsenko ulkoisen kirjaston Ed25519:lle Node.js:ssä?

Et perusoperaatioihin. Node.js:n sisäänrakennettu crypto-moduuli hoitaa avainten luonnin, allekirjoituksen ja vahvistuksen ilman riippuvuuksia. Ulkoista kirjastoa kuten jose tarvitset vain JWT-tokeneihin ja libsodium-wrappers jos haluat saman koodin selaimeen tai esihajautustilan.

Mitä eroa on Ed25519:llä ja Ed448:lla?

Molemmat ovat EdDSA-variantteja RFC 8032:ssa. Ed25519 tarjoaa noin 128 bitin turvatason 32 tavun avaimella ja 64 tavun allekirjoituksella, kun Ed448 nostaa turvatason noin 224 bittiin suuremmilla 57 tavun avaimilla ja 114 tavun allekirjoituksilla. Käytännössä lähes kaikki käyttävät Ed25519:ää, koska sen turvataso riittää reilusti ja se on nopeampi ja tiiviimpi. Ed448 on harvinainen erikoistapaus erittäin pitkäikäisille avaimille.

Voinko vahvistaa Node.js:ssä luodun allekirjoituksen selaimessa?

Kyllä. Selaimen WebCrypto-rajapinta tukee Ed25519:ää moderneissa selaimissa, ja voit tuoda julkisen avaimen JWK-muodossa vaiheen 8 mukaisesti. Vaihtoehtoisesti libsodium-wrappers toimii identtisesti sekä Node.js:ssä että selaimessa. Allekirjoitus ja avain ovat samat tavut alustasta riippumatta, kunhan käytät samaa koodausta.

Aiheeseen liittyvää

Ulkoiset lähteet

Päivitetty 16. kesäkuuta 2026. Kaikki koodiesimerkit testattu Node.js 20:lla ja toimivat myös Node.js 24 LTS:llä.