Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Assinaturas HMAC no Node.js

Atualizado em: 31 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

As Assinaturas HMAC no Node.js permitem verificar se uma mensagem foi criada por quem possui um segredo compartilhado e se o conteúdo não foi alterado. O mecanismo é comum em webhooks, callbacks de pagamento, integração entre serviços, links assinados e APIs que precisam autenticar o corpo da requisição.

Uma implementação segura precisa assinar os bytes exatos, incluir timestamp e identificador, usar algoritmo moderno, comparar em tempo constante, bloquear replay e rotacionar segredos. Recriar JSON antes de verificar, aceitar timestamps antigos ou registrar a chave pode tornar a assinatura inútil.

Neste guia, você aprenderá a criar e validar HMAC com node:crypto, definir um formato canônico, assinar webhooks, proteger contra replay, rotacionar chaves, lidar com proxies e testar o fluxo.

O que é HMAC?

HMAC significa Hash-based Message Authentication Code. A RFC 2104 define o mecanismo. A documentação do módulo node:crypto descreve createHmac(), chaves e comparações criptográficas.

O que HMAC garante?

  • integridade da mensagem;
  • autenticidade entre partes que compartilham o segredo;
  • detecção de alterações no conteúdo.

HMAC não cifra a mensagem. Quem intercepta o tráfego ainda pode ler o conteúdo se TLS não estiver sendo usado.

HMAC versus hash

Um hash simples não usa segredo:

SHA256(message)

Qualquer pessoa pode recalcular. HMAC inclui uma chave:

HMAC-SHA256(secret, message)

Somente quem possui o segredo pode gerar uma assinatura válida.

Criando uma assinatura

import { createHmac } from 'node:crypto';

function signHmac(secret, payload) {
  return createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
}

O payload pode ser uma string ou Buffer. Para webhooks, prefira os bytes brutos recebidos.

Gerando um segredo

import { randomBytes } from 'node:crypto';

const secret = randomBytes(32).toString('base64url');

Use CSPRNG e entropia suficiente. Não derive o segredo de nome da empresa, senha curta ou ID previsível.

Armazenamento do segredo

Mantenha a chave em gerenciador de segredos, não no código ou banco comum. Consulte Gestão de Segredos no Node.js.

Formato da assinatura

Um header pode carregar versão e digest:

X-Signature: v1=8a35...

A versão permite mudar o formato ou algoritmo sem quebrar todas as integrações.

Timestamp

X-Signature-Timestamp: 1788170400

Inclua o timestamp no material assinado. Assim, um atacante não pode alterá-lo para ampliar a janela.

Mensagem assinada

const signedPayload = `${timestamp}.${rawBody}`;
const signature = signHmac(secret, signedPayload);

O emissor e o receptor precisam usar exatamente o mesmo formato.

Identificador do evento

Inclua event ID ou message ID:

const signedPayload = [
  timestamp,
  eventId,
  rawBody
].join('.');

O ID ajuda na idempotência e detecção de replay.

Corpo bruto

Assine e valide os bytes originais. Parsear JSON e serializar novamente pode mudar espaços, ordem de campos ou representação de números.

Express com raw body

app.post(
  '/webhooks/provider',
  express.raw({ type: 'application/json' }),
  handleWebhook
);

Depois da validação, converta:

const body = JSON.parse(req.body.toString('utf8'));

Fastify

Configure plugin ou parser que preserve o raw body. Faça isso apenas nas rotas que precisam, para evitar duplicar buffers em toda a aplicação.

Limite do corpo

Defina tamanho máximo antes de ler. Uma assinatura válida não deve permitir um payload ilimitado.

Comparação em tempo constante

import { timingSafeEqual } from 'node:crypto';

function secureEqualHex(expectedHex, receivedHex) {
  const expected = Buffer.from(expectedHex, 'hex');
  const received = Buffer.from(receivedHex, 'hex');

  return expected.length === received.length
    && timingSafeEqual(expected, received);
}

Primeiro verifique comprimento para evitar erro da função.

Validando o header

function parseSignature(header) {
  if (typeof header !== 'string') return null;

  const match = /^v1=([a-f0-9]{64})$/.exec(header);
  return match ? match[1] : null;
}

Rejeite formatos desconhecidos, duplicados ou excessivamente longos.

Validação completa

function verifyWebhook({
  secret,
  timestamp,
  eventId,
  rawBody,
  signature
}) {
  const payload = `${timestamp}.${eventId}.${rawBody}`;
  const expected = signHmac(secret, payload);
  return secureEqualHex(expected, signature);
}

Proteção contra replay

Uma assinatura válida pode ser capturada e reenviada. Use timestamp, event ID e registro de processamento.

Janela de tempo

const toleranceSeconds = 300;
const age = Math.abs(nowSeconds - Number(timestamp));

if (age > toleranceSeconds) {
  throw new InvalidSignatureError();
}

Use o relógio do servidor e mantenha hosts sincronizados.

Event ID único

