Подпись данных в Jsrsasign строится вокруг идеи фиксации целостности и подлинности произвольного сообщения. Подписываемый payload не обязан быть структурированным в формате JWT — это может быть строка, JSON, бинарные данные в base64-представлении или результат сериализации бизнес-объекта.
Ключевой момент заключается в том, что подпись всегда вычисляется над байтовым представлением строки, поэтому критично контролировать этап преобразования данных в каноническую форму.
Перед применением криптографических алгоритмов выполняется нормализация данных:
Типичная проблема подписи произвольного payload — различие строк при одинаковом семантическом содержимом:
const payload1 = JSON.stringify({ a: 1, b: 2 });
const payload2 = JSON.stringify({ b: 2, a: 1 });
Несмотря на идентичность данных, строки различаются, а значит подписи будут разными.
Для стабилизации используется ручная канонизация:
function canonicalize(obj) {
return JSON.stringify(obj, Object.keys(obj).sort());
}
Jsrsasign предоставляет два уровня API:
KJUR.jws.JWS)KJUR.crypto.Signature)Для произвольных payload чаще применяется низкоуровневый API.
HMAC используется, когда один и тот же секрет применяется для подписи и проверки.
const msg = "data-to-sign";
const secret = "shared-secret";
const hmac = new KJUR.crypto.Mac({ alg: "HmacSHA256", pass: secret });
hmac.updateString(msg);
const signature = hmac.doFinal();
Результат signature представлен в hex-формате. Для
передачи по сети часто используется Base64:
const b64 = hextob64(signature);
RSA требует приватного ключа в формате PEM.
const msg = "important-message";
const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(privateKeyPem);
sig.updateString(msg);
const signatureHex = sig.sign();
Преобразование в Base64:
const signatureB64 = hextob64(signatureHex);
const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(publicKeyPem);
sig.updateString(msg);
const isValid = sig.verify(signatureHex);
ECDSA часто используется для компактных ключей и высокой производительности.
const sig = new KJUR.crypto.Signature({ alg: "SHA256withECDSA" });
sig.init(privateKeyPem);
sig.updateString(msg);
const signature = sig.sign();
Проверка:
const verifier = new KJUR.crypto.Signature({ alg: "SHA256withECDSA" });
verifier.init(publicKeyPem);
verifier.updateString(msg);
const valid = verifier.verify(signature);
Jsrsasign по умолчанию работает со строками, но многие сценарии требуют явного управления байтами.
Проблема возникает при подписании:
Используется updateHex:
const msgHex = "616263"; // "abc" в hex
const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(privateKeyPem);
sig.updateHex(msgHex);
const signature = sig.sign();
Иногда требуется подписывать уже вычисленный digest.
const md = new KJUR.crypto.MessageDigest({ alg: "sha256", prov: "cryptojs" });
md.updateString("payload");
const hashHex = md.digest();
Далее возможна подпись через RAW режим:
const sig = new KJUR.crypto.Signature({ alg: "NONEwithRSA" });
sig.init(privateKeyPem);
sig.updateHex(hashHex);
const signature = sig.sign();
При работе с произвольными структурами данных критично учитывать:
Пример нормализации:
function normalizePayload(obj) {
return JSON.stringify(obj, Object.keys(obj).sort(), 0);
}
const payload = {
userId: 123,
role: "admin",
timestamp: 1710000000
};
const data = JSON.stringify(payload, Object.keys(payload).sort());
const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(privateKeyPem);
sig.updateString(data);
const signature = sig.sign();
const verifier = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
verifier.init(publicKeyPem);
verifier.updateString(data);
const valid = verifier.verify(signature);
При интеграции с веб-протоколами часто используется Base64URL:
function toBase64Url(hex) {
return hextob64(hex)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
Разные сериализации JSON приводят к разным подписям даже при одинаковых данных.
UTF-16 vs UTF-8 меняет байтовое представление строки.
Объекты без фиксированного порядка ключей не пригодны для прямой подписи.
Передача объекта без сериализации приводит к
[object Object] в строковом виде.
KJUR.crypto.Signature предоставляет универсальный
механизм:
Базовая структура использования:
const sig = new KJUR.crypto.Signature({ alg: "alg-name" });
sig.init(key);
sig.updateString(data);
const result = sig.sign();
Подпись произвольного payload в Jsrsasign фактически сводится к трём этапам:
Эта модель универсальна для: