Pular para o conteúdo
ecdsa.com

HMAC vs assinatura digital: segredo ou par de chaves

Os dois comprovam que a mensagem não foi alterada. Só uma assinatura comprova quem a produziu para alguém que não consegue produzir uma por conta própria.
Itens comparadosHMACAssinatura digital

HMAC vs assinatura digital é a decisão de projeto mais importante em qualquer esquema de autenticação de mensagens, e ela diz respeito à confiança, não à velocidade. Uma tag HMAC é calculada e verificada com a mesma chave secreta, então todos que podem verificar também podem forjar. Uma assinatura é feita com uma chave privada e verificada com uma chave pública, então os verificadores não conseguem criar mensagens válidas novas.

Visão geral

HMAC (RFC 2104, FIPS 198-1)Assinatura digital
ChavesUm único segredo compartilhadoA chave privada assina, a chave pública verifica
Quem pode criar uma tag ou assinatura válidaQualquer um que tenha o segredo, incluindo todos os verificadoresApenas o detentor da chave privada
Não repúdio (comprovável a terceiros)NãoSim, desde que haja um vínculo confiável entre a chave e o signatário
Distribuição de chavesO segredo precisa chegar a cada verificador de forma confidencialA chave pública pode ser publicada (JWKS, certificado, DNS)
Tamanho da prova32 bytes para HMAC-SHA25664 bytes (Ed25519, ECDSA P-256), 256–512 bytes (RSA)
Velocidade, um núcleo do Apple M4 Maxcerca de 7 milhões de tags por segundo para mensagens de 256 bytes (OpenSSL 3.6.5)cerca de 60.000 assinaturas Ed25519 ou 87.000 P-256 por segundo
Usos típicosWebhooks, cookies de sessão, AWS SigV4, TOTP, JWTs HS256, MACs de registros do TLS 1.2Certificados e handshakes TLS, tokens de ID do OIDC, assinatura de pacotes, Git, chaves de acesso
Pós-quânticoSeguro com SHA-256 (o algoritmo de Grover só reduz à metade o esforço de pré-imagem)Esquemas clássicos quebrados pelo algoritmo de Shor; exige ML-DSA ou similar

Vazão do HMAC medida com openssl speed -hmac sha256 (cerca de 1,83 GB/s com entradas de 256 bytes); taxas de assinatura com openssl speed.

A diferença está no modelo de confiança

Um HMAC é um hash com chave: H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ m)). O objetivo de segurança dele é que ninguém sem a chave consiga produzir uma tag válida. Isso basta quando exatamente duas partes compartilham a chave e confiam uma na outra para não forjar, porque uma tag válida então significa “a outra parte enviou isto”. Com três ou mais partes, o modelo desmorona: qualquer verificador pode se passar pelo remetente diante dos demais e, em uma disputa, o remetente pode alegar que foi o destinatário quem criou a tag.

Uma assinatura digital separa as capacidades. Publicar a chave de verificação não ajuda ninguém a assinar, então um único emissor pode atender milhares de verificadores, e uma assinatura pode ser apresentada a um auditor ou a um tribunal como prova de origem, desde que a chave tenha sido vinculada ao signatário por um certificado, um diretório de chaves ou um registro de conta. É por causa dessa separação que tokens de identidade consumidos por muitos serviços (tokens de ID do OpenID Connect, tokens de acesso verificados por servidores de recursos) usam assinaturas, e que o vazamento da configuração de um verificador é inofensivo com assinaturas, mas catastrófico com HMAC.

Aspectos práticos

Gerenciamento de chaves. Os segredos de HMAC precisam ser gerados aleatoriamente (no mínimo 256 bits para HMAC-SHA256; a RFC 7518 §3.2 exige isso para HS256), transmitidos de forma confidencial, armazenados por todos os verificadores e rotacionados em todos os lugares ao mesmo tempo ou com um período de carência em que dois segredos são válidos. Nas chaves de assinatura, a metade privada fica em um só lugar, de preferência um HSM ou KMS, e a rotação é feita por identificadores de chave em um conjunto de chaves publicado.

Desempenho e tamanho. Um HMAC são dois cálculos de hash: milhões por segundo por núcleo, com uma tag de 32 bytes. Assinaturas custam dezenas de microssegundos e têm 64 bytes ou mais. Nenhum dos dois importa para a maior parte do tráfego web; importa na autenticação por pacote ou em mensageria interna de volume muito alto, em que HMAC ou um AEAD é a norma.

