Создание подписи: sign

Библиотека jsrsasign реализует криптографические операции высокого уровня поверх JavaScript, включая RSA, ECDSA, HMAC и работу с X.509. Центральным элементом для формирования цифровой подписи выступает класс KJUR.crypto.Signature, который инкапсулирует процесс хеширования сообщения и последующего подписания приватным ключом.

Класс KJUR.crypto.Signature

Объект подписи создаётся через конструктор, в который передаётся строка, определяющая алгоритм:

  • SHA256withRSA
  • SHA1withRSA
  • SHA512withRSA
  • SHA256withECDSA

Алгоритм задаёт связку: хеш-функция + криптосистема подписи. Например, SHA256withRSA означает сначала вычисление SHA-256, затем применение RSA-подписи.

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

После создания объекта требуется инициализация ключом.


Подготовка ключей

Jsrsasign использует объект KEYUTIL для загрузки и преобразования ключей в формат, пригодный для подписи.

Приватный ключ может быть представлен в PEM-формате:

const privateKeyPem = `
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...
-----END PRIVATE KEY-----`;

Преобразование в объект ключа:

const privateKey = KEYUTIL.getKey(privateKeyPem);

Инициализация процесса подписи

После выбора алгоритма и загрузки ключа выполняется инициализация:

sig.init(privateKey);

На этом этапе объект подписи готов принимать данные.


Добавление данных для подписи

Данные передаются в виде строки. Важно учитывать, что библиотека работает с байтовым представлением строки, поэтому кодировка UTF-8 играет ключевую роль.

sig.updateString("Hello world");

Метод updateString удобен для текстовых данных. Для бинарных данных используется:

sig.updateHex("a1b2c3");

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

После передачи всех данных вызывается метод sign(), который возвращает криптографическую подпись.

const signature = sig.sign();

Результат обычно возвращается в виде шестнадцатеричной строки (hex). Это значение является итоговым криптографическим маркером целостности и подлинности данных.


Полный цикл формирования подписи

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

const privateKey = KEYUTIL.getKey(privateKeyPem);

sig.init(privateKey);
sig.updateString("Message for signing");

const signature = sig.sign();

console.log(signature);

Форматы вывода подписи

Jsrsasign поддерживает несколько представлений результата:

  • HEX (по умолчанию)
  • Base64 (через преобразование)
  • DER (внутренний ASN.1 формат)

Преобразование в Base64:

const b64 = hextob64(signature);

Подпись структурированных данных

При работе с JSON важно сохранять детерминированность строки. Любое изменение порядка ключей изменяет подпись.

const payload = JSON.stringify({
  user: "alice",
  role: "admin"
});

sig.updateString(payload);
const signature = sig.sign();

Использование RSA и ECDSA

RSA

RSA-подпись является наиболее распространённой в jsrsasign. Используется в сочетании с PKCS#1 v1.5 или PSS.

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

ECDSA

ECDSA обеспечивает меньший размер подписи при сопоставимой криптографической стойкости.

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

Особенность ECDSA заключается в зависимости от кривой (P-256, P-384 и т.д.), заданной в ключе.


Подпись через PSS (RSA-PSS)

Более современный режим RSA-подписи использует вероятностное дополнение PSS.

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

Такой вариант повышает устойчивость к криптоаналитическим атакам за счёт случайного компонента.


Работа с потоковыми данными

Метод updateString можно вызывать многократно, формируя подпись по частям:

sig.init(privateKey);
sig.updateString("part1");
sig.updateString("part2");
sig.updateString("part3");

const signature = sig.sign();

Финальный результат эквивалентен подписи конкатенации всех частей.


Типичные ошибки при формировании подписи

Несоответствие алгоритма ключу

RSA-ключ нельзя использовать с ECDSA-алгоритмом и наоборот. Это приводит к исключениям при инициализации или генерации подписи.

Изменение данных после хеширования

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

Кодировка строк

Несоответствие UTF-8 приводит к различию байтового представления и, как следствие, к неверной подписи при проверке.


Подпись бинарных данных

При работе с байтовыми массивами используется hex-представление:

sig.updateHex("deadbeef");
const signature = sig.sign();

Этот режим применяется при подписи файлов, хешей или уже подготовленных дайджестов.


Подпись уже вычисленного хеша

В некоторых сценариях подписывается не исходное сообщение, а его хеш:

sig.init(privateKey);
sig.updateHex(sha256Digest);
const signature = sig.sign();

Такой подход используется в низкоуровневых протоколах и кастомных реализациях.


Внутренний процесс sign()

Метод sign() выполняет несколько этапов:

  1. Завершение накопления данных
  2. Вычисление хеша (если не передан вручную)
  3. Формирование структуры DigestInfo (для RSA)
  4. Криптографическое преобразование с использованием приватного ключа
  5. Кодирование результата в hex

Совместимость с Web Crypto и внешними системами

Подписи, созданные jsrsasign, часто используются в:

  • JWT (JWS)
  • TLS-совместимых структурах
  • API с проверкой подписи
  • блокчейн-подобных системах

При интеграции важно учитывать формат кодирования (Base64URL vs Base64 vs Hex), так как несоответствие приводит к ошибкам верификации.


Особенности обработки ошибок

Типичные исключения возникают при:

  • отсутствии приватного ключа
  • некорректном PEM-формате
  • несоответствии алгоритма
  • попытке подписать пустые данные без инициализации

Каждая из этих ситуаций прерывает выполнение sign() до генерации результата.