O BLAKE3 é o algoritmo de hash criptográfico mais rápido disponível em 2026, superando o SHA-256 em mais de 5x em hardware x86_64 moderno. Para programadores Node.js que processam ficheiros grandes, verificam integridade de dados ou autenticam webhooks, a mudança do SHA-256 para o BLAKE3 pode reduzir o tempo de hashing de segundos para milissegundos, sem abdicar de segurança. Este tutorial mostra como implementar BLAKE3 em Node.js em 12 passos práticos, com exemplos de código prontos para produção.

O Que é BLAKE3 e Por Que Importa em 2026

O BLAKE3 é uma função de hash criptográfica desenvolvida em 2020 por Jack O’Connor, Jean-Philippe Aumasson, Samuel Neves e Zooko Wilcox-O’Hearn. Surgiu como evolução do BLAKE2 e do Bao, incorporando uma estrutura de árvore binária que permite paralelismo nativo tanto por SIMD como por multithreading.

A saída padrão do BLAKE3 é de 256 bits (32 bytes), idêntica ao SHA-256, mas com três modos de operação distintos:

  • Hash simples: substituição direta do SHA-256 para integridade de dados.
  • Keyed hash (MAC): autenticação de mensagens com chave de 256 bits, resistente a ataques de extensão de comprimento que afetam o SHA-256 em construções ingénuas.
  • Derivação de chave (KDF): geração de subchaves criptográficas a partir de material de chave principal.

A relevância do BLAKE3 em 2026 tem três dimensões. Primeiro, o desempenho: em servidores x86_64 com SIMD AVX-512, o BLAKE3 atinge 13.196 MB/s para entradas de 1 MB, contra 2.373 MB/s do SHA-256 e 686 MB/s do SHA-3 no mesmo hardware (AMD EPYC 4245P, dados de kerkour.com). Segundo, a segurança: ataques conhecidos conseguem quebrar apenas 2 dos 7 rounds do BLAKE3, versus 31 dos 64 rounds do SHA-256, indicando maior margem de segurança. Terceiro, a adoção: o protocolo Solana utiliza BLAKE3 para hashes de contas desde o lançamento, e vários projetos de armazenamento distribuído e verificação de firmware adotaram o algoritmo em 2025.

Para aplicações Node.js que fazem hashing frequente de ficheiros, tokens de API ou payloads de webhook, o BLAKE3 é uma escolha técnica sólida em 2026.

BLAKE3 vs SHA-256 vs SHA-3: Benchmarks Reais em x86_64 e ARM

Antes de instalar qualquer pacote, convém perceber onde o BLAKE3 ganha e onde perde. Os dados abaixo vêm de benchmarks publicados em 2024 num AMD EPYC 4245P (Zen 5) e numa máquina ARM64 com aceleração de hardware para SHA.

Algoritmo64 B (x86_64)1 KB (x86_64)1 MB (x86_64)1 MB (ARM64)
SHA-256822 MB/s2.123 MB/s2.373 MB/s1.744 MB/s
SHA-512299 MB/s637 MB/s735 MB/sN/A
SHA3-256246 MB/s620 MB/s686 MB/sN/A
BLAKE3505 MB/s952 MB/s13.196 MB/s1.338 MB/s

A tabela revela uma nuance importante: em entradas pequenas (64 bytes), o SHA-256 supera o BLAKE3 em x86_64 porque as implementações de batching multi-mensagem para SHA-256 são mais maduras. O BLAKE3 só toma a dianteira claramente a partir de entradas de 16 KB, onde a estrutura de árvore começa a ser processada em paralelo via SIMD AVX2 ou AVX-512.

Em ARM64 (Apple Silicon, AWS Graviton, Raspberry Pi 5), os chips modernos possuem instruções de hardware dedicadas para SHA-256 e SHA-3, o que pode tornar o SHA-256 até 1,8x mais rápido que o BLAKE3 de thread única. Com multithreading, a situação inverte-se: num servidor com 8 núcleos, o BLAKE3 pode ser 16x mais rápido que o SHA-256 de thread única porque escala linearmente com os núcleos.

Para aplicações Node.js típicas, como verificar o hash de uploads de utilizadores (geralmente entre 1 KB e 100 MB), o BLAKE3 será mais rápido na maioria dos cenários de servidor. Para hashing de tokens JWT ou passwords curtas (64 bytes ou menos), o SHA-256 built-in pode ser suficiente sem depender de pacote externo.

Pré-requisitos: Versões, Dependências e Ambiente

Antes de começar, verifica se o ambiente cumpre os seguintes requisitos:

ComponenteVersão MínimaVersão RecomendadaMotivo
Node.js18.0.022.x LTSSuporte a Worker Threads estável e ESM
npm8.x10.xCompatibilidade com scripts nativos do blake3
blake3 (npm)3.0.03.0.0Versão atual com suporte a ESM e CJS
Sistema OperativoLinux/macOS/WindowsLinux x86_64AVX-512 para máximo desempenho
RAM disponível512 MB2 GB+Worker threads e ficheiros grandes

