Geração de assinatura HMAC
Geração de assinatura HMAC
Todos os eventos enviados pelos webhooks do Banco Liquidante possuem uma assinatura HMAC.
A assinatura HMAC é um algoritmo que assina as mensagens, assegurando a procedência dos eventos enviados e protegendo o endpoint do parceiro contra ataques de aplicações maliciosas.
Para validar a mensagem recebida, o parceiro também deverá gerar uma assinatura HMAC com o mesmo algoritmo enviado pelo Banco Liquidante.
AtençãoAo implementar a assinatura HMAC, o parceiro garante a segurança de sua aplicação.
Etapas
Recebimento dos headers
A aplicação cliente receberá mensagens via chamadas HTTP enviadas pela plataforma do Banco Liquidante.
Nessas chamadas, os seguintes headers são enviados para que a aplicação cliente realize a geração da assinatura HMAC:
| Header | Descrição | Valor de exemplo |
|---|---|---|
authorization | Contém a assinatura HMAC. Esse é o valor que deverá ser utilizado pela aplicação do cliente para validar se a mensagem recebida é legítima. O valor é uma HMACSHA256 em base64. | hmac lBywDt0rKwAaJHz3cKxzFw6ptdQHrelLE7kbGAbLZ5A= |
publicKey | Contém a chave pública registrada na plataforma de webhooks do Banco Liquidante para o evento que está sendo enviado. O valor é uma string em base64. | NWUyNjgwZDMtNmE2Ni00YWYzLWJkNjUtMGM2ODMzYzczYzI1 |
nonce | Contém um valor único e aleatório gerado por requisição, o que aumenta a segurança do algoritmo. O valor é um UUID sem hífens. | 972004b06b6b443d8ed71630c9430048 |
requestTimestamp | Contém o Unix Timestamp do momento em que a plataforma do Banco Liquidante efetuou a requisição. | 1615331979 |
idempotency-Key | Contém a chave de idempotência que identifica a comunicação com o parceiro. A aplicação cliente deverá utilizá-la para garantir que uma mesma requisição não seja processada duas vezes. Essa chave não faz parte da assinatura HMAC. | 30811733-2b04-44c3-848d-bfbe2976e480 |
Concatenação de dados
Para construir a assinatura HMAC, a aplicação cliente deve concatenar as informações recebidas nos headers em uma única string, na seguinte ordem:
publicKey: chave pública (recomenda-se gerar uma por evento);requestUri: URI em que a aplicação está recebendo a mensagem do webhook, codificada (percent-encode) e em letras minúsculas;requestTimestamp: data e horário em que o Banco Liquidante efetuou a requisição;nonce: valor aleatório gerado para cada requisição;requestBodyBase64: corpo da requisição em formato raw convertido para base64 antes de ser concatenado.
ImportanteOs campos devem seguir exatamente a ordem apresentada acima e utilizar o separador
&entre cada valor.
Exemplo de concatenação
const preHashedString = `${publicKey}&${requestUri}&${requestTimestamp}&${nonce}&${requestBodyBase64}`Algoritmo de hash
O algoritmo criptográfico utilizado nos webhooks do Banco Liquidante é o SHA-256, que produz hashes irreversíveis e exclusivos de 256 bits.
Exemplo de geração de assinatura HMAC
A seguir, um exemplo completo de geração de assinatura a partir de uma chamada HTTP enviada pela plataforma Banco Liquidante.
1. Considere que a requisição HTTP POST é feita para a seguinte URI:
https://url.com.br/api/webhooks
2. O Banco Liquidante enviará os seguintes valores nos headers:
| Header | Valor |
|---|---|
requestUri | https://url.com.br/api/webhooks |
publicKey | NWUyNjgwZDMtNmE2Ni00YWYzLWJkNjUtMGM2ODMzYzczYzI1 |
requestTimestamp | 1615331979 |
nonce | 972004b06b6b443d8ed71630c9430048 |
3. No body da requisição, será enviado o seguinte payload:
[
{
"entityId": "3c92b629-fc89-4d01-9147-48fc5e74d8d0",
"companyKey": "ACompanyKey",
"name": "transaction.hold.was.approved",
"timestamp": "2021-03-09T23:22:00",
"correlationId": "188be708-ff40-48af-b9c1-1ed9e335ad99",
"metadata": { "Foo": "Bar" },
"data": "{\"Bar\":\"31\"}"
}
]
NotaO payload é um array. A aplicação endpoint do cliente deve percorrer todo esse array ao processar a mensagem.
A plataforma de webhooks do Banco Liquidante efetuará uma requisição como esta:
curl --location --request POST 'https://url.com.br/api/webhooks' \
--header 'Authorization: hmac To/SnQDQxo3mmuppqcjycxEfI7jAKREpCKFVy0IikjY=' \
--header 'Nonce: 972004b06b6b443d8ed71630c9430048' \
--header 'PublicKey: NWUyNjgwZDMtNmE2Ni00YWYzLWJkNjUtMGM2ODMzYzczYzI1' \
--header 'RequestTimestamp: 1615331979' \
--header 'Content-Type: application/json' \
--data-raw '[{"entityId":"3c92b629-fc89-4d01-9147-48fc5e74d8d0","companyKey":"ACompanyKey","name":"transaction.hold.was.approved","timestamp":"2021-03-09T23:22:00","correlationId":"188be708-ff40-48af-b9c1-1ed9e335ad99","metadata":{"Foo":"Bar"},"data":"{\"Bar\":\"31\"}"}]'4. Ao receber a requisição, o parceiro concatena todos os valores em uma string:
String antes da geração do HMAC
const separator = "&";
const preHashedString = `${publicKey}${separator}${requestUri}${separator}${requestTimestamp}${separator}${nonce}${separator}${requestBodyBase64}`
// Resultado:
// NWUyNjgwZDMtNmE2Ni00YWYzLWJkNjUtMGM2ODMzYzczYzI1&https%3a%2f%2furl.com.br%2fapi%2fwebhooks&1615331979&972004b06b6b443d8ed71630c9430048&W3siZW50aXR5SWQiOi...5. Com a string montada, o parceiro gera a assinatura HMAC em base64, utilizando:
privateKey: chave privada idêntica à informada na configuração do webhook — não deve ser passada em base64;preHashedString: string do passo anterior, em formato UTF-8.
Exemplo de geração da assinatura
const signature = crypto
.createHmac('sha256', new Buffer.from(privateKey, 'base64').toString())
.update(preHashedString, 'utf8')
.digest('base64')
console.log(signature);
// x7QO2Q4SBVtLFnCO4MJWDr76qm049hjuc18zXZCBbto=
ImportanteA
privateKeynunca é enviada nas requisições pelo Banco Liquidante. Ela deve ser armazenada pelo parceiro de forma segura e utilizada somente no momento da geração do hash HMAC.
6. A assinatura gerada pelo parceiro deve ser idêntica ao valor do header authorization enviado pelo Banco Liquidante.
AtençãoSomente se as duas assinaturas forem idênticas, o parceiro deve aceitar a mensagem. Caso contrário, deve retornar o status code
401para a plataforma Banco Liquidante.
Exemplo de código completo
var crypto = require('crypto');
const privateKey = "NTRlNzM0NGMtNTdmMC00MjQ4LThiZTEtM2ZhMkDg4NzcwZTA5";
const publicKey = "MGE4NDIwM2ItNmU5Yi00Zjk0LWkE5NmEtNWIwMDdiOGVjMjJj";
const partnerUri = "https://url.com.br/api/webhooks";
const receivedHMAC = "wOeh+Yb1UoITqzQmvjafauh2kOp/w5kAEkqomsEhj8NQ=";
// Dados variáveis
const requestTimestamp = "1637839252";
const nonce = "ff4bb8520918k48f1a896d6f92d1e7605";
const requestBodyBase64 = "W3siZW50aXR5SWQiOi..."; // corpo da requisição em base64
const requestUriEncoded = encodeURIComponent(partnerUri).toLowerCase();
const separator = "&";
const preHashedString = `${publicKey}${separator}${requestUriEncoded}${separator}${requestTimestamp}${separator}${nonce}${separator}${requestBodyBase64}`;
const signature = crypto
.createHmac('sha256', new Buffer.from(privateKey, 'base64').toString())
.update(preHashedString, 'utf8')
.digest('base64');
console.log(signature);
if (receivedHMAC === signature) {
console.log("Chave HMAC válida");
}Evitando um replay attack
Um replay attack (ataque de repetição) ocorre quando uma entidade maliciosa intercepta e reenvia uma transmissão de dados válida. Como os dados originais são legítimos, os protocolos de segurança da rede tratam o ataque como uma transmissão normal.
Os webhooks do Banco Liquidante disponibilizam dois campos que podem ser utilizados para mitigar esse tipo de ataque: nonce e requestTimestamp, ambos presentes no header da requisição.
Exemplo
Se o webhook do Banco Liquidante gerar o nonce "abcd1234" e enviá-lo no header, o servidor pode verificar se esse valor já foi utilizado anteriormente.
O servidor pode armazenar o nonce em cache por 5 minutos. Qualquer requisição recebida com o mesmo nonce dentro desse intervalo pode ser considerada um replay attack e rejeitada.
Após 5 minutos, o mesmo nonce pode ser aceito sem problema. Porém, como um agente malicioso poderia reenviar a requisição após esse intervalo, o campo requestTimestamp complementa a proteção: se o timestamp da requisição for mais antigo que 5 minutos em relação ao horário atual, a requisição pode ser rejeitada pelo servidor.
Como o requestTimestamp faz parte dos dados brutos da assinatura HMAC, qualquer tentativa de alterá-lo resultará em uma assinatura diferente, que não corresponderá à enviada pelo Banco Liquidante.
NotaO exemplo acima não é uma regra. A implementação da proteção contra replay attack fica a critério do parceiro.
Idempotency
Seguindo os princípios de design REST, a API do Banco Liquidante foi projetada para ser robusta em caso de falha.
Em transações financeiras, é fundamental que interrupções na comunicação não resultem em perda ou duplicação de dados. O sistema é projetado para que uma solicitação execute a ação desejada exatamente uma vez, independentemente de quantas vezes ela seja repetida. Esse conceito é chamado de Idempotência.
O Banco Liquidante oferece suporte a solicitações idempotentes por meio do header idempotency-Key: {{key}} incluído em cada requisição.
Responsabilidades do parceiro
O endpoint webhook do parceiro deve garantir a idempotência, armazenando a chave de idempotência enviada pelo sistema de webhooks do Banco Liquidante em cada solicitação.
A chave de idempotência deve estar no formato UUID (32 dígitos hexadecimais no padrão 8-4-4-4-12, por exemplo: 01234567-9abc-def0-1234-56789abcdef0) e ser única, para evitar conflitos ou duplicações.
NotaAs chaves de idempotência devem ser armazenadas por um período mínimo de 7 dias.
