Skip to Content

Подпись запросов — HMAC-SHA256 v3

Формула одинакова для XML и JSON, отличается только формат timestamp и способ передачи полей.

Формула
inner = HMAC_SHA256(secret, timestamp) signature = lowercase( HMAC_SHA256(secret, login + inner) )
ПротоколtimestampГде передаётся
XMLyyyyMMddHHmmss UTC<Header><Authentication>: Login, NowUtc, Signature
JSONISO-8601 UTC (2026-06-25T12:00:00Z)Заголовки X-Login, X-Timestamp, X-Signature, X-Signature-Version: 3

Допустимый дрейф времени ±20 минут. Выход за предел → XML ResultCode=101 / JSON HTTP 401.

Пошаговый пример

Самая частая причина 401 — подпись, посчитанная «почти правильно». Сверьте свою реализацию с этим примером: он использует фиксированные значения, поэтому у вас должны получиться те же две hex-строки.

  1. Берём фиксированные входные данные: secret = "password", login = "login", timestamp = "2025-02-10T17:56:37Z".
  2. Считаем промежуточный хеш inner — HMAC-SHA256, где ключ — секрет, а сообщение — timestamp: 615a772b164395682438043ed16a4de88cae293dde5eb3b37d6f60701e8762ec.
  3. Склеиваем строки: login + inner, то есть login615a772b…8762ec. Именно в этом порядке и без разделителей.
  4. Считаем подпись — снова HMAC-SHA256 с тем же ключом-секретом, сообщение — результат шага 3: 5f9e4f4a7a805b898cac96a6c3e7a81dd0e06e200c291d51663e6f250b10853f.

Где обычно ошибаются: подписывают login + timestamp вместо двух проходов HMAC; на втором проходе берут ключом не секрет, а что-то другое; конкатенируют inner + login вместо login + inner; передают inner в верхнем регистре или в виде сырых байт вместо hex-строки; подписывают одно значение timestamp, а в запрос кладут другое, пересчитав его заново.

Вычисление подписи

# bash + openssl SECRET=demo_password LOGIN=demo_login NOW=$(date -u +%Y%m%d%H%M%S) # XML TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) # JSON INNER=$(printf '%s' "$NOW" \ | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}') SIG=$(printf '%s%s' "$LOGIN" "$INNER" \ | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}') echo "$SIG"

Тест-векторы

Прогоните векторы до первого сетевого вызова: несовпадение двух hex-строк локально отлаживается за минуту, 401 от сервера — нет. Если хотя бы один вектор не сошёлся — не отправляйте запрос: сравните ваш inner и склеенное сообщение с напечатанными, различие будет в формате timestamp, порядке конкатенации или регистре hex.

Полный набор — callisto_sigtest.json , там же негативные векторы: значения, которые сервер отвергает; реализация, «принимающая» их, неверна. Векторы используют публичные доступы песочницы, поэтому после локальной сверки те же значения работают в живых запросах. Тело запроса в подпись не входит — вектор v3-body-not-signed это доказывает: с телом и без тела подпись одна и та же, кириллица в description ни на что не влияет.

Фрагмент sigtest.json
{ "id": "v3-json-valid", "login": "demo_login", "secret": "demo_password", "timestamp": "2026-02-10T17:56:37Z", "inner": "ab4ea3fdd8fecb8dd57f19e087fcab319eb344629b0e8502622fcf230d09a6ee", "outer_message": "demo_loginab4ea3fdd8fecb8dd57f19e087fcab319eb344629b0e8502622fcf230d09a6ee", "signature": "0c902262f3cfc2282b05a2ccf38f59cce766617badf15c458eb32547f0a0841e" } { "id": "v3-xml-valid", "login": "demo_login", "secret": "demo_password", "timestamp": "20260210175637", "inner": "6d722ac72da42f825167f61268f6dbdf0246c18f97889b71da844293367dc5f6", "signature": "2f412b1a3b08a2ae3a5a427753bff4fd2d719e911d1eaa057d2cfa9e598dc163" } { "id": "v3-negative-single-pass", "valid": false, "why": "один проход HMAC по login+timestamp — сервер ответит 401", "signature": "c5261f39339d743804df72d2141653e8f0dc718cd5033f4e04899fed45be067b" }