O pacote blake3 versão 3.0.0 distribui binários nativos pré-compilados para Windows, macOS e Linux (x86_64 e ARM64). Não é necessário compilar manualmente em sistemas comuns. Em ambientes de CI/CD sem acesso a binários pré-compilados (Alpine Linux com musl), pode ser necessário adicionar a flag --build-from-source.

Verifica a versão do Node.js antes de avançar:

node --version
# Deve retornar v18.x.x ou superior

npm --version
# Deve retornar 8.x ou superior

Passos 1 a 3: Instalação, Estrutura do Projeto e Primeira Hash

Vamos criar um projeto Node.js de raiz dedicado a demonstrar o BLAKE3 em diferentes cenários.

Passo 1: Inicializar o Projeto

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

# Instalar o pacote blake3
npm install blake3

# Verificar a instalação
node -e "const b3 = require('blake3'); console.log('blake3 versão:', require('./node_modules/blake3/package.json').version)"
# Saída esperada: blake3 versão: 3.0.0

O pacote blake3 instala automaticamente binários nativos compilados em Rust via WASM e N-API. Se surgir um erro de compilação, adiciona o parâmetro --ignore-scripts e usa a versão WebAssembly:

# Alternativa: forçar versão WASM (mais lenta, sem dependências nativas)
npm install blake3 --ignore-scripts

# A versão WASM é importada automaticamente quando o binário nativo não está disponível

Passo 2: Estrutura de Ficheiros do Projeto

Cria a seguinte estrutura de diretórios:

blake3-demo/
├── package.json
├── src/
│   ├── 01-hash-basico.js
│   ├── 02-hash-ficheiro.js
│   ├── 03-keyed-hash.js
│   ├── 04-kdf.js
│   ├── 05-worker-threads.js
│   ├── 06-integridade.js
│   └── 07-webhook.js
├── test-files/
│   └── (ficheiros de teste)
└── server.js

Passo 3: Hash Básico de String

O uso mais simples do BLAKE3 é calcular a hash de uma string ou buffer. Cria o ficheiro src/01-hash-basico.js:

const { hash, createHash } = require('blake3');

// Método 1: hash direta (síncrono)
const input = 'Olá, BLAKE3!';
const digest = hash(input);
console.log('Hash hex:', digest.toString('hex'));
// Saída: 48 caracteres hexadecimais (256 bits)

// Método 2: interface streaming (idêntica ao crypto do Node.js)
const hasher = createHash();
hasher.update('Olá, ');
hasher.update('BLAKE3!');
const digest2 = hasher.digest('hex');
console.log('Hash streaming:', digest2);

// Comparar com SHA-256 do Node.js
const crypto = require('crypto');
const sha256 = crypto.createHash('sha256').update(input).digest('hex');
console.log('SHA-256:', sha256, '(64 chars, 256 bits)');
console.log('BLAKE3: ', digest.toString('hex'), '(64 chars, 256 bits)');

// Hash de Buffer
const buffer = Buffer.from('dados binários para verificação', 'utf8');
const hashBuffer = hash(buffer);
console.log('Hash de Buffer:', hashBuffer.toString('hex'));

A saída esperada mostra dois hashes hexadecimais de 64 caracteres (256 bits cada), idênticos em tamanho mas produzidos por algoritmos diferentes. Ambos são adequados para verificação de integridade, mas o BLAKE3 oferece vantagens em desempenho e segurança adicional em modos keyed.

Passos 4 e 5: Hash de Ficheiros em Modo Stream

A maior vantagem do BLAKE3 sobre o SHA-256 manifesta-se ao processar ficheiros grandes. O modo stream processa o ficheiro em chunks sem carregar tudo na memória, e a estrutura de árvore do BLAKE3 acelera drasticamente o processamento em disco.

Passo 4: Hash de Ficheiro com ReadStream

const { createHash } = require('blake3');
const fs = require('fs');
const path = require('path');

async function hashFicheiro(caminho) {
  return new Promise((resolve, reject) => {
    const hasher = createHash();
    const stream = fs.createReadStream(caminho, { highWaterMark: 64 * 1024 }); // chunks de 64KB

    stream.on('data', (chunk) => hasher.update(chunk));
    stream.on('end', () => resolve(hasher.digest('hex')));
    stream.on('error', reject);
  });
}

async function main() {
  // Criar ficheiro de teste de 10MB
  const testFile = 'test-files/teste-10mb.bin';
  
  if (!fs.existsSync('test-files')) {
    fs.mkdirSync('test-files');
  }
  
  // Gerar 10MB de dados aleatórios
  const buffer = require('crypto').randomBytes(10 * 1024 * 1024);
  fs.writeFileSync(testFile, buffer);
  
  console.log('A calcular hash BLAKE3 de ficheiro de 10MB...');
  
  const inicio = process.hrtime.bigint();
  const hashB3 = await hashFicheiro(testFile);
  const fimB3 = process.hrtime.bigint();
  
  console.log('BLAKE3:', hashB3);
  console.log('Tempo BLAKE3:', Number(fimB3 - inicio) / 1_000_000, 'ms');
  
  // Comparar com SHA-256
  const crypto = require('crypto');
  const inicioSHA = process.hrtime.bigint();
  const hashSHA = await new Promise((resolve, reject) => {
    const hasher = crypto.createHash('sha256');
    const stream = fs.createReadStream(testFile);
    stream.on('data', (c) => hasher.update(c));
    stream.on('end', () => resolve(hasher.digest('hex')));
    stream.on('error', reject);
  });
  const fimSHA = process.hrtime.bigint();
  
  console.log('SHA-256:', hashSHA);
  console.log('Tempo SHA-256:', Number(fimSHA - inicioSHA) / 1_000_000, 'ms');
}

