Работа с хешем перед подписью

Хеширование перед подписью является фундаментальным этапом криптографического процесса в библиотеке Jsrsasign и в асимметричных схемах в целом. Подписание произвольных данных напрямую в большинстве реализаций не выполняется: сначала формируется криптографический хеш фиксированной длины, который затем уже подписывается приватным ключом.

Основная причина такого подхода заключается в эффективности и безопасности. Криптографические алгоритмы подписи работают с фиксированными по размеру блоками данных, тогда как входные сообщения могут иметь произвольную длину. Хеш-функция решает обе задачи: нормализует вход и обеспечивает стойкость к коллизиям.

Хеш-функция преобразует входное сообщение в короткую фиксированную строку, называемую digest (хеш-значение). В контексте Jsrsasign чаще всего используются алгоритмы SHA-256, SHA-384 и SHA-512.

H = (M)

где ( M ) — исходное сообщение, а ( H ) — его хеш.

Подпись фактически вычисляется уже над этим значением:

S = (H, ; K_{private})

Такой подход уменьшает объем данных, обрабатываемых криптографическим алгоритмом, и предотвращает ряд атак, связанных с манипуляцией входными сообщениями.

Встроенное и ручное хеширование в Jsrsasign

Jsrsasign предоставляет два основных способа работы с хешем перед подписью:

  • автоматическое хеширование внутри объекта Signature
  • явное предварительное вычисление digest

В большинстве случаев используется встроенный режим, где библиотека сама применяет выбранный алгоритм хеширования.

Автоматическое хеширование через Signature

При использовании класса KJUR.crypto.Signature хеширование выполняется автоматически:

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

sig.init(privateKey);
sig.updateString("Hello world");
const signature = sig.sign();

В данном случае строка проходит этап UTF-8 кодирования, затем хешируется SHA-256, после чего результат подписывается RSA-ключом.

Важный момент заключается в том, что алгоритм подписи уже включает информацию о хеше (SHA256withRSA), и разработчику не требуется выполнять отдельный шаг.

Ручное хеширование перед подписью

В некоторых сценариях требуется явное вычисление хеша до подписания. Это необходимо при:

  • работе с внешними API, где требуется передавать digest
  • интеграции с аппаратными HSM
  • реализации кастомных протоколов

Jsrsasign предоставляет модуль KJUR.crypto.MessageDigest.

Пример вычисления SHA-256:

const md = new KJUR.crypto.MessageDigest({ alg: "sha256" });
md.updateString("Hello world");
const hashHex = md.digest();

Результат hashHex представляет собой шестнадцатеричную строку фиксированной длины (64 символа для SHA-256).

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

После получения digest его можно подписать напрямую:

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

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

Здесь используется метод updateHex, так как вход уже находится в хешированном виде.

Кодировка входных данных перед хешированием

Критически важным этапом является преобразование строки в байтовое представление. Jsrsasign использует UTF-8 кодирование, что влияет на итоговый хеш.

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

  • ASCII строка
  • UTF-8 строка
  • строка с нормализацией Unicode

Даже визуально одинаковые строки могут иметь разные байтовые последовательности, что приводит к различным хешам.

Hex, Base64 и внутренние представления

Jsrsasign оперирует несколькими форматами представления данных:

  • hex — наиболее часто используемый формат для digest
  • base64 — используется для передачи подписи
  • binary string — внутренний формат

Хеш-функции в MessageDigest возвращают результат в hex по умолчанию, что удобно для последующей передачи в криптографические операции.

Различие между хешированием и подписью

Хеширование не является криптографической защитой само по себе. Оно выполняет функцию компрессии и нормализации данных.

Подпись же обеспечивает:

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

Схема работы выглядит следующим образом:

M H = (M) S = (H, K_{private})

Алгоритмы хеширования в Jsrsasign

Поддерживаются стандартные криптографические алгоритмы:

  • SHA-1 (устаревший, не рекомендуется)
  • SHA-256 (основной стандарт)
  • SHA-384
  • SHA-512

Выбор алгоритма влияет на устойчивость к коллизиям и размер подписи. В современных реализациях предпочтение отдается SHA-256 и выше.

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

Двойное хеширование

Одна из распространённых ошибок — повторное применение SHA-256:

md.updateString(data);
const h1 = md.digest();
const h2 = md.digest(); // ошибка: повторное хеширование уже готового хеша

Повторный вызов digest на уже хешированном значении приводит к искажению результата подписи.

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

Если подпись создается как SHA256withRSA, но digest вычислен SHA-1, проверка подписи всегда будет провалена.

Несогласованность форматов

Передача hex вместо base64 или наоборот приводит к неверной интерпретации данных на этапе проверки подписи.

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

В алгоритме ECDSA хеширование также выполняется перед подписью, но важно учитывать детерминированный характер некоторых реализаций.

Jsrsasign поддерживает схемы вида:

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

Здесь SHA-256 применяется к сообщению, а затем результат используется в эллиптической криптографии.

Влияние предварительного хеширования на производительность

При работе с большими объемами данных предварительное вычисление хеша позволяет:

  • снизить нагрузку на подпись
  • оптимизировать повторное использование digest
  • ускорить операции при пакетной обработке

Особенно это заметно при RSA, где операция подписи значительно тяжелее, чем SHA-256.

Практическая модель потока данных

Полный цикл обработки данных в Jsrsasign можно представить следующим образом:

  1. входная строка
  2. UTF-8 кодирование
  3. вычисление хеша
  4. формирование криптографического блока
  5. применение приватного ключа
  6. получение подписи

S = f(((M)), K_{private})