Подпись HTTP-запросов используется для подтверждения подлинности клиента, защиты от подмены параметров и предотвращения повторного воспроизведения запросов. В практике веб-безопасности это один из базовых механизмов, применяемых в API, финансовых системах, интеграциях микросервисов и SDK сторонних платформ.
Перед подписанием запрос приводится к детерминированному виду. Это необходимо, чтобы и клиент, и сервер вычисляли подпись над абсолютно одинаковой строкой.
Обычно каноническая строка включает:
Пример канонической строки:
POST
/api/v1/payments
amount=100¤cy=USD
content-type:application/json
x-timestamp:1710000000
x-nonce:abc123
{"amount":100,"currency":"USD"}
Ключевой момент — строгая унификация форматирования: порядок строк, регистр заголовков, отсутствие лишних пробелов.
Jsrsasign предоставляет криптографические примитивы, включая
HMAC-SHA256 через KJUR.crypto.Mac.
Базовый сценарий — подпись запроса с секретным ключом API.
import { KJUR } from "jsrsasign";
function signRequest(secretKey, canonicalString) {
const hmac = new KJUR.crypto.Mac({ alg: "HmacSHA256", key: secretKey });
hmac.updateString(canonicalString);
return hmac.doFinal(); // hex строка
}
Для передачи подписи в HTTP-заголовках обычно используют base64:
import { hextob64 } from "jsrsasign";
const signatureHex = signRequest(secretKey, canonicalString);
const signatureBase64 = hextob64(signatureHex);
Пример формирования заголовков запроса:
const headers = {
"X-API-Key": apiKey,
"X-Signature": signatureBase64,
"X-Timestamp": timestamp,
"X-Nonce": nonce
};
В более защищённых системах используется RSA-SHA256. В этом случае приватный ключ хранится только на стороне клиента или сервиса, а сервер использует публичный ключ для проверки.
Jsrsasign реализует это через KJUR.crypto.Signature.
import { KEYUTIL } from "jsrsasign";
const privateKey = `
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
`;
const rsaKey = KEYUTIL.getKey(privateKey);
import { KJUR } from "jsrsasign";
function signRequestRSA(privateKey, data) {
const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(privateKey);
sig.updateString(data);
return sig.sign(); // hex
}
Преобразование в формат передачи:
import { hextob64 } from "jsrsasign";
const signatureHex = signRequestRSA(rsaKey, canonicalString);
const signature = hextob64(signatureHex);
Полный процесс включает несколько этапов:
function createSignedRequest({ url, method, body, apiKey, secret }) {
const timestamp = Math.floor(Date.now() / 1000);
const nonce = crypto.randomUUID();
const canonical = [
method,
url,
JSON.stringify(body),
`x-timestamp:${timestamp}`,
`x-nonce:${nonce}`
].join("\n");
const hmac = new KJUR.crypto.Mac({ alg: "HmacSHA256", key: secret });
hmac.updateString(canonical);
const signature = hextob64(hmac.doFinal());
return {
headers: {
"X-API-Key": apiKey,
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Signature": signature
},
body
};
}
Сервер повторяет те же вычисления:
Особое внимание уделяется:
Любое расхождение даже в одном символе делает подпись недействительной.
Одной подписи недостаточно. Поэтому в систему добавляются:
Проверка:
if (Math.abs(serverTime - clientTimestamp) > 300) {
throw new Error("Expired request");
}
if (nonceExists(nonce)) {
throw new Error("Replay detected");
}
Частые проблемы при интеграции:
/api vs
/api/)При больших payload часто подписывают не тело напрямую, а его SHA-256 хэш:
import { KJUR } from "jsrsasign";
function hashBody(body) {
return KJUR.crypto.Util.sha256(body);
}
Далее в каноническую строку включается уже хэш:
POST
/api/v1/data
body-hash:9f2c...
x-timestamp:...
Это снижает нагрузку и упрощает сравнение.
Jsrsasign позволяет гибко переключаться между алгоритмами:
Пример ECDSA:
const sig = new KJUR.crypto.Signature({ alg: "SHA256withECDSA" });
sig.init(ecPrivateKey);
sig.updateString(data);
const signature = sig.sign();
Типичная схема HTTP-запроса с подписью:
POST /api/v1/transfer
X-API-Key: abc123
X-Timestamp: 1710000000
X-Nonce: 550e8400-e29b-41d4
X-Signature: Base64(signature)
{
"from": "A",
"to": "B",
"amount": 500
}
Библиотека обеспечивает:
Это позволяет реализовать полный цикл подписи без внешних криптографических зависимостей.
В реальных системах подпись HTTP-запроса является частью многоуровневой защиты:
Каждый слой компенсирует слабости других, создавая устойчивую архитектуру передачи данных.