BLAKE3 on vuonna 2020 julkaistu hajautusalgoritmi, joka tuottaa jopa 3,5 kertaa suuremman suorituskyvyn kuin SHA-256 x86_64-arkkitehtuurilla. Algoritmilla on puumainen rinnakkaisrakenne, 128 bitin törmäyskestävyys ja laajennettava tulospiituus, joten se sopii erinomaisesti tiedostojen tarkistussummiin, viestiautentikointikoodeihin ja avainten johdannaisiin. Tässä oppaassa asennat blake3-paketin version 3.0.0, kirjoitat 10 toimivaa koodiesimerkkiä ja opit tyypillisimmät sudenkuopat tuotantokäytössä.

Mikä on BLAKE3 ja miksi se korvaa SHA-256:n monissa käyttötapauksissa

BLAKE3 syntyi BLAKE2:n jatkoksi. Jean-Philippe Aumasson, Samuel Neves, Zooko Wilcox-O’Hearn ja Jack O’Connor julkaisivat algoritmin tammikuussa 2020. Suunnittelun lähtökohta oli yhdistää BLAKE2:n nopeus Merkle-puurakenteeseen, joka mahdollistaa rinnakkaistamisen useilla prosessoriytimillä ja SIMD-vektoriyksiköillä.

Käytännön vaikutus on merkittävä. Viralliset vertailumittaukset x86_64 EPYC-prosessorilla osoittavat BLAKE3:n saavuttavan 6 121 MB/s 10 megatavun syötteellä, kun SHA-256 yltää 1 772 MB/s ja SHA-512 2 560 MB/s. Pienemmällä 64 tavun syötteellä ero on pienempi: BLAKE3 tuottaa 1 069 MB/s vastaan SHA-256:n 860 MB/s. Arm64-arkkitehtuurilla SHA-256 on paikoin nopeampi, koska prosessoreissa on usein laitteistokiihdytys SHA-laskennalle, mutta x86_64-palvelimilla BLAKE3 voittaa ylivoimaisesti suurilla syötteillä.

Turvallisuusominaisuudet vastaavat SHA-256:ta: törmäyskestävyys on 128 bittiä, esikuvakestävyys 256 bittiä ja toisen esikuvan kestävyys 256 bittiä. BLAKE3 tukee myös kolmea erikoistilaa, joita SHA-256 ei tarjoa suoraan: avaimellinen hajautus (keyed hash), avainten johdannainen (KDF) ja laajennettu tuloste (XOF). Solana-lohkoketju käyttää BLAKE3:a tilihajautuksiin, mikä osoittaa algoritmin kelpoisuuden tuotantojärjestelmissä.

Node.js:n sisäänrakennettu crypto-moduuli ei tue BLAKE3:a edes versiossa 22. Siksi tarvitset kolmannen osapuolen paketin. Kaksi suosituinta vaihtoehtoa ovat blake3 (npm, versio 3.0.0, käyttää WebAssemblyä ja natiivia sidontaa) ja @noble/hashes (versio 2.2.0, puhdas TypeScript-toteutus, nolla riippuvuutta, tietoturva-auditoitu). Tässä oppaassa käytetään molempia, mutta pääpaino on blake3-paketissa.

BLAKE3 vs SHA-256 vs SHA-512: suorituskyky ja turvallisuus

Ennen koodiin sukeltamista on hyödyllistä katsoa algoritmien ominaisuuksia rinnakkain. Alla oleva taulukko kokoaa viralliset spesifikaatiot ja vertailumittaukset.

AlgoritmiTulospiituusTörmäyskestävyysRinnakkaistettavissaNopeus x86_64, 10 Mt (MB/s)Nopeus x86_64, 64 B (MB/s)
BLAKE3Muuttuva (oletus 256 bit)128 bitKyllä (Merkle-puu)6 1211 069
SHA-256256 bit128 bitEi1 772860
SHA-512512 bit256 bitEi2 5601 000
SHA3-512512 bit256 bitEiHitaampi kuin SHA-512Hitaampi kuin SHA-512
BLAKE2bMuuttuva (maks. 512 bit)256 bitRajoitetusti~3 000~900

SHA-256 ja SHA-512 ovat tarpeellisia protokollissa, joissa standardi on pakollinen, kuten TLS-sertifikaateissa tai digitaalisissa allekirjoituksissa, koska ne noudattavat FIPS-standardeja. BLAKE3 sopii paremmin suorituskykykriittiseen sisäiseen käyttöön: tiedostojen eheyden tarkistamiseen, sisällön osoitteistamiseen (content-addressable storage) ja nopeaan MAC-laskentaan.

Esitietovaatimukset

Ennen aloittamista varmista, että kehitysympäristössäsi on seuraavat komponentit:

  • Node.js 20.x LTS tai uudempi (blake3-paketti vaatii vähintään Node.js 16, mutta 20.x on suositeltava pitkäaikaistuki)
  • npm 10.x tai yarn 4.x pakettienhallintaan
  • Python 3.x node-gyp-kääntäjää varten (tarvitaan, jos blake3:n natiivi sidos ladataan)
  • C++-kääntäjä: GCC 12+ Linuxilla, Clang 14+ macOS:lla, Visual C++ 2019+ Windowsilla
  • Perustiedot Node.js:stä ja async/await-syntaksista
  • Ymmärrys hajautusfunktioiden käytöstä (suositeltava: lue ensin SHA-256-artikkeli)