main().catch(console.error);

Em servidores Linux x86_64 com AVX2, o BLAKE3 tipicamente demora 2 a 5x menos que o SHA-256 para um ficheiro de 10 MB, e a diferença aumenta com ficheiros maiores.

Passo 5: Saída de Tamanho Variável (XOF Mode)

Ao contrário do SHA-256, o BLAKE3 suporta saída de tamanho arbitrário (eXtendable Output Function). Isto permite gerar 512 bits ou apenas 128 bits consoante a necessidade:

const { hash } = require('blake3');

const input = 'dados para derivar chaves de múltiplos tamanhos';

// Saída padrão: 32 bytes (256 bits)
const hash256 = hash(input);
console.log('256 bits:', hash256.toString('hex'));

// Saída de 64 bytes (512 bits) - útil para gerar dois blocos de 256 bits
const hash512 = hash(input, { length: 64 });
console.log('512 bits:', hash512.toString('hex'));

// Saída de 16 bytes (128 bits) - para identificadores curtos
const hash128 = hash(input, { length: 16 });
console.log('128 bits (ID curto):', hash128.toString('hex'));

// O BLAKE3 garante que os primeiros N bytes são sempre iguais,
// independentemente do tamanho total pedido.
// hash256[0..31] === hash512[0..31]
console.log('Consistência XOF:', hash256.toString('hex') === hash512.subarray(0, 32).toString('hex'));

A propriedade de consistência XOF garante que calcular hash de 256 bits e de 512 bits sobre os mesmos dados produz saídas onde os primeiros 256 bits são idênticos. Esta característica é útil para migrar sistemas sem recalcular hashes existentes.

Passo 6: BLAKE3 com Chave (Keyed Hashing para MAC)

O modo keyed do BLAKE3 é um MAC (Message Authentication Code) que substitui construções como HMAC-SHA256, mas com vantagens: sem vulnerabilidade a ataques de extensão de comprimento e com a mesma performance alta do BLAKE3 base.

A chave deve ter exatamente 32 bytes (256 bits). Cria o ficheiro src/03-keyed-hash.js:

const { hash, createHash } = require('blake3');
const crypto = require('crypto');

// Gerar chave de 32 bytes (idealmente armazenada em variável de ambiente)
const CHAVE_SECRETA = crypto.randomBytes(32);
console.log('Chave (guarda em .env):', CHAVE_SECRETA.toString('hex'));

// Autenticar uma mensagem com BLAKE3 keyed
function autenticarMensagem(mensagem, chave) {
  return hash(mensagem, { key: chave });
}

function verificarMensagem(mensagem, chave, macEsperado) {
  const macCalculado = autenticarMensagem(mensagem, chave);
  // Comparação em tempo constante para prevenir timing attacks
  return crypto.timingSafeEqual(macCalculado, macEsperado);
}

const payload = JSON.stringify({ userId: 42, action: 'comprar', amount: 99.99 });

const mac = autenticarMensagem(payload, CHAVE_SECRETA);
console.log('MAC BLAKE3:', mac.toString('hex'));

// Verificar autenticidade
const valido = verificarMensagem(payload, CHAVE_SECRETA, mac);
console.log('Autenticidade verificada:', valido); // true

// Tentar com payload modificado (ataque)
const payloadMalicioso = JSON.stringify({ userId: 42, action: 'comprar', amount: 0.01 });
const invalidoAtaque = verificarMensagem(payloadMalicioso, CHAVE_SECRETA, mac);
console.log('Ataque detetado:', !invalidoAtaque); // true (ataque falhou)

// Comparação: HMAC-SHA256 equivalente
const hmacSHA256 = crypto.createHmac('sha256', CHAVE_SECRETA).update(payload).digest();
console.log('\nHMAC-SHA256:', hmacSHA256.toString('hex'), '(32 bytes)');
console.log('BLAKE3 keyed:', mac.toString('hex'), '(32 bytes, sem extensão de comprimento)');

A função crypto.timingSafeEqual é obrigatória para comparação de MACs em código de produção. Uma comparação direta com === é vulnerável a ataques de temporização (timing attacks) que podem revelar a chave por medição do tempo de resposta.

Guarda a chave numa variável de ambiente e carrega-a assim:

# .env
BLAKE3_KEY=a1b2c3d4e5f6...  # 64 hex chars = 32 bytes
// Carregar chave do ambiente em produção
const CHAVE = Buffer.from(process.env.BLAKE3_KEY, 'hex');
if (CHAVE.length !== 32) {
  throw new Error('BLAKE3_KEY deve ter exatamente 32 bytes (64 caracteres hex)');
}

Passo 7: Modo KDF para Derivação de Chave

