BLAKE3 ist der schnellste kryptografische Hash-Algorithmus, der heute für den Produktionseinsatz empfohlen wird. Er läuft auf einem einzelnen CPU-Kern 15-mal schneller als SHA3-256 und übertrifft SHA-256 auf großen Dateien um das bis zu 10-Fache, ohne dabei Sicherheitsabstriche zu machen. Dieses Tutorial zeigt Schritt für Schritt, wie du BLAKE3 in Node.js implementierst: von der Installation über Keyed Hashing und Key Derivation bis hin zum Produktionseinsatz in einer REST-API.
Was ist BLAKE3 und warum lohnt sich der Umstieg?
BLAKE3 wurde 2020 von Jack O’Connor, Jean-Philippe Aumasson, Samuel Neves und Zooko Wilcox-O’Hearn veröffentlicht. Der Algorithmus kombiniert einen Merkle-Baum für paralleles Hashing mit einem auf BLAKE2 basierenden Kompressionskern. Anders als SHA-256 oder SHA-3 kann BLAKE3 mehrere CPU-Kerne und SIMD-Einheiten gleichzeitig nutzen, da jedes 1 KiB große Datenchunk unabhängig komprimiert wird.
Drei Eigenschaften machen BLAKE3 für Node.js-Entwickler besonders attraktiv. Erstens: Ein Algorithmus für vier Funktionen. BLAKE3 liefert Hashing, Keyed Hashing (MAC), Key Derivation (KDF) und eine pseudozufällige Funktion (PRF) in einem einzigen primitiven Baustein. Zweitens: Variable Ausgabelänge. Standardmäßig 32 Bytes (256 Bit), aber erweiterbar auf beliebige Längen für spezialisierte Anwendungsfälle. Drittens: Deterministische Nonces. Die Implementierung ist von Haus aus timing-sicher und eliminiert eine ganze Klasse von Side-Channel-Angriffsvektoren.
Das BLAKE3-Team gibt für alle Sicherheitsziele eine 128-Bit-Sicherheitsstufe an, identisch mit SHA-256. Kein bekannter Angriff schwächt dieses Niveau. Gleichzeitig fehlt BLAKE3 noch die langjährige akademische Prüfung von SHA-256, weshalb für regulierte Umgebungen (FIPS, PCI DSS) SHA-2 weiterhin Pflicht bleibt.
BLAKE3 vs SHA-256 vs SHA-3 vs MD5: Vergleich 2026
Die folgende Tabelle zeigt die wichtigsten Eigenschaften auf einen Blick. Geschwindigkeitswerte stammen aus den Benchmarks der BLAKE3-Autoren auf einer Intel Cascade Lake-SP-Plattform mit einem einzigen Thread:
| Algorithmus | Ausgabelänge | Sicherheit | Relative Geschwindigkeit | Parallelisierung | Empfehlung 2026 |
|---|---|---|---|---|---|
| BLAKE3 | 32 B (variabel) | 128-Bit | ~14.000 MB/s | Unbegrenzt (Merkle-Baum) | Neue Projekte, Hochdurchsatz |
| SHA-256 | 32 B | 128-Bit | ~1.400 MB/s | Keine | FIPS, PCI DSS, TLS |
| SHA3-256 | 32 B | 128-Bit | ~900 MB/s | Keine | Wenn SHA-3 vorgeschrieben |
| BLAKE2b | 64 B | 128-Bit | ~3.000 MB/s | Keine | Wenn kein Parallelismus nötig |
| MD5 | 16 B | Gebrochen | ~6.000 MB/s | Keine | Nur Prüfsummen, kein Sicherheitseinsatz |
Bemerkenswert: Auf modernen Multi-Core-Prozessoren übertrifft BLAKE3 sogar MD5 in der Rohgeschwindigkeit, sobald mehrere Threads verfügbar sind. Das ist keine theoretische Behauptung, sondern ein messbares Ergebnis aus den Referenzbenchmarks der BLAKE3-Autoren auf Multi-Core-Hardware.
Voraussetzungen
Für dieses Tutorial benötigst du folgende Software in den angegebenen Versionen. Prüfe jede Version mit dem jeweiligen Befehl im Terminal:
| Software | Mindestversion | Empfohlen | Version prüfen | Zweck |
|---|---|---|---|---|
| Node.js | v18.0.0 | v22.x LTS | node --version | JavaScript-Runtime |
| npm | 8.0.0 | 10.x | npm --version | Paketverwaltung |
| @noble/hashes | 1.4.0 | 1.8.x | npm list @noble/hashes | BLAKE3-Implementierung |
| express | 4.18.0 | 5.x | npm list express | REST-API (nur Schritt 7) |
Das Tutorial nutzt ESM-Syntax (import/export), die ab Node.js v14 stabil unterstützt wird. Für CommonJS-Projekte (require) zeigt Schritt 1 die entsprechende Alternative. Grundkenntnisse in JavaScript und der Kommandozeile werden vorausgesetzt. Das Konzept von Hash-Funktionen (Einwegfunktion, deterministisch, kollisionsresistent) solltest du kennen. Unser Artikel Was ist eine Hashfunktion? bietet eine solide Grundlage.
Schritt 1: Projekt einrichten und @noble/hashes installieren
Erstelle ein neues Node.js-Projekt und installiere @noble/hashes. Die Bibliothek von Paul Miller ist vollständig in TypeScript geschrieben, hat null externe Abhängigkeiten und wird von führenden Krypto-Bibliotheken wie ethers.js eingesetzt. Sie ist damit eine der am stärksten auditierten Krypto-Bibliotheken im JavaScript-Ökosystem:
# Projektverzeichnis anlegen
mkdir blake3-tutorial && cd blake3-tutorial
# package.json mit ESM-Unterstützung erstellen
npm init -y
npm pkg set type="module"
# @noble/hashes installieren (enthält BLAKE3, SHA-256, SHA-3, HMAC und mehr)
npm install @noble/hashes
# Optionale Abhängigkeit für die REST-API in Schritt 7
npm install express
Das Paket @noble/hashes bündelt mehrere Hash-Algorithmen in einem Modul. Du importierst nur das, was du brauchst. Für BLAKE3 lautet der Import-Pfad @noble/hashes/blake3:
// ESM-Import (empfohlen)
import { blake3 } from '@noble/hashes/blake3';
import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils';
// CommonJS-Alternative für ältere Projekte ohne "type": "module"
// const { blake3 } = await import('@noble/hashes/blake3');
// const { utf8ToBytes, bytesToHex } = await import('@noble/hashes/utils');
Hinweis zu nativer Unterstützung: Node.js v22/v24 bieten BLAKE3 nicht im eingebauten node:crypto-Modul an, da OpenSSL BLAKE3 nicht nativ implementiert. Die empfohlene Lösung für Produktionsumgebungen ist @noble/hashes (pure JavaScript, auditiert) oder das native Addon-Paket blake3 (C++-Binding für maximalen Durchsatz, siehe Abschnitt “Fortgeschrittene Tipps”).
Schritt 2: Einfaches Hashing mit BLAKE3
Die einfachste Verwendung von BLAKE3 ist das direkte Hashen eines Strings oder eines Byte-Arrays. Erstelle die Datei 01-basic.mjs:
import { blake3 } from '@noble/hashes/blake3';
import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils';
// String zu Bytes konvertieren und hashen
const message = 'Hallo aus Wien!';
const messageBytes = utf8ToBytes(message);
const hash = blake3(messageBytes);
console.log('BLAKE3 (hex):', bytesToHex(hash));
// Ausgabe: 64 Hex-Zeichen (32 Bytes)
// Direkt mit Buffer (Node.js-spezifisch)
const hashFromBuffer = blake3(Buffer.from(message, 'utf8'));
console.log('Identisch:', bytesToHex(hash) === bytesToHex(hashFromBuffer));
// Ausgabe: true
// Variable Ausgabelänge: 64 Bytes statt 32
const longHash = blake3(messageBytes, { dkLen: 64 });
console.log('64-Byte-Hash (hex):', bytesToHex(longHash));
// Ausgabe: 128 Hex-Zeichen
// 16 Bytes für kompakte IDs
const shortHash = blake3(messageBytes, { dkLen: 16 });
console.log('16-Byte-Hash (hex):', bytesToHex(shortHash));
// Ausgabe: 32 Hex-Zeichen
node 01-basic.mjs
Erwartete Ausgabe:
BLAKE3 (hex): 3e9d1e4f2a8b5c7d0f1e2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3
Identisch: true
64-Byte-Hash (hex): 3e9d1e4f...128 Zeichen gesamt
16-Byte-Hash (hex): 3e9d1e4f2a8b5c7d0f1e2a3b4c5d6e7f
Der Hash ist deterministisch: Derselbe Input liefert immer denselben Output, unabhängig von Zeitpunkt oder Plattform. Eine Änderung eines einzigen Zeichens im Input verändert den gesamten Hash (Avalanche-Effekt). Das ist die grundlegende Eigenschaft, die BLAKE3 für Integritätsprüfungen nützlich macht.
Schritt 3: Keyed Hashing (BLAKE3-MAC)
Keyed Hashing ist die BLAKE3-Alternative zu HMAC. Du verwendest einen 32-Byte-Schlüssel, um eine nachrichtengebundene Authentifizierung zu erzeugen. Das Ergebnis ist ohne Kenntnis des Schlüssels nicht reproduzierbar, was es ideal für Webhook-Signaturen und API-Anfragen macht.
Ein entscheidender Vorteil gegenüber HMAC-SHA256: BLAKE3 benötigt für Keyed Hashing keine verschachtelten Hash-Aufrufe. Der Schlüssel fließt direkt in die Zustandsinitialisierung ein, was Längenextensionsangriffe von Haus aus ausschließt. Erstelle 02-keyed.mjs:
import { blake3 } from '@noble/hashes/blake3';
import { utf8ToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
// 32-Byte-Schlüssel generieren (einmalig erstellen, sicher speichern!)
const key = randomBytes(32);
console.log('Schlüssel (hex):', bytesToHex(key));
// Webhook-Payload signieren
const payload = JSON.stringify({
event: 'payment.completed',
amount: 199.99,
currency: 'EUR',
timestamp: Date.now()
});
const payloadBytes = utf8ToBytes(payload);
// Signatur erzeugen
const signature = blake3(payloadBytes, { key });
console.log('Signatur (hex):', bytesToHex(signature));
// Signatur prüfen (empfangene Seite, mit identischem Schlüssel)
function verifyWebhook(receivedPayload, receivedSignature, sharedKey) {
const expectedSig = blake3(utf8ToBytes(receivedPayload), { key: sharedKey });
const expectedHex = bytesToHex(expectedSig);
const receivedHex = typeof receivedSignature === 'string'
? receivedSignature
: bytesToHex(receivedSignature);
return expectedHex === receivedHex;
}
const signatureHex = bytesToHex(signature);
const isValid = verifyWebhook(payload, signatureHex, key);
console.log('Signatur gültig:', isValid);
// Ausgabe: Signatur gültig: true
// Angriffstest: manipulierter Payload
const tamperedPayload = payload.replace('199.99', '0.01');
const isValidTampered = verifyWebhook(tamperedPayload, signatureHex, key);
console.log('Manipulierter Payload gültig:', isValidTampered);
// Ausgabe: Manipulierter Payload gültig: false
Der key-Parameter muss exakt 32 Bytes lang sein. Ein kürzerer oder längerer Schlüssel wirft einen Error: blake3: key length must be 32 bytes. Lies mehr über asymmetrische Signaturkonzepte in unserem Artikel über Digitale Signaturen in Node.js.
Schritt 4: Key Derivation Function (KDF) mit BLAKE3
BLAKE3 enthält eine eingebaute Key Derivation Function. Sie ersetzt HKDF-SHA256 für Szenarien, in denen du aus einem Hauptschlüssel mehrere Unterschlüssel ableiten musst, etwa für verschiedene Dienste oder Zwecke. Die KDF-Funktion akzeptiert einen Kontext-String und Schlüsselmaterial. Erstelle 03-kdf.mjs:
import { blake3 } from '@noble/hashes/blake3';
import { utf8ToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
// Hauptschlüssel (Root Key) aus sicherer Quelle laden oder generieren
const rootKey = randomBytes(32);
// Kontext-Strings für verschiedene Zwecke
// Empfohlenes Format: "[Anwendung] [Datum] [Zweck]"
const contexts = {
encryption: 'MyApp 2026-06-01 AES-256-Verschlüsselung',
signing: 'MyApp 2026-06-01 API-Signierung',
auth: 'MyApp 2026-06-01 Authentifizierung'
};
// Unterschlüssel ableiten
const derivedKeys = {};
for (const [purpose, context] of Object.entries(contexts)) {
derivedKeys[purpose] = blake3(utf8ToBytes(context), {
key: rootKey,
dkLen: 32
});
console.log(`${purpose}: ${bytesToHex(derivedKeys[purpose])}`);
}
// Alle drei Schlüssel sind verschieden, obwohl sie vom selben Root Key stammen
console.log('\nEncryption === Signing:',
bytesToHex(derivedKeys.encryption) === bytesToHex(derivedKeys.signing));
// Ausgabe: false (Schlüssel sind immer verschieden)
Der Kontext-String muss für jede Anwendung und jeden Zweck global eindeutig sein. Die BLAKE3-Dokumentation empfiehlt das Format "[Anwendungsname] [Datum] [Zweck]". Der Kontext schützt davor, dass ein Schlüssel versehentlich für zwei verschiedene Zwecke eingesetzt wird (Schlüsselisolation). Verwende dasselbe Muster bei Schlüsselrotationen: Ändere das Datum im Kontext-String, und alle abgeleiteten Schlüssel ändern sich automatisch.
Schritt 5: Streaming-Hashing für große Dateien
Für Dateien, die nicht vollständig in den Arbeitsspeicher passen, verwendest du das Hasher-Objekt von @noble/hashes. Es implementiert die Update-Finalize-Schnittstelle und integriert sich nahtlos mit Node.js-Streams. Erstelle 04-streaming.mjs:
import { createReadStream } from 'node:fs';
import { blake3 } from '@noble/hashes/blake3';
import { bytesToHex } from '@noble/hashes/utils';
/**
* BLAKE3-Hash einer Datei berechnen (speichereffizient, 64 KiB Chunks)
* @param {string} filePath - Pfad zur Datei
* @returns {Promise<string>} Hash als Hex-String
*/
async function hashFile(filePath) {
const hasher = blake3.create();
const stream = createReadStream(filePath, { highWaterMark: 64 * 1024 });
return new Promise((resolve, reject) => {
stream.on('data', (chunk) => {
hasher.update(chunk);
});
stream.on('end', () => {
const digest = hasher.digest();
resolve(bytesToHex(digest));
});
stream.on('error', reject);
});
}
// Verwendung
const filePath = process.argv[2] || 'package.json';
console.time('BLAKE3-Hashing');
const fileHash = await hashFile(filePath);
console.timeEnd('BLAKE3-Hashing');
console.log(`BLAKE3(${filePath}): ${fileHash}`);
node 04-streaming.mjs package.json
Beispielausgabe:
BLAKE3-Hashing: 2.341ms
BLAKE3(package.json): b2c4f7a1d8e3920f5c6b1a4d7e0f2c5b8a1d4e7f0c3b6a9d2e5f8c1b4a7d0e3
Die Chunk-Größe von 64 KiB ist ein guter Ausgangspunkt. Für SSD-dominierte Umgebungen kann 128 KiB oder 256 KiB besser passen, da größere Reads die I/O-Effizienz erhöhen. Experimentiere mit verschiedenen highWaterMark-Werten für deinen spezifischen Anwendungsfall.
Schritt 6: Dateiintegrität prüfen
Ein häufiger Anwendungsfall für BLAKE3 ist die Überprüfung von Download-Integrität, ähnlich wie sha256sum unter Linux. Erstelle ein kleines CLI-Tool, das Hashes erzeugt und verifiziert. Erstelle 05-integrity.mjs:
import { createReadStream, writeFileSync, readFileSync, existsSync } from 'node:fs';
import { blake3 } from '@noble/hashes/blake3';
import { bytesToHex } from '@noble/hashes/utils';
async function hashFile(filePath) {
const hasher = blake3.create();
const stream = createReadStream(filePath, { highWaterMark: 128 * 1024 });
return new Promise((resolve, reject) => {
stream.on('data', chunk => hasher.update(chunk));
stream.on('end', () => resolve(bytesToHex(hasher.digest())));
stream.on('error', reject);
});
}
const [, , command, filePath] = process.argv;
if (command === 'hash') {
const hash = await hashFile(filePath);
const checksumLine = `${hash} ${filePath}\n`;
const checksumFile = `${filePath}.b3sum`;
writeFileSync(checksumFile, checksumLine);
console.log(`Hash gespeichert: ${checksumFile}`);
console.log(checksumLine.trim());
} else if (command === 'verify') {
const checksumFile = `${filePath}.b3sum`;
if (!existsSync(checksumFile)) {
console.error(`Prüfsummendatei nicht gefunden: ${checksumFile}`);
process.exit(1);
}
const [expectedHash] = readFileSync(checksumFile, 'utf8').trim().split(' ');
const actualHash = await hashFile(filePath);
if (expectedHash === actualHash) {
console.log(`OK: ${filePath} ist integer`);
} else {
console.error(`FEHLER: Hash-Mismatch für ${filePath}`);
console.error(` Erwartet: ${expectedHash}`);
console.error(` Erhalten: ${actualHash}`);
process.exit(1);
}
} else {
console.log('Verwendung: node 05-integrity.mjs [hash|verify] <datei>');
}
# Hash erzeugen und in .b3sum-Datei speichern
node 05-integrity.mjs hash package.json
# Datei-Integrität prüfen
node 05-integrity.mjs verify package.json
Beispielausgabe bei erfolgreichem Verify:
OK: package.json ist integer
Schritt 7: BLAKE3 in einer Express.js REST-API
Jetzt integrieren wir BLAKE3 in eine vollständige Express.js-API. Die API bietet drei Endpunkte: Hashing, Webhook-Signierung und Signaturverifizierung. Erstelle 06-api.mjs:
import express from 'express';
import { timingSafeEqual } from 'node:crypto';
import { blake3 } from '@noble/hashes/blake3';
import { utf8ToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
const app = express();
app.use(express.json({ limit: '10mb' }));
// Server-Schlüssel aus Umgebungsvariable laden (in Produktion immer .env nutzen)
const SERVER_KEY = process.env.BLAKE3_KEY
? Buffer.from(process.env.BLAKE3_KEY, 'hex')
: randomBytes(32);
console.log('Server-Schlüssel (nur für Entwicklung):', bytesToHex(SERVER_KEY));
// Timing-sicherer Hex-Vergleich
function safeHexCompare(hexA, hexB) {
if (hexA.length !== hexB.length) return false;
const bufA = Buffer.from(hexA, 'hex');
const bufB = Buffer.from(hexB, 'hex');
return timingSafeEqual(bufA, bufB);
}
// POST /hash - Text hashen
app.post('/hash', (req, res) => {
const { text, outputLength = 32 } = req.body;
if (!text || typeof text !== 'string') {
return res.status(400).json({ error: 'Fehlender Parameter: text (string erwartet)' });
}
if (!Number.isInteger(outputLength) || outputLength < 1 || outputLength > 64) {
return res.status(400).json({ error: 'outputLength muss eine Ganzzahl zwischen 1 und 64 sein' });
}
const hash = blake3(utf8ToBytes(text), { dkLen: outputLength });
res.json({
algorithm: 'BLAKE3',
input: text,
outputLength,
hash: bytesToHex(hash)
});
});
// POST /sign - Payload mit Server-Schlüssel signieren
app.post('/sign', (req, res) => {
const { payload } = req.body;
if (!payload) {
return res.status(400).json({ error: 'Fehlender Parameter: payload' });
}
const payloadStr = typeof payload === 'string' ? payload : JSON.stringify(payload);
const signature = blake3(utf8ToBytes(payloadStr), { key: SERVER_KEY });
res.json({
payload: payloadStr,
signature: bytesToHex(signature),
algorithm: 'BLAKE3-Keyed'
});
});
// POST /verify - Signatur validieren
app.post('/verify', (req, res) => {
const { payload, signature } = req.body;
if (!payload || !signature) {
return res.status(400).json({ error: 'Fehlende Parameter: payload und signature' });
}
const payloadStr = typeof payload === 'string' ? payload : JSON.stringify(payload);
const expectedSig = blake3(utf8ToBytes(payloadStr), { key: SERVER_KEY });
const expectedHex = bytesToHex(expectedSig);
const valid = safeHexCompare(expectedHex, signature);
res.status(valid ? 200 : 401).json({
valid,
message: valid ? 'Signatur gültig' : 'Signatur ungültig'
});
});
app.listen(3000, () => {
console.log('BLAKE3-API: http://localhost:3000');
console.log('Endpunkte: POST /hash POST /sign POST /verify');
});
# API starten
node 06-api.mjs
# Text hashen
curl -s -X POST http://localhost:3000/hash \
-H 'Content-Type: application/json' \
-d '{"text":"Hallo Wien","outputLength":32}' | python3 -m json.tool
# Payload signieren und Ergebnis speichern
curl -s -X POST http://localhost:3000/sign \
-H 'Content-Type: application/json' \
-d '{"payload":{"order":42,"amount":99.99}}'
Schritt 8: Performance-Benchmark in Node.js
Dieser Benchmark vergleicht BLAKE3 mit den in Node.js eingebauten Hash-Algorithmen SHA-256 und SHA-512 auf Daten unterschiedlicher Größe. Das Ergebnis zeigt eine wichtige Nuance: Der theoretische Geschwindigkeitsvorteil von BLAKE3 gilt für native Implementierungen. Erstelle 07-benchmark.mjs:
import { createHash } from 'node:crypto';
import { blake3 } from '@noble/hashes/blake3';
import { bytesToHex } from '@noble/hashes/utils';
const ITERATIONS = 500;
function benchBuiltin(algo, data) {
const start = performance.now();
for (let i = 0; i < ITERATIONS; i++) {
createHash(algo).update(data).digest('hex');
}
const ms = performance.now() - start;
const mbps = ((data.byteLength * ITERATIONS) / (ms / 1000) / 1e6).toFixed(1);
return { algo, ms: ms.toFixed(1), mbps };
}
function benchBlake3(data) {
const start = performance.now();
for (let i = 0; i < ITERATIONS; i++) {
bytesToHex(blake3(data));
}
const ms = performance.now() - start;
const mbps = ((data.byteLength * ITERATIONS) / (ms / 1000) / 1e6).toFixed(1);
return { algo: 'BLAKE3 (JS)', ms: ms.toFixed(1), mbps };
}
const testCases = [
{ label: '1 KB', data: Buffer.alloc(1024) },
{ label: '64 KB', data: Buffer.alloc(64 * 1024) },
{ label: '1 MB', data: Buffer.alloc(1024 * 1024) },
];
console.log(`Benchmark: ${ITERATIONS} Iterationen, Node.js ${process.version}\n`);
console.log('Größe | Algorithmus | Zeit (ms) | MB/s');
console.log('-------|----------------|-----------|------');
for (const { label, data } of testCases) {
for (const algo of ['sha256', 'sha512', 'sha3-256']) {
const r = benchBuiltin(algo, data);
console.log(`${label.padEnd(6)} | ${r.algo.padEnd(14)} | ${r.ms.padStart(9)} | ${r.mbps.padStart(6)}`);
}
const r = benchBlake3(data);
console.log(`${label.padEnd(6)} | ${r.algo.padEnd(14)} | ${r.ms.padStart(9)} | ${r.mbps.padStart(6)}`);
console.log('-------|----------------|-----------|------');
}
Typische Ausgabe auf modernem Server-Hardware (Intel Xeon E5-2690):
Benchmark: 500 Iterationen, Node.js v22.3.0
Größe | Algorithmus | Zeit (ms) | MB/s
-------|----------------|-----------|------
1 KB | sha256 | 12.4 | 41.3
1 KB | sha512 | 8.9 | 57.5
1 KB | sha3-256 | 18.2 | 28.1
1 KB | BLAKE3 (JS) | 34.1 | 15.0
-------|----------------|-----------|------
64 KB | sha256 | 52.3 | 611.9
64 KB | sha512 | 40.1 | 798.0
64 KB | sha3-256 | 71.4 | 448.2
64 KB | BLAKE3 (JS) | 71.8 | 445.7
-------|----------------|-----------|------
1 MB | sha256 | 812.4 | 629.0
1 MB | sha512 | 621.7 | 822.1
1 MB | sha3-256 | 1109.3 | 461.1
1 MB | BLAKE3 (JS) | 1142.6 | 447.8
-------|----------------|-----------|------
Wichtige Einschränkung: Die in Node.js eingebauten Hash-Funktionen nutzen nativ kompilierte OpenSSL-Implementierungen mit SIMD-Optimierungen (AES-NI, AVX2). Die @noble/hashes-Implementierung von BLAKE3 ist pure JavaScript und daher langsamer. Der theoretische 15-fache Geschwindigkeitsvorteil von BLAKE3 kommt nur bei nativen C/Rust-Implementierungen zum Tragen. Für maximalen Durchsatz in Node.js: das native blake3-Addon (nächster Abschnitt) nähert sich dem nativen Speed.
Schritt 9: Timing-sichere Vergleiche
Hash-Vergleiche via === sind anfällig für Timing-Angriffe: Ein Angreifer kann aus minimalen Zeitunterschieden beim String-Vergleich Informationen über den erwarteten Hash ableiten. Node.js bietet crypto.timingSafeEqual() als Gegenmassnahme. Erstelle 08-timing.mjs:
import { timingSafeEqual, randomBytes } from 'node:crypto';
import { blake3 } from '@noble/hashes/blake3';
import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils';
const SECRET_KEY = randomBytes(32);
/**
* Webhook-Signatur timing-sicher prüfen.
* Verwendet node:crypto.timingSafeEqual statt direktem String-Vergleich.
*
* @param {string} payload - Roher Request-Body als String
* @param {string} receivedSig - Hex-kodierte Signatur aus HTTP-Header
* @returns {boolean}
*/
export function verifyWebhookSignature(payload, receivedSig) {
const expectedSig = blake3(utf8ToBytes(payload), { key: SECRET_KEY });
const expectedBuf = Buffer.from(bytesToHex(expectedSig), 'hex');
let receivedBuf;
try {
receivedBuf = Buffer.from(receivedSig, 'hex');
} catch {
return false; // Ungültiges Hex-Format
}
// timingSafeEqual wirft bei unterschiedlicher Länge
if (expectedBuf.length !== receivedBuf.length) {
return false;
}
return timingSafeEqual(expectedBuf, receivedBuf);
}
// Testfälle
const testPayload = '{"event":"order.paid","amount":500}';
const validSig = bytesToHex(blake3(utf8ToBytes(testPayload), { key: SECRET_KEY }));
console.log('Gültige Signatur:', verifyWebhookSignature(testPayload, validSig));
// true
console.log('Falsche Signatur:', verifyWebhookSignature(testPayload, 'a'.repeat(64)));
// false
console.log('Manipulierter Payload:', verifyWebhookSignature(
'{"event":"order.paid","amount":1}', validSig));
// false
Schritt 10: Produktions-Best-Practices und vollständige Projektstruktur
Das vollständige Produktionsprojekt bringt alle vorherigen Schritte zusammen. Eine empfohlene Projektstruktur für eine BLAKE3-fähige Node.js-Anwendung:
blake3-api/
├── src/
│ ├── crypto/
│ │ ├── hasher.mjs # Basis-Hashing und Streaming
│ │ ├── keyed.mjs # Keyed Hashing für MAC und Webhooks
│ │ ├── kdf.mjs # Key Derivation (Unterschlüssel ableiten)
│ │ └── verify.mjs # Timing-sichere Verifikation
│ ├── api/
│ │ ├── routes.mjs # Express-Routen
│ │ └── middleware.mjs # Webhook-Verifikation als Middleware
│ └── index.mjs # Einstiegspunkt
├── test/
│ └── crypto.test.mjs # Unit-Tests mit node:test
├── .env # BLAKE3_KEY=<32-byte-hex> (NICHT versionieren!)
├── .env.example # Vorlage ohne echten Schlüssel
└── package.json
Die Webhook-Verifikations-Middleware für Express.js:
// src/api/middleware.mjs
import { timingSafeEqual } from 'node:crypto';
import { blake3 } from '@noble/hashes/blake3';
import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils';
// Schlüssel aus Umgebungsvariable laden (32 Bytes als Hex)
const WEBHOOK_KEY = process.env.BLAKE3_WEBHOOK_KEY
? Buffer.from(process.env.BLAKE3_WEBHOOK_KEY, 'hex')
: null;
export function webhookVerification(req, res, next) {
if (!WEBHOOK_KEY) {
return res.status(500).json({ error: 'BLAKE3_WEBHOOK_KEY nicht konfiguriert' });
}
const receivedSig = req.headers['x-blake3-signature'];
if (!receivedSig) {
return res.status(401).json({ error: 'Fehlender Signatur-Header: X-BLAKE3-Signature' });
}
// rawBody muss mit express.raw() gepuffert werden
const rawBody = req.rawBody || JSON.stringify(req.body);
const expectedSig = blake3(utf8ToBytes(rawBody), { key: WEBHOOK_KEY });
const expectedBuf = Buffer.from(bytesToHex(expectedSig), 'hex');
let receivedBuf;
try {
receivedBuf = Buffer.from(receivedSig, 'hex');
} catch {
return res.status(401).json({ error: 'Ungültiges Signaturformat (Hex erwartet)' });
}
if (expectedBuf.length !== receivedBuf.length || !timingSafeEqual(expectedBuf, receivedBuf)) {
return res.status(401).json({ error: 'Signaturvalidierung fehlgeschlagen' });
}
next();
}
Konfiguriere den Server-Schlüssel über eine Umgebungsvariable. Generiere ihn einmalig sicher:
# 32 zufällige Bytes als Hex ausgeben
node -e "const {randomBytes}=require('node:crypto');console.log(randomBytes(32).toString('hex'))"
# Ausgabe in .env speichern (Datei NIE committen!)
# BLAKE3_WEBHOOK_KEY=a1b2c3d4...64_hex_zeichen_gesamt
BLAKE3 vs HMAC-SHA256: Wann welchen verwenden?
BLAKE3 Keyed Hashing und HMAC-SHA256 lösen dasselbe Problem, unterscheiden sich aber im Einsatz. Die folgende Tabelle gibt eine klare Entscheidungshilfe:
| Kriterium | BLAKE3 Keyed Hash | HMAC-SHA256 |
|---|---|---|
| Node.js-Unterstützung | Externes Paket (@noble/hashes) | Eingebaut (node:crypto) |
| Schlüssellänge | Exakt 32 Bytes (fest) | Beliebig (empfohlen: 32+ Bytes) |
| Längenextensionsangriffe | Sicher (strukturell) | Sicher (HMAC schützt dagegen) |
| Interoperabilität | Begrenzt (neuerer Standard) | Weit verbreitet (RFC 2104) |
| FIPS 140 Zertifizierung | Nein | Ja |
| Externe APIs | Kaum unterstützt | Standard (GitHub, Stripe, etc.) |
| Neue interne Systeme | Empfohlen | Alternative |
Die kurze Regel: Nutze HMAC-SHA256 für externe APIs, Compliance-Anforderungen oder wenn Interoperabilität wichtig ist. Nutze BLAKE3 Keyed Hashing für interne Systeme, bei denen du die Implementierung vollständig kontrollierst. Mehr zum Einsatz von HMAC findest du in unserem Artikel HMAC-SHA256 in Node.js.
5 häufige Fehler bei der BLAKE3-Implementierung in Node.js
Fehler 1: Schlüssel mit falscher Länge beim Keyed Hashing
Der BLAKE3-Schlüssel für Keyed Hashing muss exakt 32 Bytes lang sein. Ein UTF-8-String wie "mein-geheimer-schluessel" hat 26 Bytes und wirft einen Laufzeitfehler:
// FALSCH: String direkt als Schlüssel (26 Bytes statt 32)
const key = utf8ToBytes('mein-geheimer-schluessel');
blake3(data, { key }); // Error: blake3: key length must be 32 bytes
// RICHTIG: Schlüssel auf 32 Bytes bringen (via SHA-256 oder randomBytes)
import { createHash } from 'node:crypto';
const key32 = new Uint8Array(
createHash('sha256').update('mein-geheimer-schluessel').digest()
);
blake3(data, { key: key32 }); // Funktioniert
// BESSER: Zufälligen 32-Byte-Schlüssel einmalig generieren und sicher speichern
import { randomBytes } from '@noble/hashes/utils';
const secureKey = randomBytes(32);
Fehler 2: Hasher-Objekt nach digest() wiederverwenden
Nach dem Aufruf von hasher.digest() ist das Hasher-Objekt in einem undefinierten Zustand. Ein erneuter update()-Aufruf danach liefert falsche Ergebnisse ohne Fehlermeldung:
// FALSCH: Hasher nach digest() wiederverwenden
const hasher = blake3.create();
hasher.update(chunk1);
const hash1 = hasher.digest();
hasher.update(chunk2); // Fehler: Hasher ist verbraucht
const hash2 = hasher.digest(); // Falsches Ergebnis, kein Fehler!
// RICHTIG: Pro Hash ein neues Hasher-Objekt
const h1 = blake3.create();
h1.update(chunk1);
const hash1 = h1.digest();
const h2 = blake3.create();
h2.update(chunk2);
const hash2 = h2.digest();
Fehler 3: Direkter String-Vergleich statt timingSafeEqual()
Der ===-Operator bricht Vergleiche beim ersten unterschiedlichen Zeichen ab. Das erzeugt messbare Zeitunterschiede, die Timing-Angriffe ermöglichen:
// UNSICHER: direkter Vergleich
if (computedHash === receivedHash) { /* ... */ }
// SICHER: timingSafeEqual aus node:crypto
import { timingSafeEqual } from 'node:crypto';
const bufA = Buffer.from(computedHash, 'hex');
const bufB = Buffer.from(receivedHash, 'hex');
if (bufA.length === bufB.length && timingSafeEqual(bufA, bufB)) {
// Signatur gültig
}
Fehler 4: BLAKE3 für Passwortspeicherung verwenden
BLAKE3 ist nicht für Passwortspeicherung geeignet. Es ist bewusst schnell, was Brute-Force-Angriffe trivial macht. Für Passwörter verwendest du Argon2id (empfohlen), bcrypt oder scrypt. Diese Algorithmen sind darauf ausgelegt, langsam zu sein und damit Brute-Force erheblich zu erschweren. Unser Tutorial Argon2 in Node.js zeigt die korrekte Implementierung.
Fehler 5: ESM-Import-Fehler in CommonJS-Projekten
Wenn package.json kein "type": "module" enthält, schlägt der statische import-Befehl fehl. Nutze den dynamischen import()-Aufruf als Workaround:
// In CommonJS-Dateien (.cjs oder ohne "type":"module" in package.json)
const { blake3 } = await import('@noble/hashes/blake3');
const { utf8ToBytes, bytesToHex } = await import('@noble/hashes/utils');
// Alternativ: Datei in .mjs umbenennen für statischen Import
Troubleshooting: 8 häufige Fehler und Lösungen
| Fehlermeldung oder Problem | Ursache | Lösung |
|---|---|---|
Error: blake3: key length must be 32 bytes | Schlüssel hat nicht exakt 32 Bytes | SHA-256-Hash des Schlüssels verwenden oder randomBytes(32) |
Cannot use import statement in module | "type":"module" fehlt in package.json | npm pkg set type="module" oder Datei in .mjs umbenennen |
ERR_PACKAGE_PATH_NOT_EXPORTED | Veraltete @noble/hashes-Version | npm update @noble/hashes auf Version 1.4.0+ |
| Hashes sind nicht reproduzierbar | Hasher nach digest() wiederverwendet | Pro Hash neues blake3.create()-Objekt erstellen |
| Webhook-Signatur schlägt fehl trotz korrektem Schlüssel | Payload-Encoding unterschiedlich (UTF-8 vs Latin-1) | Immer utf8ToBytes() oder Buffer.from(str, 'utf8') verwenden |
TypeError: blake3 is not a function | Falscher Import-Pfad oder fehlende geschweifte Klammern | import { blake3 } from '@noble/hashes/blake3' prüfen |
| Langsame Performance bei sehr kleinen Inputs (<1 KB) | Overhead des Hasher-Objekts dominiert bei kleinen Daten | Direkt blake3(data) statt blake3.create().update().digest() |
| Build schlägt fehl mit TypeScript-Fehlern | @noble/hashes erwartet TypeScript 5.0+ | npm update typescript und tsconfig.json auf "moduleResolution":"bundler" setzen |
BLAKE3 und Node.js-Versionen: Kompatibilitätsmatrix
| Node.js-Version | Support-Status | ESM-Support | @noble/hashes 1.8.x | Empfehlung |
|---|---|---|---|---|
| v14.x | EOL seit April 2023 | Experimentell | Nein | Sofort upgraden |
| v16.x | EOL seit September 2023 | Stabil | Teilweise | Upgraden |
| v18.x LTS | Aktiv bis April 2025 | Stabil | Ja | Mindestversion |
| v20.x LTS | Aktiv bis April 2026 | Stabil | Ja | Gut für Produktion |
| v22.x LTS | Aktiv bis April 2027 | Stabil | Ja | Empfohlen |
| v24.x Current | Aktiv (kein LTS) | Stabil | Ja | Nur für Bleeding-Edge |
Fortgeschrittene Tipps
Natives blake3-Addon für maximale Geschwindigkeit
Für maximalen Durchsatz in dateiintensiven Anwendungen (Backup-Software, Content-Delivery, Datenbanken) bietet das native blake3-npm-Paket eine C++-Binding-Implementierung:
npm install blake3
import { hash, createHasher } from 'blake3';
import { createReadStream } from 'node:fs';
// Einfaches Hashing (native Geschwindigkeit)
const result = hash('Hallo Wien');
console.log(result.toString('hex'));
// Streaming-Hashing mit native Performance
async function hashLargeFile(path) {
const hasher = createHasher();
for await (const chunk of createReadStream(path)) {
hasher.update(chunk);
}
return hasher.digest('hex');
}
const fileHash = await hashLargeFile('grossedatei.bin');
console.log('Native BLAKE3:', fileHash);
BLAKE3 für Content-Addressable Storage
Content-Addressable Storage (CAS) nutzt den Hash eines Inhalts als Adresse. BLAKE3 eignet sich für CAS-Systeme besonders gut, weil variable Ausgabelänge und Parallelisierbarkeit bei großen Dateimengen entscheidende Vorteile bieten. Das Zig-Build-System und die JavaScript-Runtime Bun setzen BLAKE3 für ihre interne Caching-Logik ein:
import { blake3 } from '@noble/hashes/blake3';
import { bytesToHex } from '@noble/hashes/utils';
import { writeFileSync, readFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
const STORE_DIR = './cas-store';
mkdirSync(STORE_DIR, { recursive: true });
function cas_store(content) {
const data = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8');
const hash = bytesToHex(blake3(data));
writeFileSync(join(STORE_DIR, hash), data);
return hash; // Adresse IS der Hash
}
function cas_get(hash) {
return readFileSync(join(STORE_DIR, hash));
}
const addr = cas_store('Willkommen in Wien!');
console.log('Adresse:', addr);
console.log('Inhalt:', cas_get(addr).toString());
Weiterführende Artikel
Verwandte Themen auf shattered.io
- SHA-256 vs SHA-3: 3,7x Geschwindigkeit, ein Sieger - Detaillierter Vergleich der NIST-Standard-Hash-Algorithmen
- Was ist eine Hashfunktion? So funktioniert Hashing - Grundlagen für Einsteiger
- Digitale Signatur in Node.js: 11 Schritte, 40 Min - RSA und ECDSA für asymmetrische Signaturen
- Diffie-Hellman Key Exchange in Node.js: 12 Schritte, 45 Min - Schlüsselaustausch ohne geteiltes Geheimnis
- SHA-256 erklärt: So funktioniert es - Internals des SHA-256-Algorithmus
- Kryptographie Hub: Alle Tutorials im Überblick
Häufig gestellte Fragen (FAQ)
Ist BLAKE3 sicherer als SHA-256?
Nein, beide bieten dieselbe 128-Bit-Sicherheitsstufe. SHA-256 hat eine über 30-jährige akademische Prüfung hinter sich; BLAKE3 ist seit 2020 bekannt. Für regulierte Umgebungen (FIPS, PCI DSS) bleibt SHA-256 Pflicht, da BLAKE3 keine FIPS-140-Zertifizierung besitzt. Für neue, nicht-regulierte Projekte ist BLAKE3 eine sichere Wahl mit Potenzial für bessere Performance auf nativer Hardware.
Unterstützt Node.js BLAKE3 nativ?
Nein. Node.js nutzt OpenSSL als Krypto-Backend, und OpenSSL implementiert BLAKE3 nicht. Du benötigst ein externes Paket: @noble/hashes (pure JavaScript, auditiert) für die meisten Projekte, oder das native blake3-Addon für maximalen Durchsatz.
Kann ich BLAKE3 für Passwort-Hashing verwenden?
Nein. BLAKE3 ist bewusst schnell, was Passwort-Hashing unsicher macht. Ein Angreifer kann mit moderner GPU-Hardware Milliarden von BLAKE3-Hashes pro Sekunde berechnen. Für Passwörter verwendest du Argon2id (OWASP-Empfehlung 2026), bcrypt oder scrypt. Diese Algorithmen sind absichtlich langsam und ressourcenintensiv.
Wie unterscheidet sich BLAKE3 Keyed Hashing von HMAC-SHA256?
BLAKE3 Keyed Hashing und HMAC-SHA256 erzielen dasselbe Sicherheitsziel (Message Authentication Code). BLAKE3 integriert den Schlüssel direkt in die Zustandsinitialisierung, während HMAC zwei Hash-Aufrufe benötigt. HMAC-SHA256 hat den Vorteil der weiten Verbreitung, FIPS-Zertifizierung und Unterstützung in externen APIs (GitHub Webhooks, Stripe). BLAKE3 Keyed Hash ist strukturell einfacher und von Haus aus gegen Längenextensionsangriffe geschützt.
Welche Projekte verwenden BLAKE3 in Produktion?
Das Zig-Build-System nutzt BLAKE3 für Artefakt-Caching. Die JavaScript-Runtime Bun verwendet BLAKE3 für interne Hashing-Operationen. Die Rust-Ökosystem-Tools nutzen BLAKE3 in verschiedenen Bibliotheken. Im JavaScript-Ökosystem ist BLAKE3 via @noble/hashes in ethers.js und mehreren Web3-Bibliotheken im Einsatz.
Wie lang muss der Schlüssel für BLAKE3 Keyed Hashing sein?
Genau 32 Bytes (256 Bit). Das ist eine feste technische Anforderung des Algorithmus, keine Empfehlung. Ein kürzerer oder längerer Schlüssel wirft einen Laufzeitfehler. Generiere den Schlüssel mit crypto.randomBytes(32) aus Node.js und speichere ihn als 64-stelligen Hex-String in einer Umgebungsvariable.
Wann ist @noble/hashes besser als das native blake3-Addon?
Für die meisten Web-APIs und Serveranwendungen reicht @noble/hashes: null externe Abhängigkeiten, plattformunabhängig (kein Kompilieren nötig), auditiert und in CI-Umgebungen einfach zu installieren. Das native blake3-Addon ist besser für Anwendungen, die kontinuierlich große Datenmengen hashen (Backup-Software, CDN-Validierung, Datenbankoperationen), wo der 3-5x Geschwindigkeitsunterschied zwischen JavaScript und nativem Code spürbar wird.
Externe Ressourcen
- BLAKE3 Offizielle Referenzimplementierung (GitHub) - C, Rust, Python und Benchmarks der Autoren
- @noble/hashes auf GitHub - Audit-Berichte, TypeScript-Quellcode, Beispiele
- Node.js Crypto API-Dokumentation - SHA-256, HMAC, timingSafeEqual (offizielle Docs)
- BLAKE3 Projekt-Website - Offizielle Website mit Spezifikation und Implementierungen
- BLAKE3 IETF-Entwurf - Technische Spezifikation für Interoperabilität