Tarkista Node.js-versio ennen aloittamista:

node --version
# v20.14.0 tai uudempi
npm --version
# 10.7.0 tai uudempi

Vaihe 1: Projektin rakenne ja alustus

Luo uusi hakemisto ja alusta npm-projekti. ESM-moduulijärjestelmä (ES Modules) on suositeltava, koska blake3 3.0.0 tukee sitä täysimääräisesti ja CommonJS-yhteensopivuus on rajatumpaa.

mkdir blake3-demo && cd blake3-demo
npm init -y

# Lisää "type": "module" package.json-tiedostoon
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
pkg.type = 'module';
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
console.log('package.json päivitetty: type=module');
"

mkdir src test

Projektin lopullinen hakemistorakenne tulee näyttämään tältä:

blake3-demo/
├── package.json
├── src/
│   ├── basic-hash.mjs
│   ├── file-hash.mjs
│   ├── keyed-hash.mjs
│   ├── kdf.mjs
│   ├── xof.mjs
│   ├── cas.mjs
│   └── benchmark.mjs
└── test/
    └── hash.test.mjs

Vaihe 2: blake3-paketin asennus ja vahvistus

Asenna blake3-paketti tarkalla versionumerolla. Paketti lataa ensin natiivisen sidonnan node-gyp:n avulla. Jos kääntäminen epäonnistuu, se palaa automaattisesti WebAssembly-toteutukseen.

npm install [email protected]

# Tarkista asennus ja API
node --input-type=module << 'EOF'
import blake3 from 'blake3';
console.log('blake3 versio 3.0.0 ladattu');
console.log('Saatavilla olevat funktiot:', Object.keys(blake3).join(', '));
EOF

Onnistuneen asennuksen jälkeen terminaalissa näkyy:

blake3 versio 3.0.0 ladattu
Saatavilla olevat funktiot: hash, createHash, keyedHash, createKeyedHash, derive, createDeriveKey

Jos natiivinen sidonta käännetään onnistuneesti, BLAKE3 käyttää C++-toteutusta AVX2- tai AVX512-vektoriohjeilla ja saavuttaa täyden suorituskyvyn. WebAssembly-varajärjestelmä on noin 30 prosenttia hitaampi kuin natiivitoteutus, joten tuotantopalvelimella kannattaa varmistaa, että kääntäjäriippuvuudet ovat kunnossa ennen käyttöönottoa.

Vaihe 3: Perustiivisteen laskeminen

BLAKE3:n yksinkertaisin käyttötapaus on tiivisteen laskeminen merkkijonosta tai Buffer-objektista. Luo tiedosto src/basic-hash.mjs:

import { hash } from 'blake3';

// Merkkijonon tiiviste, eksplisiittinen UTF-8-enkoodaus
const viesti = 'Hei, maailma!';
const tiiviste = hash(Buffer.from(viesti, 'utf8'));
console.log('Tiiviste (hex):', tiiviste.toString('hex'));
// 256-bittinen (32 tavua) heksadesimaalimerkkijono

// Uint8Array toimii myös suoraan
const data = new TextEncoder().encode('Hei, maailma!');
const tiiviste2 = hash(data);
console.log('Sama tulos:', tiiviste.toString('hex') === tiiviste2.toString('hex'));
// true - molemmat tuottavat saman tiivisteen

// Pidempi tuloste: 512 bittiä (64 tavua)
const pitkaTiiviste = hash(Buffer.from(viesti, 'utf8'), { length: 64 });
console.log('512-bittinen:', pitkaTiiviste.toString('hex'));
console.log('Pituus:', pitkaTiiviste.length, 'tavua');

Aja skripti komennolla node src/basic-hash.mjs. Odotettu tuloste:

Tiiviste (hex): a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3
Sama tulos: true
512-bittinen: a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3...
Pituus: 64 tavua

hash()-funktio on synkroninen. Se ottaa syötteekseen Buffer:n, Uint8Array:n tai string:n. Oletustulospiituus on 32 tavua (256 bittiä), mutta length-optiolla sen voi asettaa haluamaansa kokoon. Tärkeää: eksplisiittinen UTF-8-enkoodaus estää yllätykset eri alustoilla.

Vaihe 4: Suurten tiedostojen hajautus stream-rajapinnalla

Suurten tiedostojen tiivisteen laskeminen kerralla muistiin ladattuna on tehotonta ja voi kaataa sovelluksen muistin loppuessa. BLAKE3:n createHash() tuottaa Node.js-yhteensopivan Writable stream -objektin, jolla tiedoston voi käsitellä paloina.

Luo src/file-hash.mjs:

import { createHash } from 'blake3';
import { createReadStream } from 'fs';
import { pipeline } from 'stream/promises';

async function laskeTiedostoTiiviste(polku, puskuriKoko = 64 * 1024) {
  const hasher = createHash();
  const lukija = createReadStream(polku, { highWaterMark: puskuriKoko });
  await pipeline(lukija, hasher);
  return hasher.digest('hex');
}

// Laske tämän skriptin oma tiiviste
const tiiviste = await laskeTiedostoTiiviste('./src/file-hash.mjs');
console.log('Tiedoston BLAKE3-tiiviste:', tiiviste);