Armadilhas. O HMAC precisa ser comparado em tempo constante, não pode ser substituído por hash(key ‖ message) (que o ataque de extensão de comprimento do SHA-256 quebra) e precisa autenticar exatamente os bytes recebidos (o corpo bruto da requisição, não um objeto JSON serializado de novo). As assinaturas precisam ser verificadas com um algoritmo fixado pelo verificador, nunca com um escolhido pela mensagem.

O script abaixo, executado com o Node.js 26.10, autentica o mesmo corpo de webhook das duas formas.

Node.js: tag HMAC-SHA256 e assinatura Ed25519 sobre um mesmo corpo de webhook
import { createHmac, generateKeyPairSync, randomBytes, sign, timingSafeEqual, verify } from "node:crypto";

const body = Buffer.from('{"event":"invoice.paid","id":"evt_123"}');

// HMAC: sender and receiver share one secret (in production it comes from a secret store).
const secret = randomBytes(32);
const tag = createHmac("sha256", secret).update(body).digest();
const expected = createHmac("sha256", secret).update(body).digest();
console.log("HMAC-SHA256 tag:", tag.length, "bytes, valid:", timingSafeEqual(tag, expected));

// Signature: sender keeps the private key, receivers hold only the public key.
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
const signature = sign(null, body, privateKey);
console.log("Ed25519 signature:", signature.length, "bytes, valid:", verify(null, body, publicKey, signature));
Saída
HMAC-SHA256 tag: 32 bytes, valid: true
Ed25519 signature: 64 bytes, valid: true

O segredo do HMAC precisa ser compartilhado com todos os verificadores; a chave privada Ed25519 nunca sai do remetente, e só a chave pública de 32 bytes é distribuída.

Webhooks e APIs na prática

A maioria dos provedores de webhooks usa HMAC porque cada inscrito recebe o próprio segredo: o GitHub envia X-Hub-Signature-256: sha256= seguido de um HMAC-SHA256 do corpo em hexadecimal, e a Stripe assina um timestamp mais o corpo no cabeçalho Stripe-Signature, para que os destinatários possam rejeitar repetições. Como o segredo de cada inscrito é único, o problema de múltiplas partes não aparece. Webhooks assinados, construídos sobre assinaturas de mensagens HTTP (RFC 9421) ou JWS, surgem quando um remetente fala com muitos destinatários que não devem compartilhar um segredo, ou quando os destinatários querem verificar sem provisionar segredos. A versão 4 da assinatura de requisições da AWS (SigV4) é baseada em HMAC; o OAuth DPoP e o Web Bot Auth usam assinaturas porque o verificador não deve ser capaz de se passar pelo cliente.

Quando escolher cada um

  • O mesmo serviço emite e verifica (cookies de sessão, tokens CSRF, JWTs internos): HMAC-SHA256.
  • Um remetente, um destinatário, segredo compartilhado fora de banda (webhooks por cliente): HMAC com um timestamp nos dados assinados.
  • Um emissor, muitos verificadores (tokens de identidade, APIs públicas, aplicativos móveis que verificam dados do servidor): assinaturas, com as chaves publicadas em um JWKS.
  • Prova para terceiros (contratos, logs de auditoria, versões de software): assinaturas mais carimbos do tempo confiáveis.
  • Clientes que não devem conseguir se passar uns pelos outros (DPoP, autenticação de bots, atestado de dispositivos): assinaturas com chaves por cliente.
  • Taxas de mensagens muito altas em uma conexão confiável: HMAC ou um AEAD.

Notas de migração

A migração de HMAC para assinaturas costuma acontecer quando surge um segundo verificador ou quando um segredo vaza.

  1. Introduza um identificador de algoritmo se o seu formato não tiver um (o alg do JOSE, um parâmetro alg de assinatura HTTP, um prefixo de versão), para que os verificadores saibam qual esquema uma mensagem usa sem confiar cegamente nela.
  2. Publique a chave pública com um identificador de chave (JWKS, diretório de chaves de assinatura HTTP) e dê tempo para os verificadores a buscarem.
  3. Assine duas vezes durante a transição: envie tanto o cabeçalho HMAC quanto a assinatura e deixe os verificadores darem preferência à assinatura.
  4. Remova o segredo compartilhado dos verificadores que só precisam verificar; esse é o ganho de segurança, então não o mantenha “por via das dúvidas”.
  5. Nunca deixe uma mesma chave servir aos dois esquemas. Uma chave pública RSA reutilizada como segredo de HMAC é exatamente a confusão de algoritmo que quebrou bibliotecas de JWT; veja HS256 vs RS256.

Referências

Todas as comparações