Построение JWS вручную

JWS (JSON Web Signature) представляет собой компактное представление подписанного JSON-объекта, используемого в стандарте JWT. Формат основан на трёх частях, разделённых точками:

header.payload.signature

Каждая часть кодируется в Base64URL, а подпись формируется на основе первых двух сегментов.


Base64URL как основа сериализации

Обычный Base64 не подходит для JWS из-за символов +, / и =. Поэтому используется Base64URL:

  • + заменяется на -
  • / заменяется на _
  • = удаляется

В jsrsasign кодирование выполняется через утилиты:

KJUR.crypto.Util.sha256("data")

или более низкоуровневые преобразования:

hextob64u(...)

Заголовок JWS (JOSE Header)

Заголовок представляет собой JSON-структуру, описывающую алгоритм подписи и параметры:

{
  "alg": "HS256",
  "typ": "JWT"
}

После сериализации JSON преобразуется в строку и кодируется в Base64URL:

const header = {
  alg: "HS256",
  typ: "JWT"
};

const encodedHeader = KJUR.jws.JWS.readSafeJSONString(JSON.stringify(header));
const b64Header = KJUR.crypto.Util.base64urlencode(JSON.stringify(header));

Полезная нагрузка (Payload)

Payload содержит произвольные данные:

{
  "sub": "1234567890",
  "name": "Alice",
  "admin": true
}

Кодирование аналогично заголовку:

const payload = {
  sub: "1234567890",
  name: "Alice",
  admin: true
};

const b64Payload = KJUR.crypto.Util.base64urlencode(JSON.stringify(payload));

Формирование строки подписи

Подпись формируется не от всего токена, а от двух первых частей:

header.payload

В коде:

const signingInput = b64Header + "." + b64Payload;

Именно эта строка используется как вход для криптографической функции.


Подпись HMAC (HS256) через jsrsasign

Для симметричного алгоритма HS256 используется секретный ключ:

const secret = "my-secret-key";

const jws = KJUR.jws.JWS.sign(
  "HS256",
  JSON.stringify(header),
  JSON.stringify(payload),
  secret
);

Результатом будет уже готовый JWS-токен.


Ручное построение HMAC без обёртки JWS

Более низкоуровневый вариант через KJUR.crypto.Mac:

const mac = new KJUR.crypto.Mac({alg: "HmacSHA256", pass: secret});

mac.updateString(signingInput);
const hmacHex = mac.doFinal();

Далее hex переводится в Base64URL:

const signature = hextob64u(hmacHex);

И итоговая сборка:

const token = signingInput + "." + signature;

Подпись RSA (RS256)

Для асимметричной криптографии используется приватный ключ в формате PEM:

const privateKey = `
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
`;

const jws = KJUR.jws.JWS.sign(
  "RS256",
  JSON.stringify(header),
  JSON.stringify(payload),
  privateKey
);

Ручная RSA подпись через KJUR.crypto.Signature

Низкоуровневый вариант:

const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });

sig.init(privateKey);
sig.updateString(signingInput);

const signatureHex = sig.sign();
const signatureB64u = hextob64u(signatureHex);

Финальная сборка:

const jwsToken = signingInput + "." + signatureB64u;

Особенности сериализации JSON

Критически важно учитывать:

  • порядок полей JSON влияет на подпись
  • пробелы и форматирование должны быть одинаковыми
  • рекомендуется использовать JSON.stringify без модификаций

Пример стабильной сериализации:

JSON.stringify(payload)

Дополнительные поля заголовка

Помимо alg и typ, часто используются:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-id-123",
  "cty": "JWT"
}

kid используется для выбора ключа проверки, особенно при ротации ключей.


Итоговая структура сборки JWS

Процесс можно разложить на последовательность:

  1. Создание JSON header
  2. Создание JSON payload
  3. Base64URL(header)
  4. Base64URL(payload)
  5. Конкатенация через точку
  6. Подпись строки
  7. Base64URL(signature)
  8. Сборка финального токена
Base64URL(header) + "." +
Base64URL(payload) + "." +
Base64URL(signature)

Типовые ошибки при ручной сборке

  • использование обычного Base64 вместо Base64URL
  • изменение JSON перед подписью
  • неверная кодировка UTF-8 при формировании строки
  • подпись не строки header.payload, а отдельных частей
  • несоответствие алгоритма (HS256 vs RS256)

Работа с jsrsasign на уровне компонентов

Библиотека предоставляет несколько слоёв абстракции:

  • KJUR.jws.JWS.sign — высокоуровневое создание токена
  • KJUR.crypto.Mac — HMAC-операции
  • KJUR.crypto.Signature — RSA/DSA/ECDSA подписи
  • KJUR.crypto.Util — кодирование/декодирование

Комбинация этих уровней позволяет строить JWS полностью вручную без использования готовых JWT-обёрток.


Контроль структуры JWS

При ручной сборке важно сохранять неизменяемость промежуточных данных:

const headerPart = base64url(JSON.stringify(header));
const payloadPart = base64url(JSON.stringify(payload));
const data = headerPart + "." + payloadPart;

Любое изменение headerPart или payloadPart после подписи делает токен недействительным.