// Vertaa suoritusaikaa eri puskurikoilla
for (const koko of [4096, 65536, 1024 * 1024]) {
  const alku = performance.now();
  await laskeTiedostoTiiviste('/dev/zero', koko);
  const aika = performance.now() - alku;
  console.log(`Puskuri ${koko} B: ${aika.toFixed(1)} ms`);
}

Stream-rajapinta on välttämätön gigatasoisissa tiedostoissa. 64 kilotavun puskurikoko (highWaterMark: 64 * 1024) on yleinen optimum, joka tasapainottaa I/O-pyyntöjen määrän ja muistin käytön. Isompi puskuri ei välttämättä nopeuta laskentaa, koska pullonkaula on usein levyn I/O, ei itse hajautusfunktio.

Vaihe 5: Avaimellinen hajautus viestien autentikoinnissa

BLAKE3:n avaimellinen hajautustila toimii MAC-koodina (Message Authentication Code) ilman HMAC-rakennetta. SHA-256-pohjaisessa HMAC:ssa algoritmi ajetaan kahdesti (katso HMAC Node.js:ssä -opas), mutta BLAKE3:n keyed hash tuottaa saman turvallisuustason yhdellä läpäisyllä. Tulos: noin puolet laskenta-ajasta.

Luo src/keyed-hash.mjs:

import { keyedHash } from 'blake3';
import { randomBytes, timingSafeEqual } from 'crypto';

// Avain täytyy olla tarkalleen 32 tavua (256 bittiä)
const avain = randomBytes(32);
const viesti = Buffer.from('Maksutieto: 100 EUR asiakkaalle #4421', 'utf8');

// Laske MAC
const mac = keyedHash(avain, viesti);
console.log('MAC (hex):', mac.toString('hex'));

// Todenna viesti oikealla avaimella
const tarkistus = keyedHash(avain, viesti);
const onValidi = timingSafeEqual(mac, tarkistus);
console.log('Todentaminen onnistui:', onValidi); // true

// Väärä avain tuottaa eri MAC:n
const vaaraAvain = randomBytes(32);
const vaaraTarkistus = keyedHash(vaaraAvain, viesti);
const onVaara = timingSafeEqual(mac, vaaraTarkistus);
console.log('Väärä avain hylätty:', !onVaara); // true

// Käytännön API-esimerkki: webhook-allekirjoitusten todentaminen
function todennaWebhook(avain, runko, allekirjoitus) {
  const laskettu = keyedHash(avain, Buffer.from(runko, 'utf8'));
  const odotettu = Buffer.from(allekirjoitus, 'hex');
  if (laskettu.length !== odotettu.length) return false;
  return timingSafeEqual(laskettu, odotettu);
}

const webhookRunko = JSON.stringify({ tapahtuma: 'maksu', summa: 100 });
const webhookAllekirjoitus = keyedHash(avain, Buffer.from(webhookRunko)).toString('hex');
console.log('Webhook validi:', todennaWebhook(avain, webhookRunko, webhookAllekirjoitus));

Avaimellinen hajautus on parempi vaihtoehto kuin yksinkertainen tiiviste salaisuuden lisäämiseen (ns. "secret prefix" tai "secret suffix" -menetelmät). Rakenteessa hash(salaisuus + viesti) pituuslaajennushyökkäys on mahdollinen SHA-256:n kanssa. BLAKE3:n keyed hash on suunniteltu välttämään tämä ongelma alusta asti.

Vaihe 6: Avainten johdannainen (Key Derivation Function)

BLAKE3:n KDF-tila mahdollistaa useiden avainten johtamisen yhdestä pääavaimesta. Tämä on välttämätöntä, kun samasta salaisuudesta tarvitaan erillisiä avaimia eri käyttötarkoituksiin, kuten salaukseen ja autentikointiin. Luo src/kdf.mjs:

import { derive } from 'blake3';
import { randomBytes } from 'crypto';

// Pääavain (esim. DH-avaintenvaihdon tulos tai session secret)
const paaSalaisuus = randomBytes(32);

// Kontekstimerkintä identifioi käyttötarkoituksen yksilöllisesti.
// Sen täytyy pysyä muuttumattomana koko sovelluksen elinkaaren ajan.
const SALAUS_KTX = 'blake3-demo 2026-06-17 aes256-gcm salausavain v1';
const HMAC_KTX   = 'blake3-demo 2026-06-17 hmac-sha256 autentikointi v1';

// Johda kaksi erillistä avainta samasta pääsalaisuudesta
const salausAvain = derive(SALAUS_KTX, paaSalaisuus);
const authAvain   = derive(HMAC_KTX,   paaSalaisuus);

console.log('Salausavain:', salausAvain.toString('hex'));
console.log('Authavain:  ', authAvain.toString('hex'));
console.log('Avaimet eroavat:', salausAvain.toString('hex') !== authAvain.toString('hex'));
// true - sama pääsalaisuus, eri kontekstit, eri avaimet

// Johda 64-tavuinen avain AES-256:lle ja IV:lle kerralla
const laajaAvain = derive(SALAUS_KTX, paaSalaisuus, { length: 64 });
const aesAvain = laajaAvain.slice(0, 32);  // 256-bittinen AES-avain
const iv       = laajaAvain.slice(32, 48); // 128-bittinen IV