O modo KDF (Key Derivation Function) do BLAKE3 gera chaves criptográficas a partir de um segredo principal, com contexto textual que previne reutilização de chaves entre domínios diferentes da aplicação.

const { derive } = require('blake3');
const crypto = require('crypto');

// Material de chave principal (ex: segredo da aplicação)
const MASTER_KEY = crypto.randomBytes(32);

// Derivar chaves diferentes para diferentes propósitos
// O contexto deve ser uma string única e descritiva da aplicação
const chaveEncriptacao = derive(
  'minha-app v1.0 AES-256-GCM encryption key 2026',
  MASTER_KEY
);
const chaveAutenticacao = derive(
  'minha-app v1.0 webhook authentication key 2026',
  MASTER_KEY
);
const chaveTokens = derive(
  'minha-app v1.0 API token signing key 2026',
  MASTER_KEY
);

console.log('Chave de encriptação (32 bytes):', chaveEncriptacao.toString('hex'));
console.log('Chave de autenticação (32 bytes):', chaveAutenticacao.toString('hex'));
console.log('Chave de tokens (32 bytes):', chaveTokens.toString('hex'));

// As três chaves são completamente independentes, derivadas da mesma chave mestre
// Se a chave de encriptação for comprometida, as outras permanecem seguras

// Derivação de chave de sessão com contexto por utilizador
function derivarChaveSessao(masterKey, userId, sessaoId) {
  const contexto = `minha-app v1.0 session key user:${userId} session:${sessaoId}`;
  return derive(contexto, masterKey);
}

const chaveSessao = derivarChaveSessao(MASTER_KEY, 'user_42', 'sess_abc123');
console.log('\nChave de sessão derivada:', chaveSessao.toString('hex'));

O contexto passado à função derive é tratado como um domínio de separação (domain separation string). Isto garante que duas aplicações diferentes com a mesma chave mestre, mas contextos diferentes, nunca produzem a mesma chave derivada, mesmo que um atacante conheça o contexto de uma delas.

Passo 8: Hashing Paralelo com Worker Threads

Para processar múltiplos ficheiros em simultâneo e aproveitar todos os núcleos do servidor, combina o BLAKE3 com os Worker Threads do Node.js. Esta abordagem é especialmente eficaz em servidores com 4 ou mais núcleos.

// worker-hasher.js (ficheiro separado para o worker)
const { workerData, parentPort } = require('worker_threads');
const { hash } = require('blake3');
const fs = require('fs');

const ficheiro = workerData.ficheiro;
const conteudo = fs.readFileSync(ficheiro);
const resultado = hash(conteudo);

parentPort.postMessage({
  ficheiro,
  hash: resultado.toString('hex'),
  tamanho: conteudo.length
});
// src/05-worker-threads.js
const { Worker } = require('worker_threads');
const path = require('path');
const os = require('os');
const fs = require('fs');
const crypto = require('crypto');

// Criar ficheiros de teste
const testDir = 'test-files';
if (!fs.existsSync(testDir)) fs.mkdirSync(testDir);

const ficheiros = [];
for (let i = 0; i < 8; i++) {
  const nome = path.join(testDir, `ficheiro-${i}.bin`);
  fs.writeFileSync(nome, crypto.randomBytes(5 * 1024 * 1024)); // 5MB cada
  ficheiros.push(nome);
}

function hashComWorker(ficheiro) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker-hasher.js', {
      workerData: { ficheiro }
    });
    worker.on('message', resolve);
    worker.on('error', reject);
  });
}

async function hashParalelo(listaFicheiros) {
  const nucleos = Math.min(os.cpus().length, listaFicheiros.length);
  console.log(`A usar ${nucleos} núcleos para ${listaFicheiros.length} ficheiros`);

  const inicio = Date.now();
  const resultados = await Promise.all(listaFicheiros.map(hashComWorker));
  const tempo = Date.now() - inicio;

  console.log(`\nResultados (${tempo}ms):`);
  resultados.forEach(r => {
    const mb = (r.tamanho / 1024 / 1024).toFixed(1);
    console.log(`  ${path.basename(r.ficheiro)} (${mb}MB): ${r.hash.substring(0, 16)}...`);
  });

  const totalMB = resultados.reduce((s, r) => s + r.tamanho, 0) / 1024 / 1024;
  console.log(`\nThroughput: ${(totalMB / tempo * 1000).toFixed(0)} MB/s`);
}

hashParalelo(ficheiros).catch(console.error);

Com 8 ficheiros de 5 MB em paralelo num servidor de 8 núcleos, o BLAKE3 com Worker Threads processa tipicamente 40 MB em menos de 100ms, equivalendo a mais de 400 MB/s de throughput total, uma aceleração de 10 a 16x relativamente ao SHA-256 de thread única.

Passos 9 e 10: Verificação de Integridade e Webhook Security

Passo 9: Verificação de Integridade de Ficheiros

Um dos casos de uso mais práticos do BLAKE3 em produção é a verificação de integridade de uploads ou downloads. O padrão consiste em calcular a hash na origem, transmiti-la junto com o ficheiro, e recalculá-la no destino para confirmar que não houve corrupção ou adulteração.

