HMAC (Hash-based Message Authentication Code) применяется для проверки целостности и подлинности сообщений между клиентом и сервером. В контексте API это один из самых распространённых механизмов подписи запросов, позволяющий убедиться, что:
В Stanford JavaScript Crypto Library (SJCL) HMAC реализован поверх криптографических хэшей (чаще всего SHA-256) и используется как базовый строительный блок для схем подписи API-запросов.
HMAC строится вокруг двух ключевых элементов:
K, известного только клиенту и
серверуH, например SHA-256Формально HMAC можно представить как:
(K, m) = H((K opad) ,|, H((K ipad) ,|, m))
Где:
m — сообщение (например, HTTP-запрос)opad, ipad — фиксированные паддинги⊕ — побитовое XOR|| — конкатенацияВ SJCL все эти операции инкапсулированы, и разработчику не нужно реализовывать их вручную.
В библиотеке SJCL используются следующие модули:
sjcl.hash.sha256 — реализация SHA-256sjcl.misc.hmac — реализация HMAC поверх хэшаsjcl.codec.hex, sjcl.codec.base64 —
кодирование выходных данныхСоздание HMAC-объекта выглядит следующим образом:
var hmac = new sjcl.misc.hmac(secretKey, sjcl.hash.sha256);
Где:
secretKey — ключ в формате SJCL bitArraySJCL работает с внутренним форматом bitArray, поэтому
строковые ключи необходимо преобразовывать:
var key = sjcl.codec.utf8String.toBits("super_secret_key");
Далее этот ключ используется в HMAC:
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha256);
Для API-запросов обычно подписывается каноническая строка запроса. Она может включать:
Пример формирования строки:
var method = "POST";
var path = "/api/v1/payment";
var timestamp = Date.now().toString();
var body = JSON.stringify({ amount: 100, currency: "USD" });
var canonical = method + "\n" + path + "\n" + timestamp + "\n" + body;
SJCL требует преобразования строки в bitArray перед хэшированием:
var dataBits = sjcl.codec.utf8String.toBits(canonical);
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha256);
var result = hmac.encrypt(dataBits);
Результат HMAC — это bitArray, который обычно кодируется в hex или base64:
var signatureHex = sjcl.codec.hex.fromBits(result);
var signatureBase64 = sjcl.codec.base64.fromBits(result);
В API чаще используется base64 или hex, в зависимости от соглашения.
Типичная схема выглядит следующим образом:
Пример клиента:
var key = sjcl.codec.utf8String.toBits("secret123");
function signRequest(method, path, body, timestamp) {
var canonical =
method + "\n" +
path + "\n" +
timestamp + "\n" +
body;
var bits = sjcl.codec.utf8String.toBits(canonical);
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha256);
var signature = hmac.encrypt(bits);
return sjcl.codec.base64.fromBits(signature);
}
Подпись обычно передаётся через заголовки:
Authorization: HMAC-SHA256 <signature>
X-Timestamp: 1710000000000
или расширенный вариант:
X-API-Key: public_key
X-Signature: <signature>
X-Timestamp: <timestamp>
Сервер выполняет обратную операцию:
Важно использовать строго одинаковую канонизацию, иначе подписи будут отличаться.
SJCL оперирует bitArray, поэтому смешивание строк и
бинарных данных приводит к ошибкам.
Неправильно:
hmac.encrypt("text");
Правильно:
hmac.encrypt(sjcl.codec.utf8String.toBits("text"));
Даже один лишний пробел или изменение порядка параметров делает подпись недействительной.
Без timestamp и nonce HMAC защищает только
от подмены, но не от повторной отправки запроса.
Дополнительные поля обычно добавляются в canonical string:
var nonce = Math.random().toString(36).substring(2);
var timestamp = Date.now().toString();
Canonical:
method + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + body;
Это значительно усложняет повторное использование подписи.
SJCL написан на чистом JavaScript и не использует нативные криптографические API, поэтому:
Для сравнения, в браузере WebCrypto API выполняет HMAC значительно быстрее, но SJCL остаётся полезен там, где требуется единый кросс-платформенный код.
SJCL HMAC полностью соответствует стандарту RFC 2104, поэтому:
crypto.createHmacconst crypto = require("crypto");
const hmac = crypto.createHmac("sha256", "secret123");
hmac.update("message");
console.log(hmac.digest("base64"));
Этот результат будет совпадать с SJCL при одинаковых входных данных.
Типичная архитектура включает:
SJCL выступает как универсальный криптографический слой, позволяющий реализовать эту схему полностью на стороне JavaScript без внешних зависимостей.