HMAC em Bash

Receita openssl + curl — passo a passo

HMAC-SHA256 em Bash (openssl + curl)

Receita reutilizável em shell POSIX. Funciona em Linux, macOS e WSL. Para testes manuais e scripts de CI sem dependências além do openssl e do curl.

Pré-requisitos

1. GET sem body — o caso mais simples

Exemplo: chamar GET /v1/ping para validar suas credenciais. Substitua API_KEY e API_SECRET pelos valores que você recebeu da Sivoe (ou pela chave sandbox pública do portal).

#!/usr/bin/env bash
set -euo pipefail

API_KEY="svp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
API_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE_URL="https://api.sivoe.med.br"

TS=$(date +%s)
METHOD="GET"
PATH_Q="/v1/ping"
BODY=""

# IMPORTANTE: o separador entre "METHOD PATH" e o body e uma unica
# quebra de linha LF (\n). As aspas duplas em PAYLOAD preservam o
# newline literal entre as duas linhas.
PAYLOAD="${TS}.${METHOD} ${PATH_Q}
${BODY}"

SIG=$(printf '%s' "$PAYLOAD" \
    | openssl dgst -sha256 -hmac "$API_SECRET" \
    | awk '{print $2}')

curl -i \
    -H "X-API-Key: $API_KEY" \
    -H "X-Timestamp: $TS" \
    -H "X-Signature: $SIG" \
    "${BASE_URL}${PATH_Q}"

Saída esperada (resumo)

HTTP/1.1 200 OK
Content-Type: application/json
X-Api-Version: v1
Cache-Control: no-store

{
  "data": { "ok": true, "service": "Sivoe API Gateway", "version": "v1", ... },
  "meta": { "request_id": "...", "duracao_ms": 12 },
  "error": null
}

2. POST com body JSON

Quando você envia body, ele faz parte do payload assinado. Assine exatamente o que será transmitido — sem espaços extras, sem reformatação, sem BOM.

API_KEY="svp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
API_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE_URL="https://api.sivoe.med.br"

TS=$(date +%s)
METHOD="POST"
PATH_Q="/v1/ping"

# Body em uma unica variavel -- assine e envie exatamente isto.
BODY='{"hello":"world","numero":42}'

PAYLOAD="${TS}.${METHOD} ${PATH_Q}
${BODY}"

SIG=$(printf '%s' "$PAYLOAD" \
    | openssl dgst -sha256 -hmac "$API_SECRET" \
    | awk '{print $2}')

curl -i \
    -H "X-API-Key: $API_KEY" \
    -H "X-Timestamp: $TS" \
    -H "X-Signature: $SIG" \
    -H "Content-Type: application/json" \
    --data-binary "$BODY" \
    "${BASE_URL}${PATH_Q}"
Por que --data-binary e não -d? A flag -d do curl tira quebras de linha do body por padrão, o que muda o conteúdo enviado em relação ao que você assinou. --data-binary envia byte a byte, idêntico ao que está na variável.

3. GET com query string

A query string faz parte do path_com_query. Coloque-a dentro de PATH_Q antes de calcular o HMAC.

TS=$(date +%s)
METHOD="GET"
PATH_Q="/v1/exames?status_laudo=EMITIDO&limit=10"
BODY=""

PAYLOAD="${TS}.${METHOD} ${PATH_Q}
${BODY}"

SIG=$(printf '%s' "$PAYLOAD" \
    | openssl dgst -sha256 -hmac "$API_SECRET" \
    | awk '{print $2}')

# Aspas em torno da URL impedem o shell de interpretar &
curl -i \
    -H "X-API-Key: $API_KEY" \
    -H "X-Timestamp: $TS" \
    -H "X-Signature: $SIG" \
    "${BASE_URL}${PATH_Q}"

Pegadinhas comuns

Troubleshooting

Erro recebidoCausa provávelO que checar
AUTH_INVALID_SIGNATURE HMAC não bate Aspas duplas no PAYLOAD; query string em PATH_Q; body sem reformatação; secret correto
AUTH_TIMESTAMP_EXPIRED Relógio fora da janela ±300s date -u +%s bate com tempo do servidor? Ative NTP.
AUTH_INVALID_API_KEY_FORMAT api_key mal formada Deve casar ^svp_live_[0-9a-f]{32}$
AUTH_INSUFFICIENT_SCOPE Chave não tem o escopo do endpoint Solicite o escopo ao suporte Sivoe (contato@roomtec.com.br)

Catálogo completo: Códigos de erro. Valide a assinatura manualmente em Try it out com a chave sandbox.