Отладка криптографических операций

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

Одной из ключевых особенностей Jsrsasign является строгая зависимость от корректного формата входных данных: PEM, DER, Base64URL, Hex и UTF-8 должны использоваться без отклонений. Даже незначительное несоответствие приводит к ошибкам валидации подписи или невозможности распарсить ключ.


Ошибки парсинга ключей и форматов PEM/DER

Наиболее частый класс проблем связан с ключами RSA и ECDSA.

Jsrsasign использует KEYUTIL.getKey() для преобразования PEM-строк в внутренние структуры. Ошибки возникают при:

  • наличии лишних символов переноса строк
  • повреждённом PEM-заголовке
  • использовании PKCS#1 вместо PKCS#8 без учета формата
  • смешении форматов OpenSSL и JWK

Пример корректного RSA ключа:

const key = KEYUTIL.getKey(`
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr...
-----END PUBLIC KEY-----
`);

Типичная ошибка:

  • Error: unsupported key format
  • ASN.1 parsing error

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


Проблемы ASN.1 и внутреннего разбора структуры

Jsrsasign активно использует ASN.1-парсер для обработки сертификатов и ключей. Ошибки уровня ASN.1 часто возникают при:

  • повреждённых бинарных данных DER
  • неправильной Base64-декодировке
  • передаче строки вместо бинарного массива

Особенно критична ситуация, когда данные уже были декодированы внешней библиотекой:

// Ошибочный подход
const der = Buffer.from(base64, 'base64');
KEYUTIL.getKey(der);

Jsrsasign ожидает либо PEM, либо строку, но не Node Buffer.

Правильный подход:

const pem = KJUR.asn1.ASN1Util.getPEMStringFromHex(base64);
const key = KEYUTIL.getKey(pem);

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

При проверке подписи важно совпадение:

  • алгоритма хэширования (SHA1, SHA256, SHA512)
  • алгоритма подписи (RSA, ECDSA)
  • формата кодирования (HEX, Base64, Base64URL)

Распространённая ошибка:

const isValid = sig.verifyString("message", signatureHex);

При этом подпись была создана как Base64URL — проверка всегда будет ложной.

Диагностические шаги:

  • проверить алгоритм через sigalg
  • убедиться в совпадении hash function
  • сравнить raw bytes подписи

Проблемы Base64 и Base64URL

Jsrsasign строго различает Base64 и Base64URL. JWT-операции особенно чувствительны к этому.

Base64URL:

  • заменяет + на -
  • заменяет / на _
  • убирает = padding

Ошибка часто возникает при ручной конкатенации JWT:

const token = header + "." + payload + "." + signatureBase64;

Если подпись не преобразована в Base64URL, проверка JWT завершится неудачей.

Корректная генерация:

KJUR.jws.JWS.sign(
  "HS256",
  JSON.stringify(header),
  JSON.stringify(payload),
  key
);

Ошибки кодировки UTF-8 и строковых данных

Криптографические операции чувствительны к байтовому представлению строки.

Проблемные ситуации:

  • использование UTF-16 строк JavaScript без явного преобразования
  • несогласованность серверной и клиентской кодировки
  • наличие невидимых символов (BOM, zero-width space)

Диагностический подход:

console.log(new Buffer.from(msg, 'utf8').toString('hex'));

Сравнение hex-представления позволяет выявить скрытые различия.


JWT-отладка и расхождения в времени (exp, iat, nbf)

При работе с KJUR.jws.JWS частая ошибка связана не с криптографией, а с временными полями:

  • exp (expiration)
  • iat (issued at)
  • nbf (not before)

Если системное время клиента отличается от сервера, токен может считаться недействительным.

Диагностические признаки:

  • подпись корректна, но verifyJWT() возвращает false
  • ошибка без явного криптографического сообщения

Проверка:

const payloadObj = KJUR.jws.JWS.readSafeJSONString(payload);
console.log(payloadObj.exp, Date.now()/1000);

Несовместимость с Node.js crypto и браузером

Jsrsasign реализует криптографию самостоятельно, не полагаясь на WebCrypto или Node crypto. Это приводит к различиям:

  • разные реализации random padding (PKCS#1 v1.5)
  • отличия в ECDSA сериализации подписи
  • различие в обработке BigInt

Ошибка проявляется как:

  • подпись создаётся, но не проверяется в другом окружении

Диагностика требует фиксирования окружения:

console.log(navigator.userAgent);
console.log(process.version);

Отладка RSA подписи: пошаговый подход

При проблемах с RSA проверкой эффективен последовательный разбор:

  1. Проверка ключа:
console.log(KEYUTIL.getKey(pubPEM));
  1. Проверка алгоритма:
sig.init(pubKey);
sig.updateString(message);
  1. Проверка подписи в сыром виде:
console.log(sig.sign());
  1. Сравнение с внешним источником (OpenSSL):
openssl dgst -sha256 -verify pub.pem -signature sig.bin msg.txt

Проблемы ECDSA: нестабильность формата подписи

ECDSA в Jsrsasign особенно чувствителен к формату r || s.

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

  • подпись приходит в формате DER
  • библиотека ожидает raw concat

Диагностика:

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

Если подпись не проходит, проверяется длина r и s.


Логирование внутренних криптографических операций

Jsrsasign не предоставляет полноценного debug-режима, поэтому отладка строится через ручное логирование промежуточных значений:

  • hex представление сообщений
  • Base64URL состояния
  • ASN.1 структуры

Пример:

console.log(KJUR.asn1.ASN1Util.newObject({seq: [...] }));

Диагностика ошибок через сравнение с контрольными данными

Наиболее надёжный метод отладки — фиксация эталонных значений:

  • исходное сообщение (bytes)
  • хэш
  • подпись
  • публичный ключ

Если любой этап расходится — проблема локализуется без анализа всей цепочки.


Частые скрытые проблемы

Некоторые ошибки не проявляются напрямую:

  • лишний пробел в конце строки перед подписью
  • нормализация Unicode (NFC vs NFD)
  • случайная конкатенация строк без разделителей
  • повторное хэширование уже хэшированных данных

Особенно опасна ситуация двойного SHA-256:

sig.updateString(sha256(message));

вместо

sig.updateString(message);

Работа с ошибками KJUR и интерпретация сообщений

Сообщения Jsrsasign часто абстрактны:

  • invalid signature
  • ASN1 parse error
  • not supported algorithm

Правильная стратегия — не интерпретировать их буквально, а проверять слой входных данных:

  • формат ключа
  • байтовое представление
  • алгоритм подписи
  • кодировку строки

Минимизация неопределённости при отладке

Для устойчивой диагностики важно:

  • фиксировать входные данные в hex
  • исключать динамические значения
  • использовать один и тот же runtime
  • избегать автоматических преобразований строк

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