Подпись запросов — HMAC-SHA256 v3
Формула одинакова для XML и JSON, отличается только формат timestamp и способ передачи полей.
inner = HMAC_SHA256(secret, timestamp)
signature = lowercase( HMAC_SHA256(secret, login + inner) )| Протокол | timestamp | Где передаётся |
|---|---|---|
| XML | yyyyMMddHHmmss UTC | <Header><Authentication>: Login, NowUtc, Signature |
| JSON | ISO-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-строки.
- Берём фиксированные входные данные:
secret = "password",login = "login",timestamp = "2025-02-10T17:56:37Z". - Считаем промежуточный хеш
inner— HMAC-SHA256, где ключ — секрет, а сообщение — timestamp:615a772b164395682438043ed16a4de88cae293dde5eb3b37d6f60701e8762ec. - Склеиваем строки:
login + inner, то естьlogin615a772b…8762ec. Именно в этом порядке и без разделителей. - Считаем подпись — снова HMAC-SHA256 с тем же ключом-секретом, сообщение — результат шага 3:
5f9e4f4a7a805b898cac96a6c3e7a81dd0e06e200c291d51663e6f250b10853f.
Где обычно ошибаются: подписывают login + timestamp вместо двух проходов HMAC; на втором проходе берут ключом не секрет, а что-то другое; конкатенируют inner + login вместо login + inner; передают inner в верхнем регистре или в виде сырых байт вместо hex-строки; подписывают одно значение timestamp, а в запрос кладут другое, пересчитав его заново.
Вычисление подписи
Sandbox
curl
# 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 ни на что не влияет.
Sandbox
{ "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" }