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
bash4+ (suporte a$(...)e arrays)openssl≥ 1.1 (paradgst -sha256 -hmac)curl7+- Relógio do sistema sincronizado por NTP (janela ±300s)
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}"
--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
-
$(date +%s)retorna UTC em Linux/macOS, mas em alguns BSDs antigos retorna local — confira comdate -u +%sse desconfiar. -
Sempre use aspas duplas em
PAYLOADpara preservar a quebra de linha literal entre"${METHOD} ${PATH_Q}"e"${BODY}". Aspas simples viram o\nem literal. -
printf '%s'não adiciona\nfinal, diferente deecho. Useprintf. -
Se o body veio de um arquivo
(
BODY=$(cat req.json)), confira se o arquivo não tem BOM UTF-8 ou CRLF do Windows. -
Em
$(...)o bash remove o último newline automaticamente — útil aqui (queremos exatamente isso), mas surpreendente se for binary.
Troubleshooting
| Erro recebido | Causa provável | O 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.