INSERT INTO processed_webhooks (provider, event_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING;

Se nenhuma linha for inserida, o evento já foi recebido.

Consulte Idempotência em APIs Node.js.

Ordem das verificações

  1. Limitar corpo e headers.
  2. Validar formato.
  3. Validar timestamp.
  4. Calcular assinatura.
  5. Comparar.
  6. Registrar event ID.
  7. Processar.

Resposta rápida

Depois de validar e persistir o evento, responda rapidamente e processe de forma assíncrona. Consulte Webhooks no Node.js.

Transação

O registro de idempotência e a mensagem de fila ou outbox devem confirmar juntos quando necessário.

Veja Outbox Pattern no Node.js.

Várias assinaturas

Durante rotação, o emissor pode enviar assinaturas com duas chaves:

X-Signature: v1=antiga,v1=nova

Defina um parser limitado e aceite se uma assinatura válida corresponder a uma chave ativa.

Rotação do segredo

  1. Criar chave nova.
  2. Distribuir ao receptor.
  3. Assinar com nova e antiga.
  4. Confirmar validação.
  5. Parar assinatura antiga.
  6. Revogar a chave.

Key ID

X-Signature-Key-Id: webhook-2026-08

O ID localiza a chave sem expor o segredo. Valide contra uma lista conhecida e limite tentativas.

Segredo por cliente

Não compartilhe uma única chave com todos os parceiros. Uma chave por integração permite revogar e auditar individualmente.

Segredo por ambiente

Teste e produção precisam de chaves diferentes. Eventos de sandbox não devem ser aceitos na rota de produção.

Algoritmo

HMAC-SHA-256 é uma escolha comum. Não permita que o cliente escolha arbitrariamente o algoritmo por header.

Algorithm confusion

A versão do protocolo deve definir o algoritmo. Rejeite versões desconhecidas em vez de fazer fallback inseguro.

Encoding

Escolha hex ou Base64 e use o mesmo padrão nos dois lados. Base64URL é útil em headers, mas não misture variantes.

Unicode

Assinar os bytes brutos evita diferenças de normalização. Quando assinar texto criado pela aplicação, defina UTF-8 explicitamente.

Canonicalização

Para estruturas criadas nos dois lados, use um formato canônico documentado. JSON comum não garante ordem de chaves.

Assinatura de query string

Ordene parâmetros, normalize encoding e inclua método, caminho e expiração:

GET
/downloads/report.pdf
expires=1788170400&user=42

Evite assinar uma URL e validar outra representação equivalente sem normalização.

Inclua prazo curto e escopo do recurso. Não use o mesmo segredo de webhooks para links de download.

Assinatura de requisições de API

Um protocolo pode assinar método, caminho, body hash, timestamp e nonce. Documente exatamente quais headers participam.

HMAC e API keys

Uma API key identifica o cliente; HMAC prova posse do segredo e integridade da requisição. Consulte API Keys no Node.js.

TLS continua obrigatório

HMAC não protege confidencialidade. Use HTTPS e valide certificados. Em integrações de alto risco, considere mTLS no Node.js.

Proxy e body

Proxies podem descomprimir, transformar encoding ou alterar caminho. Defina o ponto em que a assinatura é calculada e preserve os bytes.

Compressão

Assine o corpo antes ou depois da compressão conforme o protocolo. O receptor precisa validar exatamente a representação recebida.

Headers duplicados

Defina comportamento para múltiplos headers de assinatura. Rejeitar ambiguidade costuma ser mais seguro.

Logs

Registre provider, key ID, event ID, idade, resultado e request ID. Nunca registre segredo ou assinatura completa sem necessidade.

Veja Logs com Pino no Node.js.

Auditoria

Criação, rotação, revogação e falhas repetidas devem ser auditadas. Consulte Logs de Auditoria no Node.js.

Métricas

Monitore assinaturas inválidas, timestamps expirados, replay, versões desconhecidas, atraso de entrega e eventos duplicados.

Rate limiting

Limite falhas por origem e integração. Um endpoint de webhook também pode sofrer DoS.

Erros para o cliente

{
  "code": "INVALID_SIGNATURE",
  "message": "Assinatura inválida"
}

Não revele se falhou timestamp, chave, formato ou digest.

Teste de sucesso

test('aceita assinatura válida', () => {
  const signature = signHmac(secret, payload);
  assert.equal(verify(secret, payload, signature), true);
});

Teste de alteração

Mude um byte do corpo e confirme rejeição.

Teste de timestamp

Assine corretamente com timestamp fora da janela. A requisição deve falhar.

Teste de replay

Envie o mesmo event ID duas vezes. O segundo processamento não pode repetir efeitos.

Teste de raw body

Use JSON com espaços e ordem diferente. A validação deve operar sobre os bytes recebidos, não sobre objeto serializado.

Teste de rotação

Durante a janela, assinatura antiga e nova funcionam. Depois da revogação, apenas a nova.

Teste de timing

Confirme que comprimentos inválidos são rejeitados sem chamar timingSafeEqual com buffers diferentes.

Erros comuns

  • Assinar JSON reserializado: bytes mudam.
  • Sem timestamp: replay permanece válido.
  • Timestamp fora da assinatura: atacante altera a janela.
  • Comparação com ===: diferença de tempo pode vazar informação.
  • Segredo compartilhado por todos: revogação afeta todas as integrações.
  • Registrar chave: observabilidade vira fonte de vazamento.
  • Confiar no HMAC sem TLS: conteúdo fica visível.

Boas práticas

  • Use segredo aleatório.
  • Assine bytes brutos.
  • Inclua timestamp e event ID.
  • Use HMAC-SHA-256.
  • Compare em tempo constante.
  • Bloqueie replay.
  • Versione o protocolo.
  • Rotacione com sobreposição.
  • Use chave por integração.
  • Audite sem registrar segredos.

Conclusão

As Assinaturas HMAC no Node.js fornecem autenticidade e integridade para webhooks, APIs e mensagens entre sistemas. A assinatura só é confiável quando cobre a representação exata recebida.

Timestamp, event ID, comparação segura e rotação completam a proteção. Com TLS, idempotência e segredos individuais, HMAC cria um protocolo simples e robusto sem depender de assinaturas que permanecem válidas indefinidamente.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita