O problema: como confiar numa requisição
Quando um servidor recebe uma requisição de API, como ele sabe que quem está chamando é quem diz ser, e que ninguém alterou a requisição no caminho? Mandar um segredo compartilhado em cada requisição funcionaria, até o dia em que esse segredo fosse parar num log, num cache ou num vazamento. A assinatura de requisições com (hash-based message authentication code) resolve isso sem nunca transmitir o segredo: quem chama prova que tem o segredo ao usá-lo para assinar a requisição, e o servidor confere a assinatura.
Como funciona a assinatura
Os dois lados compartilham uma chave secreta. Para assinar uma requisição, o cliente:
- Monta uma string canônica com as partes da requisição que não podem mudar, normalmente o método HTTP, o path, os headers (cabeçalhos) principais, um timestamp (a marca de data e hora) e um hash do corpo. "Canônica" quer dizer que os dois lados montam essa string exatamente do mesmo jeito, combinado entre eles, para que os dois façam o cálculo sobre uma entrada idêntica.
- Calcula
HMAC-SHA256(string canônica, segredo)e anexa o resultado à requisição, geralmente num header comoAuthorizationouX-Signature.
O servidor, que tem o mesmo segredo, remonta a string canônica a partir da requisição que recebeu e calcula ele mesmo o HMAC. Se o valor dele bater com o que veio no header, duas coisas ficam provadas de uma vez: quem chamou tem o segredo (autenticidade), e nada do que a assinatura cobre foi alterado (integridade). O segredo em si nunca trafega.
Um exemplo real: AWS Signature Version 4
O SigV4 da Amazon é o exemplo mais conhecido. Cada requisição é assinada com HMAC-SHA256 sobre uma requisição canônica, usando uma chave de assinatura derivada da chave de acesso secreta (a secret access key), da data, da região e do serviço. O servidor deriva a chave de novo e recalcula a assinatura; se não bater, a requisição é rejeitada. O mesmo padrão (canonizar, calcular o HMAC, comparar) está por trás de inúmeros outros esquemas de autenticação de API.
Webhooks: a mesma ideia, ao contrário
Os webhooks invertem a direção. Um provedor (um processador de pagamentos, um host Git) chama o seu endpoint para avisar de um evento e assina o payload (a carga útil) com um segredo que vocês dois compartilham, mandando o HMAC num header. O seu endpoint recalcula o HMAC sobre o corpo recebido e compara, e é assim que você rejeita chamadas forjadas por qualquer um que não tenha o segredo. Faça sempre a comparação em tempo constante, para não vazar informação pelo tempo de resposta.
Proteção contra replay
Se um atacante capturar uma requisição assinada e válida, ele pode fazer o replay dela, ou seja, reenviá-la exatamente como está. As defesas padrão são incluir um timestamp nos dados assinados e rejeitar as requisições que caírem fora de uma janela curta e, opcionalmente, usar um nonce (um valor de uso único que o servidor guarda por pouco tempo), para que a mesma requisição não seja aceita duas vezes. A assinatura prova a autenticidade; o timestamp e o nonce impedem que uma requisição antiga, mas genuína, seja reaproveitada.
A ferramenta HMAC calcula o HMAC-SHA256 (e também o -384/512) que está no centro de todos esses esquemas, sobre uma mensagem e uma chave fornecidas por você, inteiramente no seu navegador.