const { createHash } = require('blake3');
const fs = require('fs');
const path = require('path');

// Classe de verificação de integridade
class IntegrityChecker {
  static async calcularHash(caminho) {
    return new Promise((resolve, reject) => {
      const hasher = createHash();
      const stream = fs.createReadStream(caminho, { highWaterMark: 65536 });
      stream.on('data', (chunk) => hasher.update(chunk));
      stream.on('end', () => resolve(hasher.digest('hex')));
      stream.on('error', reject);
    });
  }

  static async verificar(caminho, hashEsperado) {
    const hashReal = await this.calcularHash(caminho);
    if (hashReal !== hashEsperado.toLowerCase()) {
      throw new Error(
        `Integridade comprometida!\n` +
        `  Esperado: ${hashEsperado}\n` +
        `  Calculado: ${hashReal}`
      );
    }
    return true;
  }

  static async gerarManifesto(directorio) {
    const ficheiros = fs.readdirSync(directorio);
    const manifesto = {};
    
    for (const ficheiro of ficheiros) {
      const caminho = path.join(directorio, ficheiro);
      if (fs.statSync(caminho).isFile()) {
        manifesto[ficheiro] = await this.calcularHash(caminho);
      }
    }
    
    return manifesto;
  }
}

// Exemplo de uso
async function exemplo() {
  const testFile = 'test-files/package.json';
  fs.writeFileSync(testFile, JSON.stringify({ name: 'blake3-demo', version: '1.0.0' }));
  
  // Calcular hash na origem (ex: durante o upload)
  const hashOrigem = await IntegrityChecker.calcularHash(testFile);
  console.log('Hash BLAKE3 original:', hashOrigem);
  
  // Guardar o hash junto com o ficheiro (ex: em base de dados)
  
  // Verificar no destino (ex: após download)
  try {
    await IntegrityChecker.verificar(testFile, hashOrigem);
    console.log('✓ Integridade verificada com sucesso');
  } catch (erro) {
    console.error('✗', erro.message);
  }
  
  // Gerar manifesto de um directório
  const manifesto = await IntegrityChecker.gerarManifesto('test-files');
  console.log('\nManifesto BLAKE3:', JSON.stringify(manifesto, null, 2));
}

exemplo().catch(console.error);

Passo 10: Autenticação de Webhooks com BLAKE3 Keyed

A autenticação de webhooks (GitHub, Stripe, etc.) usa tipicamente HMAC-SHA256. O BLAKE3 em modo keyed oferece a mesma segurança com melhor desempenho e proteção nativa contra ataques de extensão de comprimento.

// server.js - servidor Express com verificação de webhook BLAKE3
const express = require('express');
const { hash } = require('blake3');
const crypto = require('crypto');

const app = express();

// Carregar chave do ambiente (gerada com: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
const WEBHOOK_KEY = Buffer.from(process.env.WEBHOOK_SECRET || crypto.randomBytes(32).toString('hex'), 'hex');

// Middleware para capturar o body raw (necessário para verificação de assinatura)
app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf;
  }
}));

function verificarAssinaturaWebhook(rawBody, assinaturaRecebida, chave) {
  const mac = hash(rawBody, { key: chave });
  const macEsperado = Buffer.from(`blake3=${mac.toString('hex')}`);
  const macRecebido = Buffer.from(assinaturaRecebida);
  
  if (macEsperado.length !== macRecebido.length) {
    return false;
  }
  
  return crypto.timingSafeEqual(macEsperado, macRecebido);
}

app.post('/webhook', (req, res) => {
  const assinatura = req.headers['x-webhook-signature'];
  
  if (!assinatura) {
    return res.status(401).json({ erro: 'Assinatura em falta' });
  }
  
  if (!verificarAssinaturaWebhook(req.rawBody, assinatura, WEBHOOK_KEY)) {
    return res.status(403).json({ erro: 'Assinatura inválida' });
  }
  
  console.log('Webhook autenticado:', req.body);
  res.json({ status: 'ok' });
});

// Rota de teste: gerar assinatura para testar
app.post('/webhook/assinar', (req, res) => {
  const payload = Buffer.from(JSON.stringify(req.body));
  const mac = hash(payload, { key: WEBHOOK_KEY });
  res.json({
    assinatura: `blake3=${mac.toString('hex')}`,
    instrucoes: 'Adiciona este valor ao header X-Webhook-Signature'
  });
});

app.listen(3000, () => {
  console.log('Servidor webhook a correr em http://localhost:3000');
  console.log('WEBHOOK_KEY:', WEBHOOK_KEY.toString('hex').substring(0, 8) + '...');
});

Para testar o endpoint de webhook, gera uma assinatura e envia o pedido:

# Passo 1: gerar assinatura
curl -s -X POST http://localhost:3000/webhook/assinar \
  -H "Content-Type: application/json" \
  -d '{"evento":"pagamento","valor":99.99}' | jq .

# Passo 2: enviar webhook com assinatura
ASSINATURA="blake3=<hash da resposta anterior>"
curl -s -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: $ASSINATURA" \
  -d '{"evento":"pagamento","valor":99.99}'

# Saída esperada: {"status":"ok"}