console.log('AES-avain:', aesAvain.toString('hex'));
console.log('IV:', iv.toString('hex'));
console.log('Pituudet:', aesAvain.length, 'ja', iv.length, 'tavua');

Kontekstimerkintä on kriittinen turvallisuuden kannalta. Hyväksi käytännöksi on vakiintunut sisällyttää sovelluksen nimi, päivämäärä, käyttötarkoitus ja versionumero. Muuta kontekstia vain, jos haluat tarkoituksellisesti tehdä vanhoista johdetuista avaimista käyttökelvottomia.

Vaihe 7: Laajennettu tuloste (XOF)

BLAKE3 toimii myös XOF-funktiona (eXtendable Output Function), jolloin se tuottaa mielivaltaisen pitkän deterministisen tulosteen. Tämä sopii pseudosatunnaistiedon generointiin tai lookup-taulukoiden rakentamiseen. Luo src/xof.mjs:

import { hash } from 'blake3';

const siemen = Buffer.from('deterministinen siemen 2026', 'utf8');

// Tuota 128 tavua (1024 bittiä) laajennettua tulostetta
const xof128 = hash(siemen, { length: 128 });
console.log('XOF 128 tavua:', xof128.toString('hex'));
console.log('Pituus:', xof128.length, 'tavua');

// Ensimmäiset 32 tavua vastaavat oletustiivistettä
const perus = hash(siemen);
console.log('Alkaa oletustiivisteellä:',
  Buffer.compare(perus, xof128.slice(0, 32)) === 0); // true

// Käyttötapaus: generoi deterministinen S-laatikko kryptografiaan
function generoiSLaatikko(avain, koko = 256) {
  const data = hash(Buffer.from(avain, 'utf8'), { length: koko });
  return Array.from(data);
}

const slaatikko = generoiSLaatikko('sovellus-v1-slaatikko', 256);
console.log('S-laatikko (ensimmäiset 8 arvoa):', slaatikko.slice(0, 8));
console.log('S-laatikko koko:', slaatikko.length);

Vaihe 8: Suorituskykymittaus ja vertailu

Mitataan BLAKE3:n todellinen suorituskyky kehitysympäristössäsi ja verrataan sitä Node.js:n sisäänrakennettuun SHA-256:een. Luo src/benchmark.mjs:

import { hash } from 'blake3';
import { createHash } from 'crypto';

function mittaa(nimi, fn, iteraatiot, dataKoko) {
  // Lämmittelyvaihe estää JIT-kääntäjän vinouttamisen
  for (let i = 0; i < 200; i++) fn();
  const alku = performance.now();
  for (let i = 0; i < iteraatiot; i++) fn();
  const ms = performance.now() - alku;
  const mbps = (dataKoko * iteraatiot) / (ms / 1000) / (1024 * 1024);
  return { nimi, mbps: mbps.toFixed(0) };
}

const KOOT = [
  { koko: 64,            iter: 50000, label: '64 B' },
  { koko: 1024,          iter: 20000, label: '1 kt' },
  { koko: 65536,         iter: 2000,  label: '64 kt' },
  { koko: 1024 * 1024,   iter: 200,   label: '1 Mt' },
];

console.log('Algoritmi     | 64 B    | 1 kt    | 64 kt   | 1 Mt');
console.log('--------------|---------|---------|---------|--------');

const blake3Tulokset = [];
const sha256Tulokset = [];

for (const { koko, iter, label } of KOOT) {
  const data = Buffer.allocUnsafe(koko).fill(0x42);
  blake3Tulokset.push(mittaa('BLAKE3', () => hash(data), iter, koko));
  sha256Tulokset.push(mittaa('SHA-256', () => createHash('sha256').update(data).digest(), iter, koko));
}

console.log('BLAKE3       ', blake3Tulokset.map(r => r.mbps.padStart(7) + ' MB/s').join(' | '));
console.log('SHA-256      ', sha256Tulokset.map(r => r.mbps.padStart(7) + ' MB/s').join(' | '));

const suhde = blake3Tulokset.map((b, i) =>
  (parseInt(b.mbps) / parseInt(sha256Tulokset[i].mbps)).toFixed(1) + 'x'
);
console.log('Suhde         ', suhde.map(s => s.padStart(7)).join('       | '));

Tyypilliset tulokset modernilla x86_64-palvelimella Node.js 20.x:llä natiivisidonnalla:

Algoritmi64 B1 kt64 kt1 Mt
BLAKE3~950 MB/s~2 800 MB/s~5 100 MB/s~5 800 MB/s
SHA-256~820 MB/s~1 400 MB/s~1 700 MB/s~1 700 MB/s
Suhde1,2x2,0x3,0x3,4x

Suorituskykyetu kasvaa syötteen koon mukana, koska BLAKE3:n Merkle-puurakenne hyödyntää prosessorin välimuistihierarkiaa paremmin kuin SHA-256:n peräkkäinen kompressiorakenne. Pienillä syötteillä ero on vähäinen, suurilla (yli 64 kt) BLAKE3 on 3 kertaa nopeampi.

Vaihe 9: Content-addressable storage tiedostovarastona

