A Web Crypto API no Node.js oferece uma interface padronizada para geração de números aleatórios, hashes, assinaturas, derivação de chaves, criptografia e importação ou exportação de material criptográfico. Como a mesma família de APIs existe em navegadores, ela facilita compartilhar conceitos e parte do código entre frontend e backend.
No Node.js, a Web Crypto API aparece por meio de globalThis.crypto e do módulo node:crypto. A interface é assíncrona e trabalha com objetos como CryptoKey, ArrayBuffer e Uint8Array. Isso difere das APIs tradicionais de streams e buffers do módulo Crypto.
Neste guia, você aprenderá a usar crypto.getRandomValues(), randomUUID(), subtle.digest(), HMAC, AES-GCM, PBKDF2, geração de pares de chaves, importação, exportação, assinatura, verificação e práticas para proteger chaves e evitar escolhas inseguras.
O que é a Web Crypto API?
A API foi criada como um padrão da Web para operações criptográficas. A documentação oficial de Web Crypto no Node.js mostra algoritmos e compatibilidade. A documentação da Web Crypto API na MDN explica conceitos compartilhados com navegadores.
Para APIs tradicionais do runtime, consulte Crypto no Node.js. Para fundamentos da linguagem, veja o que é JavaScript.
Acessando crypto
const crypto = globalThis.crypto;Também é possível importar:
const { webcrypto } = require('node:crypto');
const crypto = webcrypto;Em versões modernas, a disponibilidade global depende da versão e das flags. Fixe a versão mínima suportada.
Números aleatórios
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);Use números aleatórios criptograficamente seguros para tokens, nonces e identificadores imprevisíveis. Não use Math.random() para segurança.
UUID aleatório
const id = crypto.randomUUID();UUIDs são úteis como identificadores, mas não substituem tokens secretos quando há requisitos específicos de entropia e formato.
Conversão de texto
const encoder = new TextEncoder();
const data = encoder.encode('mensagem');Operações da SubtleCrypto usam bytes. Defina UTF-8 explicitamente com TextEncoder e TextDecoder.
Hash SHA-256
const digest = await crypto.subtle.digest(
'SHA-256',
encoder.encode('conteúdo')
);
const hex = [...new Uint8Array(digest)]
.map(value => value.toString(16).padStart(2, '0'))
.join('');Hash não é criptografia. Ele produz um resumo e não permite recuperar o texto original.
Hash de senha
Não use SHA-256 puro para armazenar senhas. Senhas exigem funções lentas e com salt, como scrypt, Argon2 ou PBKDF2 com parâmetros adequados.
Importando uma chave HMAC
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(process.env.WEBHOOK_SECRET),
{
name: 'HMAC',
hash: 'SHA-256'
},
false,
['sign', 'verify']
);O parâmetro false indica que a chave não é exportável. Isso reduz exposição acidental dentro da aplicação.
Assinando com HMAC
const signature = await crypto.subtle.sign(
'HMAC',
key,
encoder.encode(payload)
);HMAC é útil para autenticar mensagens quando emissor e receptor compartilham um segredo.
Verificando HMAC
const valid = await crypto.subtle.verify(
'HMAC',
key,
receivedSignature,
encoder.encode(payload)
);Use os bytes originais da mensagem. Reconstruir JSON pode alterar espaços e ordem de propriedades.
Veja Webhooks Seguros com Node.js para idempotência e validação de assinatura.
AES-GCM
AES-GCM oferece confidencialidade e autenticação. Gere um IV único para cada criptografia com a mesma chave:
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv
},
encryptionKey,
plaintext
);Reutilizar IV com a mesma chave pode comprometer a segurança.
Gerando chave AES
const encryptionKey = await crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256
},
true,
['encrypt', 'decrypt']
);Marcar como exportável permite persistência, mas aumenta risco. Use false quando a chave vive apenas na sessão.
Descriptografando
const plaintext = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv
},
encryptionKey,
ciphertext
);Se a autenticação falhar, a Promise rejeita. Não retorne detalhes que ajudem um atacante a distinguir erros internos.
Additional authenticated data
const additionalData = encoder.encode('record:123');
const ciphertext = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv,
additionalData
},
encryptionKey,
plaintext
);O dado adicional não é criptografado, mas é autenticado. O mesmo valor precisa ser usado na descriptografia.
PBKDF2
const passwordKey = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
const salt = crypto.getRandomValues(new Uint8Array(16));
const derivedKey = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
hash: 'SHA-256',
salt,
iterations: 310000
},
passwordKey,
{
name: 'AES-GCM',
length: 256
},
false,
['encrypt', 'decrypt']
);O número de iterações precisa ser medido e revisado. Parâmetros envelhecem conforme hardware evolui.
Salt
Salt não precisa ser secreto, mas deve ser aleatório e único por derivação. Armazene-o junto com o resultado.
Não derive chave sem autenticação
Criptografar dados com uma senha fraca não torna o sistema forte. Aplique políticas de senha, limitação de tentativas e gestão segura.
Gerando par de chaves
const keyPair = await crypto.subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'P-256'
},
true,
['sign', 'verify']
);Algoritmos suportados dependem da versão do Node.js e do OpenSSL compilado.
Assinatura ECDSA
const signature = await crypto.subtle.sign(
{
name: 'ECDSA',
hash: 'SHA-256'
},
keyPair.privateKey,
encoder.encode(message)
);Verificação ECDSA
const valid = await crypto.subtle.verify(
{
name: 'ECDSA',
hash: 'SHA-256'
},
keyPair.publicKey,
signature,
encoder.encode(message)
);Confirme o formato da assinatura ao integrar com outras bibliotecas. Representações DER e formatos brutos podem diferir.
RSA
A Web Crypto API também suporta algoritmos RSA em versões compatíveis. Escolha parâmetros documentados e evite esquemas antigos. Para novas assinaturas, avalie ECDSA ou Ed25519 quando suportado pelo ecossistema.
Exportando chave
const publicJwk = await crypto.subtle.exportKey(
'jwk',
keyPair.publicKey
);Chaves públicas podem ser distribuídas. Chaves privadas exigem proteção forte, criptografia em repouso e controle de acesso.
Importando JWK
const publicKey = await crypto.subtle.importKey(
'jwk',
publicJwk,
{
name: 'ECDSA',
namedCurve: 'P-256'
},
true,
['verify']
);Valide propriedades como kty, crv e use. Não confie em um JWK recebido de origem desconhecida.
Formatos SPKI e PKCS8
Chaves públicas costumam usar SPKI e privadas PKCS8. A conversão para PEM exige codificação Base64 e cabeçalhos corretos.
CryptoKey
CryptoKey contém algoritmo, usos, tipo e indicador de exportabilidade. Verifique antes de usar:
console.log({
type: key.type,
extractable: key.extractable,
usages: key.usages,
algorithm: key.algorithm
});Usos mínimos
Ao importar uma chave, forneça apenas os usos necessários. Uma chave de verificação não precisa de permissão para assinar.
Armazenamento de chaves
Evite colocar chaves privadas em código, repositório ou imagem de container. Use gerenciador de segredos, HSM ou KMS quando o risco justificar.
O artigo de Variáveis de Ambiente no Node.js explica cuidados com segredos, embora variáveis também tenham limitações.
Rotação
Inclua identificador da chave nos dados assinados ou criptografados. Durante a rotação, mantenha chaves antigas apenas para leitura ou verificação pelo período necessário.
Codificação Base64
const base64 = Buffer
.from(new Uint8Array(signature))
.toString('base64url');Base64 e Base64URL são formatos diferentes. Confirme o esperado pelo protocolo.
ArrayBuffer e Buffer
const buffer = Buffer.from(arrayBuffer);
const view = new Uint8Array(buffer);Evite cópias desnecessárias em operações grandes. Entenda offsets e comprimentos ao compartilhar memória.
Veja Buffer no Node.js para dados binários.
Erros
SubtleCrypto rejeita Promises em algoritmo inválido, chave incompatível, autenticação falha ou formato incorreto. Normalize erros sem expor detalhes sensíveis.
AbortController
Muitas operações criptográficas não aceitam AbortSignal diretamente. Para trabalhos longos, execute em Worker Thread ou defina limites na arquitetura. O guia de AbortController no Node.js mostra cancelamento em APIs compatíveis.
Performance
Operações podem usar threads internas e competir com outras tarefas. Faça benchmark com tamanho real de dados e concorrência. Evite criptografar payloads gigantes inteiramente em memória quando uma API de streams tradicional for mais adequada.
Web Crypto ou node:crypto?
- Web Crypto: padrão da Web, Promises e CryptoKey.
- node:crypto: APIs adicionais, streams e compatibilidade histórica.
Escolha pela interoperabilidade, algoritmo, formato e volume. Não misture formatos sem testes.
Testes
Cubra:
- assinatura válida;
- mensagem alterada;
- chave errada;
- IV repetido bloqueado pela sua camada;
- JWK inválido;
- Base64URL;
- rotação de chave;
- erro de autenticação;
- Unicode;
- dados vazios.
Use o Node Test Runner e vetores de teste oficiais quando disponíveis.
Erros comuns
- Usar Math.random: tokens ficam previsíveis.
- Reutilizar IV no AES-GCM: a confidencialidade é comprometida.
- Usar SHA-256 para senha: ataques ficam baratos.
- Exportar chave sem necessidade: o segredo pode vazar.
- Confundir Base64 com Base64URL: integrações falham.
- Reconstruir payload assinado: bytes mudam.
- Registrar chaves: logs passam a conter segredos.
Boas práticas
- Use algoritmos modernos.
- Gere aleatoriedade segura.
- Use IV único.
- Restrinja usos da chave.
- Marque chaves como não exportáveis quando possível.
- Proteja material privado.
- Planeje rotação.
- Valide formatos.
- Teste interoperabilidade.
- Faça revisão criptográfica em sistemas críticos.
Conclusão
A Web Crypto API no Node.js oferece uma interface padronizada para hashes, HMAC, AES-GCM, derivação e assinaturas. Ela facilita interoperabilidade com navegadores e trabalha com objetos de chave que explicitam algoritmos e usos.
Criptografia segura depende mais das escolhas do que da chamada de API. IVs únicos, algoritmos atuais, chaves protegidas, formatos testados e rotação planejada são essenciais. Ao combinar a Web Crypto API com gestão de segredos e testes, aplicações Node.js conseguem proteger dados e mensagens com uma base moderna e portável.