# Testar com payload modificado (deve falhar)
curl -s -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: $ASSINATURA" \
  -d '{"evento":"pagamento","valor":0.01}'

# Saída esperada: {"erro":"Assinatura inválida"}

Passos 11 e 12: Projeto Completo com API REST

Para completar o tutorial, vamos montar uma API REST de serviço de verificação de integridade, que recebe uploads de ficheiros, guarda a hash BLAKE3, e permite verificar integridade posteriormente.

// package.json - adicionar dependências
{
  "name": "blake3-integrity-api",
  "version": "1.0.0",
  "scripts": {
    "start": "node api-server.js",
    "test": "node test-api.js"
  },
  "dependencies": {
    "blake3": "^3.0.0",
    "express": "^4.18.0",
    "multer": "^1.4.5-lts.1"
  }
}
// api-server.js - API completa de verificação de integridade
const express = require('express');
const multer = require('multer');
const { createHash } = require('blake3');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

const app = express();
app.use(express.json());

// Armazenamento em memória (substitui por base de dados em produção)
const manifestoGlobal = new Map();

// Configurar upload para diretório temporário
const upload = multer({ dest: 'uploads/' });

// Calcular BLAKE3 de um ficheiro via stream
function hashFicheiroStream(caminho) {
  return new Promise((resolve, reject) => {
    const hasher = createHash();
    const stream = fs.createReadStream(caminho, { highWaterMark: 65536 });
    stream.on('data', (chunk) => hasher.update(chunk));
    stream.on('end', () => resolve(hasher.digest('hex')));
    stream.on('error', reject);
  });
}

// POST /ficheiros - upload com hash automático
app.post('/ficheiros', upload.single('ficheiro'), async (req, res) => {
  if (!req.file) {
    return res.status(400).json({ erro: 'Nenhum ficheiro recebido' });
  }

  try {
    const hashBlake3 = await hashFicheiroStream(req.file.path);
    const id = crypto.randomUUID();

    manifestoGlobal.set(id, {
      nome: req.file.originalname,
      tamanho: req.file.size,
      hash: hashBlake3,
      algoritmo: 'blake3',
      timestamp: new Date().toISOString(),
      caminho: req.file.path
    });

    res.status(201).json({
      id,
      nome: req.file.originalname,
      tamanho: req.file.size,
      hash: hashBlake3,
      algoritmo: 'blake3'
    });
  } catch (erro) {
    res.status(500).json({ erro: erro.message });
  }
});

// GET /ficheiros/:id/verificar - verificar integridade
app.get('/ficheiros/:id/verificar', async (req, res) => {
  const entrada = manifestoGlobal.get(req.params.id);
  if (!entrada) {
    return res.status(404).json({ erro: 'Ficheiro não encontrado' });
  }

  try {
    const hashAtual = await hashFicheiroStream(entrada.caminho);
    const integro = hashAtual === entrada.hash;

    res.json({
      id: req.params.id,
      nome: entrada.nome,
      integro,
      hashOriginal: entrada.hash,
      hashAtual,
      verificadoEm: new Date().toISOString()
    });
  } catch (erro) {
    res.status(500).json({ erro: 'Ficheiro não acessível: ' + erro.message });
  }
});

// GET /ficheiros - listar todos com hashes
app.get('/ficheiros', (req, res) => {
  const lista = Array.from(manifestoGlobal.entries()).map(([id, dados]) => ({
    id,
    nome: dados.nome,
    tamanho: dados.tamanho,
    hash: dados.hash,
    algoritmo: dados.algoritmo,
    timestamp: dados.timestamp
  }));
  res.json(lista);
});

app.listen(3001, () => {
  console.log('API de Integridade BLAKE3 em http://localhost:3001');
  console.log('Endpoints:');
  console.log('  POST /ficheiros          - Upload com hash automático');
  console.log('  GET  /ficheiros/:id/verificar - Verificar integridade');
  console.log('  GET  /ficheiros          - Listar ficheiros');
});

Para instalar e executar o projeto completo:

npm install blake3 express multer
node api-server.js

# Fazer upload de ficheiro
curl -s -X POST http://localhost:3001/ficheiros \
  -F "[email protected]" | jq .

# Saída esperada:
# {
#   "id": "uuid-gerado",
#   "nome": "package.json",
#   "tamanho": 248,
#   "hash": "a3b4c5...",
#   "algoritmo": "blake3"
# }

# Verificar integridade
curl -s http://localhost:3001/ficheiros/<id>/verificar | jq .

# Saída esperada:
# {
#   "integro": true,
#   "hashOriginal": "a3b4c5...",
#   "hashAtual": "a3b4c5..."
# }

5 Erros Comuns ao Implementar BLAKE3 em Node.js

Estes são os erros mais frequentes que surgem durante a implementação, baseados em discussões da comunidade e nas diferenças de comportamento face ao SHA-256 built-in do Node.js.

Erro 1: Assumir que BLAKE3 é sempre mais rápido que SHA-256 em qualquer cenário. Para inputs inferiores a 4 KB e em servidores ARM64 com aceleração de hardware SHA, o SHA-256 built-in pode ser mais rápido. O BLAKE3 brilha em ficheiros de 16 KB ou maiores em hardware x86_64. Faz benchmarks no teu hardware específico antes de migrar.

