Хеширование перед подписью является фундаментальным этапом криптографического процесса в библиотеке Jsrsasign и в асимметричных схемах в целом. Подписание произвольных данных напрямую в большинстве реализаций не выполняется: сначала формируется криптографический хеш фиксированной длины, который затем уже подписывается приватным ключом.
Основная причина такого подхода заключается в эффективности и безопасности. Криптографические алгоритмы подписи работают с фиксированными по размеру блоками данных, тогда как входные сообщения могут иметь произвольную длину. Хеш-функция решает обе задачи: нормализует вход и обеспечивает стойкость к коллизиям.
Хеш-функция преобразует входное сообщение в короткую фиксированную строку, называемую digest (хеш-значение). В контексте Jsrsasign чаще всего используются алгоритмы SHA-256, SHA-384 и SHA-512.
H = (M)
где ( M ) — исходное сообщение, а ( H ) — его хеш.
Подпись фактически вычисляется уже над этим значением:
S = (H, ; K_{private})
Такой подход уменьшает объем данных, обрабатываемых криптографическим алгоритмом, и предотвращает ряд атак, связанных с манипуляцией входными сообщениями.
Jsrsasign предоставляет два основных способа работы с хешем перед подписью:
В большинстве случаев используется встроенный режим, где библиотека сама применяет выбранный алгоритм хеширования.
При использовании класса 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), и разработчику не
требуется выполнять отдельный шаг.
В некоторых сценариях требуется явное вычисление хеша до подписания. Это необходимо при:
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 кодирование, что влияет на итоговый хеш.
Разные представления строки дают разные результаты:
Даже визуально одинаковые строки могут иметь разные байтовые последовательности, что приводит к различным хешам.
Jsrsasign оперирует несколькими форматами представления данных:
Хеш-функции в MessageDigest возвращают результат в hex по умолчанию, что удобно для последующей передачи в криптографические операции.
Хеширование не является криптографической защитой само по себе. Оно выполняет функцию компрессии и нормализации данных.
Подпись же обеспечивает:
Схема работы выглядит следующим образом:
M H = (M) S = (H, K_{private})
Поддерживаются стандартные криптографические алгоритмы:
Выбор алгоритма влияет на устойчивость к коллизиям и размер подписи. В современных реализациях предпочтение отдается SHA-256 и выше.
Одна из распространённых ошибок — повторное применение SHA-256:
md.updateString(data);
const h1 = md.digest();
const h2 = md.digest(); // ошибка: повторное хеширование уже готового хеша
Повторный вызов digest на уже хешированном значении приводит к искажению результата подписи.
Если подпись создается как SHA256withRSA, но digest
вычислен SHA-1, проверка подписи всегда будет провалена.
Передача hex вместо base64 или наоборот приводит к неверной интерпретации данных на этапе проверки подписи.
В алгоритме ECDSA хеширование также выполняется перед подписью, но важно учитывать детерминированный характер некоторых реализаций.
Jsrsasign поддерживает схемы вида:
const sig = new KJUR.crypto.Signature({ "alg": "SHA256withECDSA" });
Здесь SHA-256 применяется к сообщению, а затем результат используется в эллиптической криптографии.
При работе с большими объемами данных предварительное вычисление хеша позволяет:
Особенно это заметно при RSA, где операция подписи значительно тяжелее, чем SHA-256.
Полный цикл обработки данных в Jsrsasign можно представить следующим образом:
S = f(((M)), K_{private})