Content-addressable storage (CAS) tallentaa tiedostot niiden tiivisteen perusteella nimettyinä. Git käyttää vastaavaa rakennetta SHA-256:n kanssa. BLAKE3 on ihanteellinen CAS:iin suuren nopeutensa ansiosta. Luo src/cas.mjs:

import { hash } from 'blake3';
import { writeFile, readFile, mkdir, access } from 'fs/promises';
import { join } from 'path';
import { constants } from 'fs';

const VARASTO = './blake3-cas';

async function tallenna(data) {
  const tiiviste = hash(Buffer.isBuffer(data) ? data : Buffer.from(data)).toString('hex');
  const hakemisto = join(VARASTO, tiiviste.slice(0, 2));
  const polku = join(hakemisto, tiiviste);

  // Älä kirjoita uudelleen, jos jo olemassa (sisältö identtinen tiivisteen perusteella)
  try {
    await access(polku, constants.F_OK);
    return tiiviste; // Jo tallennettu
  } catch { /* ei olemassa, jatka */ }

  await mkdir(hakemisto, { recursive: true });
  await writeFile(polku, Buffer.isBuffer(data) ? data : Buffer.from(data));
  return tiiviste;
}

async function hae(tiiviste) {
  const polku = join(VARASTO, tiiviste.slice(0, 2), tiiviste);
  const data = await readFile(polku);
  // Todenna eheys latauksen jälkeen
  const tarkistus = hash(data).toString('hex');
  if (tarkistus !== tiiviste) {
    throw new Error(`Eheysvirhe: odotettu ${tiiviste}, laskettu ${tarkistus}`);
  }
  return data;
}

// Testi
const osoite = await tallenna('Tärkeä dokumentti 2026');
console.log('Tallennettu:', osoite);

const haettu = await hae(osoite);
console.log('Haettu:', haettu.toString('utf8'));

// Sama sisältö tallennetaan vain kerran
const osoite2 = await tallenna('Tärkeä dokumentti 2026');
console.log('Sama osoite:', osoite === osoite2); // true

Vaihe 10: Yksikkötestit ja CI/CD-integrointi

Node.js 20.x:ssä on sisäänrakennettu testikehys. Luo test/hash.test.mjs:

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { hash, keyedHash, derive, createHash } from 'blake3';

describe('BLAKE3 perustiiviste', () => {
  it('tuottaa 32-tavuisen oletustulosteen', () => {
    const tulos = hash(Buffer.from('testi', 'utf8'));
    assert.equal(tulos.length, 32);
  });

  it('tuottaa muuttuvan pituisen tulosteen', () => {
    const tulos = hash(Buffer.from('testi', 'utf8'), { length: 64 });
    assert.equal(tulos.length, 64);
  });

  it('sama syöte tuottaa aina saman tiivisteen', () => {
    const syote = Buffer.from('deterministinen', 'utf8');
    assert.equal(hash(syote).toString('hex'), hash(syote).toString('hex'));
  });

  it('eri syötteet tuottavat eri tiivisteet', () => {
    assert.notEqual(
      hash(Buffer.from('a', 'utf8')).toString('hex'),
      hash(Buffer.from('b', 'utf8')).toString('hex')
    );
  });

  it('tyhjä syöte toimii', () => {
    const tulos = hash(Buffer.alloc(0));
    assert.equal(tulos.length, 32);
  });
});

describe('BLAKE3 stream-rajapinta', () => {
  it('tuottaa saman tuloksen kuin suora hash()', () => {
    const data = Buffer.from('stream-testi', 'utf8');
    const hasher = createHash();
    hasher.update(data);
    const streamTulos = hasher.digest('hex');
    const suoraTulos = hash(data).toString('hex');
    assert.equal(streamTulos, suoraTulos);
  });

  it('useat update()-kutsut toimivat', () => {
    const hasher = createHash();
    hasher.update(Buffer.from('osa1', 'utf8'));
    hasher.update(Buffer.from('osa2', 'utf8'));
    const streamTulos = hasher.digest('hex');
    const suoraTulos = hash(Buffer.from('osa1osa2', 'utf8')).toString('hex');
    assert.equal(streamTulos, suoraTulos);
  });
});

describe('BLAKE3 avaimellinen hajautus', () => {
  it('vaatii tarkalleen 32-tavuisen avaimen', () => {
    const avain = Buffer.alloc(32, 0x01);
    const mac = keyedHash(avain, Buffer.from('viesti', 'utf8'));
    assert.equal(mac.length, 32);
  });

  it('eri avain tuottaa eri MAC:n', () => {
    const viesti = Buffer.from('sama viesti', 'utf8');
    const mac1 = keyedHash(Buffer.alloc(32, 0x01), viesti).toString('hex');
    const mac2 = keyedHash(Buffer.alloc(32, 0x02), viesti).toString('hex');
    assert.notEqual(mac1, mac2);
  });
});

describe('BLAKE3 avainten johdannainen', () => {
  it('eri konteksti tuottaa eri avaimen', () => {
    const materiaali = Buffer.alloc(32, 0xFF);
    const k1 = derive('konteksti-A', materiaali).toString('hex');
    const k2 = derive('konteksti-B', materiaali).toString('hex');
    assert.notEqual(k1, k2);
  });

  it('sama konteksti ja materiaali tuottavat saman avaimen', () => {
    const materiaali = Buffer.alloc(32, 0xAB);
    const k1 = derive('sama-konteksti', materiaali).toString('hex');
    const k2 = derive('sama-konteksti', materiaali).toString('hex');
    assert.equal(k1, k2);
  });
});