Erro 2: Comparar hashes com o operador === ou com ==. Esta comparação é vulnerável a timing attacks. Usa sempre crypto.timingSafeEqual() em código de segurança. O operador === retorna no primeiro byte diferente, o que permite a atacantes medir o tempo de resposta e deduzir bytes do hash correto.

// ERRADO - vulnerável a timing attack
if (hashCalculado.toString('hex') === hashEsperado) { ... }

// CORRETO - tempo constante
const a = Buffer.from(hashCalculado.toString('hex'));
const b = Buffer.from(hashEsperado);
if (a.length === b.length && crypto.timingSafeEqual(a, b)) { ... }

Erro 3: Reutilizar o mesmo hasher para múltiplas mensagens sem chamar digest(). Após chamar hasher.update(dados), o estado interno é acumulado. Se não chamares hasher.digest() e criares um novo hasher para a próxima mensagem, acabas por misturar dados de mensagens diferentes.

// ERRADO - hasher reutilizado
const hasher = createHash();
mensagens.forEach(msg => {
  hasher.update(msg);
  console.log(hasher.digest('hex')); // O 2º digest inclui dados do 1º!
});

// CORRETO - novo hasher por mensagem
mensagens.forEach(msg => {
  const hasher = createHash();
  hasher.update(msg);
  console.log(hasher.digest('hex'));
});

Erro 4: Usar a chave como string em vez de Buffer de 32 bytes no modo keyed. A função hash(dados, { key: 'minha-chave' }) irá rejeitar chaves que não tenham exatamente 32 bytes. Uma string ASCII de 32 caracteres tem 32 bytes, mas isso não é seguro. Gera a chave com crypto.randomBytes(32) e armazena em hex.

Erro 5: Não instalar o pacote blake3 corretamente em Docker ou Alpine Linux. O Alpine Linux usa musl em vez de glibc, o que pode impedir o carregamento dos binários pré-compilados. A solução é usar a imagem node:22-alpine com as ferramentas de compilação instaladas, ou forçar a versão WebAssembly.

# Dockerfile para Alpine com BLAKE3
FROM node:22-alpine
RUN apk add --no-cache python3 make g++
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD ["node", "api-server.js"]

Resolução de Problemas: 8 Cenários Comuns

Esta secção cobre os erros mais frequentes durante a instalação e uso do BLAKE3 em Node.js.

Problema 1: Error: Cannot find module 'blake3/browser'. Este erro ocorre quando tentares importar a variante de browser num ambiente Node.js. Usa require('blake3') sem qualquer sufixo.

Problema 2: bindings.node not found após instalação. Indica que os binários nativos não foram descarregados corretamente. Solução: apaga node_modules e reinstala, ou força a reconstrução com npm rebuild blake3.

Problema 3: Versão WASM carregada em vez da nativa (desempenho inferior). O pacote blake3 carrega a versão WASM automaticamente quando o binário nativo falha. Para verificar qual está a ser usada:

node -e "
const b3 = require('blake3');
// Se não der erro, os binários nativos estão a funcionar
console.log('blake3 carregado com sucesso');
// Testar performance básica
const { hash } = b3;
const inicio = Date.now();
for (let i = 0; i < 10000; i++) hash('teste');
console.log('10.000 hashes em', Date.now() - inicio, 'ms');
// Nativo: ~50ms | WASM: ~200ms"

Problema 4: Error [ERR_WORKER_UNSUPPORTED_EXTENSION] com Worker Threads. Ocorre ao usar ficheiros .mjs como workers em versões mais antigas de Node.js. Usa ficheiros .cjs ou atualiza para Node.js 20+.

Problema 5: RangeError: The key length must be 32 bytes. O modo keyed exige exatamente 32 bytes. Verifica o tamanho da chave antes de usar e normaliza se necessário usando um KDF como PBKDF2 ou o próprio modo KDF do BLAKE3.

Problema 6: Resultados diferentes entre instâncias do servidor. Se dois servidores calculam hashes diferentes para os mesmos dados, o problema está provavelmente na codificação de texto. Garante que a mesma codificação (UTF-8) é usada em toda a cadeia: Buffer.from(texto, 'utf8').

Problema 7: Hash de ficheiro diferente após transferência por SFTP/FTP. Transferências em modo texto podem alterar quebras de linha (CRLF vs LF) em Windows. Usa sempre modo binário em transferências de ficheiros e verifica a hash do ficheiro recebido imediatamente após o download.

Problema 8: Memória crescente ao hashear muitos ficheiros grandes. Garante que o stream é fechado após o digest(). Em versões anteriores do Node.js, streams não fechados acumulavam descritores de ficheiros. Adiciona stream.destroy() após o processamento ou usa pipeline do módulo stream/promises.

const { pipeline } = require('stream/promises');
const { Writable } = require('stream');

async function hashSeguro(caminho) {
  const hasher = createHash();
  
  const sink = new Writable({
    write(chunk, encoding, callback) {
      hasher.update(chunk);
      callback();
    }
  });
  
  await pipeline(fs.createReadStream(caminho), sink);
  return hasher.digest('hex');
}
// pipeline fecha automaticamente todos os streams, mesmo em caso de erro

