Подписи на основе RSA: PKCS#1 v1.5

RSA-подпись в формате PKCS#1 v1.5 представляет собой детерминированную схему формирования цифровой подписи, основанную на хэшировании сообщения и последующем шифровании хэша закрытым ключом RSA с применением стандартизированного заполнения (padding).

Суть схемы заключается в следующем:

  1. Вычисляется хэш сообщения (например, SHA-256).

  2. Формируется структура DigestInfo, содержащая идентификатор алгоритма хэширования и сам хэш.

  3. К полученным данным применяется padding по стандарту PKCS#1 v1.5.

  4. Результат возводится в степень закрытого ключа RSA:

    • signature = (EM)^d mod n, где EM — encoded message.

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


Поддержка RSA PKCS#1 v1.5 в Jsrsasign

Библиотека Jsrsasign реализует RSA-подписи через объект KJUR.crypto.Signature. Поддерживаются алгоритмы вида:

  • SHA1withRSA
  • SHA256withRSA
  • SHA384withRSA
  • SHA512withRSA

Алгоритм PKCS#1 v1.5 используется по умолчанию при выборе RSA Signature API, если не задействованы схемы PSS.


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

Jsrsasign работает с ключами в формате PEM и внутренним объектом RSAKey.

Загрузка существующего ключа

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

const privateKey = KEYUTIL.getKey(pemPrivateKey);

Публичный ключ:

const pemPublicKey = `
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
`;

const publicKey = KEYUTIL.getKey(pemPublicKey);

Создание подписи RSA (SHA256withRSA)

Подпись формируется через объект KJUR.crypto.Signature.

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

sig.init(privateKey);
sig.updateString("Сообщение для подписи");

const signatureHex = sig.sign();

Ключевые этапы:

  • init(privateKey) — инициализация закрытого ключа
  • updateString() — добавление данных
  • sign() — генерация подписи в hex-формате

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

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

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

Проверка подписи

Для проверки используется тот же класс Signature, но с открытым ключом.

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

verifier.init(publicKey);
verifier.updateString("Сообщение для подписи");

const isValid = verifier.verify(signatureHex);

Результат:

  • true — подпись корректна
  • false — данные или подпись изменены

Внутреннее устройство PKCS#1 v1.5 в Jsrsasign

Jsrsasign абстрагирует сложную структуру PKCS#1 v1.5, но внутри происходит:

  • Формирование DER-структуры DigestInfo

  • Кодирование ASN.1

  • Применение padding:

    0x00 || 0x01 || PS || 0x00 || DigestInfo
  • Возведение в степень RSA

Эта схема не требует ручного формирования padding, но важно учитывать фиксированность структуры.


Работа с хэш-алгоритмами

Выбор алгоритма влияет на безопасность и формат DigestInfo.

Пример SHA-1 (устаревший):

new KJUR.crypto.Signature({ alg: "SHA1withRSA" });

SHA-256 (рекомендуется):

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

SHA-512 (для повышенной стойкости):

new KJUR.crypto.Signature({ alg: "SHA512withRSA" });

Важные особенности PKCS#1 v1.5

  • Схема детерминированная: одна и та же подпись для одинаковых данных
  • Нет встроенной защиты от некоторых атак на структуру padding
  • Требует точного совпадения данных при проверке
  • Широко поддерживается в legacy-системах

Типичные ошибки при работе с Jsrsasign

Несовпадение алгоритма

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

SHA256withRSA ≠ SHA1withRSA

Разные форматы данных

Ошибка возникает при смешивании:

  • UTF-8 строк
  • HEX строк
  • Base64

Решение — использовать единый формат (updateString или updateHex).


Некорректный ключ

Частая проблема:

  • Использование публичного ключа для подписи
  • Использование приватного ключа для проверки

Работа с Base64 вместо HEX

Jsrsasign возвращает подпись в HEX. Для Base64 требуется преобразование:

const hex = sig.sign();
const b64 = hextob64(hex);

Обратное преобразование:

const hex = b64tohex(b64);

Проверка подписи из внешних систем

При взаимодействии с Java, .NET или OpenSSL важно учитывать:

  • PKCS#1 v1.5 используется по умолчанию во многих реализациях
  • Различия чаще всего возникают из-за хэширования
  • Необходимо совпадение алгоритма (например, SHA256withRSA)

Использование низкоуровневого RSAKey

Вместо Signature можно использовать RSAKey, но это требует ручного хэширования:

const rsa = KEYUTIL.getKey(pemPrivateKey);
const sigValue = rsa.signString("msg", "sha256");

Проверка:

const isValid = rsa.verifyString("msg", signatureHex);

Совместимость PKCS#1 v1.5

Схема широко поддерживается:

  • OpenSSL
  • Java Keystore
  • .NET RSACryptoServiceProvider
  • Браузерные криптографические библиотеки

Это делает её стандартом для интеграций, несмотря на появление более современных схем (например, PSS).


Практическая структура подписи

Типичный процесс в Jsrsasign выглядит следующим образом:

  1. Загрузка PEM-ключа
  2. Выбор алгоритма SHA256withRSA
  3. Инициализация Signature
  4. Передача данных
  5. Генерация подписи
  6. Проверка с публичным ключом

Эта последовательность фиксирована и повторяется для любых RSA PKCS#1 v1.5 операций.