Aja testit Node.js:n omalla testikehyksellä:

node --test test/hash.test.mjs

# Odotettu tuloste:
# ✔ BLAKE3 perustiiviste > tuottaa 32-tavuisen oletustulosteen (1.23ms)
# ✔ BLAKE3 perustiiviste > tuottaa muuttuvan pituisen tulosteen (0.45ms)
# ✔ BLAKE3 perustiiviste > sama syöte tuottaa aina saman tiivisteen (0.12ms)
# ✔ BLAKE3 perustiiviste > eri syötteet tuottavat eri tiivisteet (0.11ms)
# ✔ BLAKE3 perustiiviste > tyhjä syöte toimii (0.09ms)
# ✔ BLAKE3 stream-rajapinta > tuottaa saman tuloksen kuin suora hash() (0.18ms)
# ✔ BLAKE3 stream-rajapinta > useat update()-kutsut toimivat (0.14ms)
# ✔ BLAKE3 avaimellinen hajautus > vaatii tarkalleen 32-tavuisen avaimen (0.11ms)
# ✔ BLAKE3 avaimellinen hajautus > eri avain tuottaa eri MAC:n (0.13ms)
# ✔ BLAKE3 avainten johdannainen > eri konteksti tuottaa eri avaimen (0.10ms)
# ✔ BLAKE3 avainten johdannainen > sama konteksti ja materiaali tuottavat saman avaimen (0.09ms)
# ℹ tests 11
# ℹ pass 11
# ℹ fail 0

Lisää testit GitHub Actions -putkeen (.github/workflows/ci.yml):

name: BLAKE3 CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [20.x, 22.x]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: node --test test/hash.test.mjs

5 yleistä virhettä BLAKE3:n käytössä Node.js:ssä

Seuraavat virheet toistuvat usein kehittäjien ottaessa BLAKE3:a käyttöön ensimmäistä kertaa.

Virhe 1: Merkkijonon suora syöttäminen ilman enkoodausta. blake3-paketti hyväksyy merkkijonot, mutta enkoodaus voi vaihdella kontekstin mukaan. Turvallisin tapa on aina muuntaa Buffer.from(str, 'utf8')-muotoon. Ilman eksplisiittistä enkoodausta eri alustojen välillä voi syntyä tiivistepoikkeamia, joita on vaikea debugata.

Virhe 2: Väärä avaimen pituus keyedHash()-funktiossa. Avaimellinen hajautus vaatii tarkalleen 32 tavua. Liian lyhyt tai pitkä avain aiheuttaa poikkeuksen key must be 32 bytes. Yleinen sudenkuoppa on käyttää salasanaa tai muuta muuttuvan pituisen merkkijonon suoraan avaimena:

// VÄÄRIN: salasana ei ole 32 tavua
const avain = Buffer.from('salasana', 'utf8'); // vain 8 tavua
keyedHash(avain, data); // Heittää virheen!

// OIKEIN: johda 32-tavuinen avain salasanasta
import { scryptSync } from 'crypto';
const avain32 = scryptSync('salasana', 'suola', 32);
keyedHash(avain32, data); // Toimii

Virhe 3: Timing-safe vertailun laiminlyönti. Tiivisteen vertailu ===-operaattorilla on altis ajoitushyökkäyksille, koska JavaScript palaa heti ensimmäisestä poikkeavasta merkistä. Käytä aina crypto.timingSafeEqual():

// VÄÄRIN: vuotaa tietoa ajoituksen kautta
if (laskettuMac.toString('hex') === asiakkaanMac) { ... }

// OIKEIN: vakioaikainen vertailu
import { timingSafeEqual } from 'crypto';
if (timingSafeEqual(laskettuMac, Buffer.from(asiakkaanMac, 'hex'))) { ... }

Virhe 4: createHash()-objektin uudelleenkäyttö digest()-kutsun jälkeen. Kun hasher.digest() kutsutaan, objekti merkitään päättyneeksi. Uusi hasher.update() sen jälkeen ei tuota oikeaa tulosta. Luo aina uusi instanssi jokaista erillistä hajautusta varten.

Virhe 5: BLAKE3:n käyttö salasanojen tiivistykseen. BLAKE3 on suunniteltu nopeudelle, mikä on täsmälleen päinvastainen ominaisuus kuin mitä salasanojen suojauksessa tarvitaan. Nopea hajautusfunktio tekee brute-force-hyökkäyksestä tehokkaampaa. Salasanoihin käytä aina tarkoitukseen suunniteltua KDF:ää: Argon2id (suositeltavin 2026), bcrypt tai scrypt.

Vianmääritys: 8 yleistä ongelmaa ratkaisuineen

Ongelma 1: node-gyp kääntäminen epäonnistuu Windowsilla. Virheviesti: gyp ERR! build error. Ratkaisu: asenna Visual Studio Build Tools 2019 tai uudempi C++-työkuormalla. Jos kääntäjää ei voi asentaa, käytä npm install [email protected] --ignore-scripts, jolloin paketti käyttää WebAssembly-toteutusta.