Dicas Avançadas para Produção

Cache de hashes em Redis. Em aplicações de alto tráfego que verificam frequentemente os mesmos ficheiros, guarda as hashes calculadas no Redis com um TTL adequado. O BLAKE3 é determinístico, por isso um ficheiro não alterado produz sempre a mesma hash. Uma camada de cache elimina recálculos desnecessários.

Streaming de verificação em uploads multipart. Em vez de guardar o ficheiro completo e depois calcular a hash, inicia o hasher no início do stream de upload e calcula em simultâneo com a gravação. Assim, quando o upload termina, a hash já está calculada sem custo adicional de leitura.

Migração incremental de SHA-256 para BLAKE3. Em sistemas com hashes SHA-256 existentes, adiciona um campo hash_blake3 à base de dados e calcula-o da próxima vez que o ficheiro for acedido. Após um período de migração, remove o campo SHA-256. Esta estratégia evita recalcular todos os hashes de uma só vez.

Uso do BLAKE3 com TypeScript. O pacote blake3 inclui definições de tipos TypeScript nativas. Importa assim:

import { hash, createHash, derive } from 'blake3';
import type { HashInput } from 'blake3';

function hashDados(dados: HashInput): string {
  return hash(dados).toString('hex');
}

Benchmark formal antes de migrar. Antes de substituir o SHA-256 em produção, cria um benchmark com os tamanhos reais dos teus dados. Usa o pacote tinybench ou benchmark.js para comparar BLAKE3 vs SHA-256 no hardware de produção. Em ARM64, o SHA-256 built-in pode ser imbatível para tokens pequenos.

Cobertura Relacionada

Para aprofundar os temas abordados neste tutorial, consulta os seguintes artigos:

Perguntas Frequentes

O BLAKE3 é aprovado por organismos de normalização como o NIST? Não. Em 2026, o BLAKE3 não é um padrão NIST. O NIST padronizou o SHA-2 (FIPS 180-4) e o SHA-3 (FIPS 202). Para sistemas que exigem conformidade FIPS, o SHA-256 ou SHA-3 são as opções corretas. O BLAKE3 é adequado para sistemas sem requisitos FIPS, onde a performance é prioritária.

Posso usar BLAKE3 para hashing de passwords? Não. O BLAKE3, tal como o SHA-256 e o SHA-3, é um algoritmo rápido por design, o que o torna inadequado para hashing de passwords. Velocidade é uma desvantagem no contexto de passwords porque facilita ataques de força bruta. Usa Argon2id, bcrypt ou scrypt para passwords.

O BLAKE3 substitui completamente o SHA-256 em Node.js? Para verificação de integridade de ficheiros, checksums e MACs, o BLAKE3 é um substituto superior ao SHA-256 em hardware x86_64 moderno. Para assinaturas digitais (ECDSA, RSA), continua a usar SHA-256 ou SHA-384, já que os algoritmos de assinatura definem o algoritmo de hash interno. Para certificados TLS, o SHA-256 é mandatório por especificação.

Qual é a diferença entre o modo keyed e o modo KDF do BLAKE3? O modo keyed (MAC) produz uma hash autenticada de uma mensagem com uma chave, funcionando como substituto do HMAC. O modo KDF (derivação de chave) transforma material de chave existente em novas chaves criptográficas com separação de domínio, funcionando como substituto do HKDF. São propósitos complementares.

O pacote blake3 funciona em Deno e Bun? O pacote blake3 npm tem suporte para Node.js via N-API. No Bun, a compatibilidade N-API está disponível desde a versão 1.0, mas os binários pré-compilados podem exigir teste. No Deno, o suporte npm está disponível via npm: prefixo desde o Deno 1.28. Em ambos os casos, a versão WASM serve como fallback.

Como migrar de SHA-256 para BLAKE3 sem quebrar sistemas existentes? A estratégia recomendada é prefixar os hashes com o identificador do algoritmo: sha256:abc123... ou blake3:def456.... Assim, o sistema pode verificar o prefixo e usar o algoritmo correto para comparar hashes antigos (SHA-256) com novos (BLAKE3), permitindo migração gradual sem invalidar hashes existentes.

O BLAKE3 é resistente a computadores quânticos? Como todos os hashes de 256 bits, o BLAKE3 oferece 128 bits de segurança pós-quântica (o algoritmo de Grover reduz a força de um hash à raiz quadrada do espaço de estados). Para resistência pós-quântica conservadora, usa o BLAKE3 com saída de 512 bits (hash(dados, { length: 64 })), que oferece 256 bits de segurança pós-quântica.

Quais sistemas de produção já usam BLAKE3? O protocolo Solana usa BLAKE3 para hashes de contas desde o lançamento. Vários projetos de sincronização de ficheiros e armazenamento distribuído adotaram o BLAKE3 em 2024-2025 devido ao desempenho em paralelismo. A ferramenta de linha de comandos b3sum (substituto do md5sum e sha256sum) usa BLAKE3 nativamente.