Ongelma 2: ESM-yhteensopivuusvirhe CommonJS-ympäristössä. Virheviesti: SyntaxError: Cannot use import statement in a CommonJS module. Ratkaisu: lisää "type": "module" package.json:iin tai käytä dynaamista importia: const { hash } = await import('blake3').

Ongelma 3: Suorituskyky on odotettua heikompi. Tarkista, onko natiivinen sidonta aktiiivinen vai WASM-varajärjestelmä käytössä. Jos npm rebuild blake3 ei auta, asenna ensin node-gyp globaalisti: npm install -g node-gyp, ja varmista Python 3 ja C++-kääntäjä.

Ongelma 4: Eri alustojen tuottamat tiivisteet eroavat. BLAKE3 on täysin deterministinen. Jos saat eri tiivisteen samasta syötteestä eri alustoilla, syy on lähes aina enkoodauserot. Tarkista, että syöte on Buffer-objekti, ei merkkijono.

Ongelma 5: Muistivuoto pitkäaikaisessa server-käytössä. createHash()-objekteja ei saa jättää roikkumaan. Pipeline-käytössä varmista pipeline()-funktion oikea käyttö ja that stream sulkeutuu virheen sattuessa. Lisää try/finally-rakenne kriittisiin kohtiin.

Ongelma 6: derive()-funktio tuottaa eri tulokset eri paketin versioissa. Blake3 3.0.0 teki murtavan muutoksen. Lukitse versio tarkasti: "blake3": "3.0.0" (ei ^3.0.0). Käytä npm ci CI/CD-putkessa, ei npm install, jotta versio pysyy kiinnitettynä.

Ongelma 7: TypeScript-tyypit eivät löydy. Varmista, että tsconfig.json:ssa on "moduleResolution": "bundler" tai "node16". Vaihtoehto: käytä @noble/hashes-pakettia, joka on kirjoitettu alun perin TypeScriptillä ja jonka tyypit ovat täydelliset.

Ongelma 8: Testit epäonnistuvat rinnakkaisessa ajossa. createHash()-instanssi ei ole säievarmistettu. Jokaisessa testissä täytyy olla oma instanssi. Jos testit jakavat yhteistä tilaa epähuomiossa, lisää beforeEach-koukku, joka luo uuden hasher-objektin.

Edistyneet tekniikat tuotantokäyttöön

Worker Threads rinnakkaistamiseen

Node.js:n yksisäikeinen tapahtumasilmukka rajoittaa BLAKE3:n Merkle-puun rinnakkaistamisen hyötyä. Worker Threads -laajennuksella voit jakaa suuren tiedoston paloihin ja käsitellä ne rinnakkaisesti. Käytännöllisin jako: jaetaan tiedosto 1 Mt:n segmentteihin, lasketaan segmenttikohtaiset tiivisteet erillisessä säikeessä ja yhdistetään koko tiedoston tiiviste pääsäikeessä. Tämä on hyödyllistä, kun tiedostoja on useita kymmeniä prosessoitavana yhtä aikaa.

@noble/hashes vaihtoehtona serverless-ympäristöissä

Jos natiivinen sidonta ei ole mahdollinen, esimerkiksi Cloudflare Workers tai AWS Lambda -ympäristöissä, @noble/hashes versio 2.2.0 on paras vaihtoehto. Se on tietoturva-auditoitu, kirjoitettu puhtaasti TypeScriptillä ja toimii ilman build-riippuvuuksia:

import { blake3 } from '@noble/hashes/blake3';
import { bytesToHex } from '@noble/hashes/utils';

// Perustiiviste
const tiiviste = blake3(new TextEncoder().encode('Hei Suomi'));
console.log(bytesToHex(tiiviste));

// Avaimellinen hajautus
const avain = new Uint8Array(32).fill(1);
const mac = blake3(new TextEncoder().encode('viesti'), { key: avain });

// Avainten johdannainen
const johdettu = blake3(new TextEncoder().encode('materiaali'), {
  context: 'sovellus 2026 avainjohdannainen v1'
});
console.log('Johdettu avain:', bytesToHex(johdettu));

Sama koodirakenne toimii selaimessa, Deno:ssa, Bun:issa ja Node.js:ssä. Ed25519-allekirjoituksiin (katso Ed25519 Node.js:ssä -opas) noble-kirjasto tarjoaa identtisen lähestymistavan auditoidun koodin kanssa.

Webhook-allekirjoitusten varmentaminen tuotannossa

BLAKE3:n avaimellinen hajautus on nopein tapa varmentaa webhook-allekirjoituksia, kuten Stripe- tai GitHub-webhookeja, jos molemmat osapuolet käyttävät BLAKE3:a. Käytännössä useimmat palvelut lähettävät HMAC-SHA256-allekirjoituksen, joten vertailu tapahtuu crypto.createHmac('sha256', avain)-funktiolla. BLAKE3-MAC sopii parhaiten sovelluksen sisäisiin rajapintoihin, joissa molemmat päät ovat hallinnassasi.

Milloin BLAKE3 ei ole oikea valinta

BLAKE3 ei sovi kaikkiin tilanteisiin. Seuraavat käyttötapaukset vaativat muun algoritmin:

  • TLS-sertifikaatit ja X.509: Sertifikaattiviranomaisten allekirjoitukset käyttävät SHA-256 tai SHA-384:ä. BLAKE3 ei ole FIPS-hyväksytty eikä CA/Browser Forumin sallima.
  • Salasanojen tiivistäminen: BLAKE3 on liian nopea suojaamaan salasanoja. Käytä Argon2id:tä tai bcrypt:ä, jotka on suunniteltu hidastamaan brute-force-hyökkäyksiä.
  • FIPS 140-2/3 -ympäristöt: BLAKE3 ei kuulu NIST:n hyväksymiin algoritmeihin. Käytä SHA-2 tai SHA-3 -perheen algoritmeja.
  • Standardin velvoittamat digitaaliset allekirjoitukset: RSA-PKCS1 ja ECDSA käyttävät SHA-256:ta tai SHA-512:a standardin mukaan. BLAKE3-pohjainen allekirjoitusjärjestelmä ei ole yhteensopiva vakioprotokollien kanssa.
  • Vanha järjestelmäintegraatio: Jos toinen järjestelmä odottaa SHA-256-tiivistettä, BLAKE3 ei ole suoraan korvattavissa ilman molempien päiden muuttamista.

Usein kysytyt kysymykset

Onko BLAKE3 turvallinen tuotantokäyttöön vuonna 2026? Kyllä. BLAKE3:a on analysoitu kryptografiyhteisön toimesta vuodesta 2020, eikä merkittäviä haavoittuvuuksia ole löydetty. Solana käyttää algoritmia tuotannossa tilihajautuksiin. Se ei kuitenkaan ole FIPS-hyväksytty, joten FIPS-ympäristöihin se ei sovi.

Voinko korvata SHA-256:n BLAKE3:lla kaikessa koodissani? Ei kaikessa. Protokollapohjaisessa koodissa (JWT, TLS, S3-allekirjoitukset) SHA-256 on pakollinen standardi. Sovelluksen sisäisessä logiikassa BLAKE3 on usein parempi valinta nopeuden ja monipuolisuutensa ansiosta.

Mitä eroa on blake3-paketilla ja @noble/hashes-paketilla? blake3-paketti käyttää natiivista C++-sidontaa tai WebAssemblyä, mikä tekee siitä nopeamman erityisesti suurilla syötteillä. @noble/hashes on puhdas TypeScript-toteutus, joka on tietoturva-auditoitu ja toimii ilman build-riippuvuuksia. Palvelimilla blake3 on yleensä nopeampi, serverless-ympäristöissä @noble/hashes on käytännöllisempi.

Kuinka suuri suorituskykyero on käytännössä Node.js:ssä? Natiivisidonnalla BLAKE3 on noin 3,4 kertaa nopeampi kuin SHA-256 megatavun syötteillä x86_64-arkkitehtuurilla. Alle 1 kilotavun syötteillä ero on 20 prosenttia. WASM-varajärjestelmällä ero kutistuu merkittävästi.

Toimiiko BLAKE3 selainympäristössä? Kyllä. blake3-paketti sisältää WASM-sidonnan, joka toimii selaimessa. @noble/hashes toimii myös suoraan ilman WASM:a. Voit jakaa saman hajautuskoodin Node.js:n ja selaimen välillä.

Mitä tapahtuu, jos muutan kontekstia derive()-kutsussa tuotannossa? Kontekstin muuttaminen tuottaa täysin eri avaimet. Kaikki aiemmin johdetut avaimet muuttuvat, mikä tekee tallennetusta salatusta datasta purettamatonta. Kontekstimerkintä täytyy olla muuttumaton koko sovelluksen elinkaaren ajan.

Voiko BLAKE3:a käyttää satunnaistiedon korvaajana? BLAKE3 KDF sopii avainten johdannaiseen deterministisestä materiaalista, kuten DH-avaintenvaihdon tuloksesta. Se ei korvaa kryptografisesti turvallista satunnaislukugeneraattoria (CSPRNG). Käytä crypto.randomBytes(), kun avain tarvitaan täysin satunnaisena.

Miten BLAKE3 vertautuu SHA-3:een turvallisuudessa? Turvallisuusominaisuudet ovat käytännössä samat 256-bittisellä tulospiituudella: 128 bitin törmäyskestävyys molemmissa. SHA-3 perustuu Keccak-sponge-rakenteeseen, BLAKE3 Merkle-puuhun. SHA-3 on NIST-standardisoitu ja FIPS-hyväksytty, BLAKE3 on nopeampi mutta ei standardisoitu.

Onko BLAKE3:lle olemassa referenssitoteutus muille kielille? Kyllä. Viralliset toteutukset löytyvät Rustille, C:lle, Pythonille, Javalle ja Go:lle osoitteesta github.com/BLAKE3-team/BLAKE3. Yhteensopivuus on taattu: sama syöte tuottaa saman tiivisteen kaikissa virallisjulkaistuissa toteutuksissa.

Aiheeseen liittyvät artikkelit

Aiheeseen liittyvä sisältö

Lisätietoja BLAKE3:n virallisesta spesifikaatiosta löytyy osoitteesta blake3.io ja GitHub-repositoriosta BLAKE3-team/BLAKE3. Node.js:n crypto-moduulin dokumentaatio käsittelee sisäänrakennetut hajautusalgoritmit, ja MDN Web Docs selittää hajautusfunktioiden yleisen toimintaperiaatteen.