Detached и embedded подписи

В криптографических подписях Jsrsasign различают два базовых подхода к хранению подписанных данных: отделённые (detached) и встроенные (embedded) подписи. Разница между ними определяется тем, включается ли исходное содержимое в структуру подписи или хранится отдельно.

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

В контексте Jsrsasign этот подход активно используется в двух основных механизмах: CMS/PKCS#7 и JWS (JSON Web Signature).

В CMS (Cryptographic Message Syntax) отделённая подпись реализуется через SignedData, где поле encapContentInfo не содержит встроенного содержимого. Вместо этого подписывается внешнее сообщение, а внутри структуры хранится только его хеш.

Пример создания CMS detached подписи:

const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(privateKeyPem);
sig.updateString("Данные для подписи");
const signatureHex = sig.sign();

Далее формируется CMS структура:

const cmsSigned = KJUR.asn1.cms.CMSUtil.newSignedData({
  content: "Данные для подписи",
  signerCert: certPem,
  signerPrvKey: privateKeyPem,
  detached: true
});

Ключевой параметр detached: true определяет, что содержимое не будет встроено в CMS объект. Проверка такой подписи требует наличия оригинальных данных, поскольку они не входят в сам контейнер.

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

В JWS (JSON Web Signature) detached-подпись реализуется через стандарт RFC 7515. В этом случае payload исключается из финальной JWT-строки, но используется при вычислении подписи.

Пример:

const sHeader = { alg: "HS256", b64: false, crit: ["b64"] };
const payload = "Данные для подписи";

const jws = KJUR.jws.JWS.sign(
  "HS256",
  JSON.stringify(sHeader),
  payload,
  "secret"
);

При использовании detached payload итоговая структура имеет вид:

header..signature

две точки подряд указывают на отсутствие встроенного payload. Это позволяет передавать данные отдельно, например в HTTP-теле, а подпись — в заголовках.


Встроенные подписи (embedded)

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

В CMS это реализуется через encapsulated content. В Jsrsasign достаточно не указывать параметр detached или явно задать его как false.

Пример CMS embedded подписи:

const cmsSigned = KJUR.asn1.cms.CMSUtil.newSignedData({
  content: "Данные для подписи",
  signerCert: certPem,
  signerPrvKey: privateKeyPem,
  detached: false
});

В результате формируется структура, в которой поле encapContentInfo содержит исходный текст. Это делает контейнер самодостаточным: достаточно иметь CMS объект, чтобы извлечь и проверить данные.

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

const info = KJUR.asn1.cms.CMSUtil.verify(cmsSigned);

Встроенные подписи особенно часто применяются в формате PKCS#7 (P7M), где документ и подпись объединены в единый бинарный контейнер. Такой формат используется в системах документооборота и электронного архивирования.


Сравнение моделей хранения данных

Отделённая подпись характеризуется тем, что:

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

Встроенная подпись:

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

Использование в Jsrsasign CMS API

Внутренняя архитектура Jsrsasign при работе с CMS основана на объекте KJUR.asn1.cms.SignedData. Он управляет формированием структуры ASN.1 и поддерживает оба режима.

Ключевым параметром является eContentType и наличие поля eContent. При embedded-режиме eContent заполняется данными, при detached — отсутствует или равен null.

Упрощённая структура:

SignedData = {
  version,
  digestAlgorithms,
  encapContentInfo: {
    eContentType,
    eContent // присутствует только в embedded
  },
  certificates,
  signerInfos
}

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

При проверке отделённой подписи необходимо предоставить два компонента: саму подпись и оригинальные данные.

const result = KJUR.asn1.cms.CMSUtil.verify({
  cms: cmsSigned,
  content: "Данные для подписи"
});

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


Практические особенности выбора режима

Отделённые подписи используются в сценариях, где:

  • данные уже передаются отдельно (например, JSON API)
  • важно избежать дублирования больших файлов
  • требуется минимальный размер подписи
  • подпись хранится в отдельной системе

Встроенные подписи применяются, когда:

  • необходим единый переносимый контейнер
  • важна автономная проверка без внешних источников
  • документ должен храниться как единый объект (архивы, P7M)
  • требуется совместимость с системами электронного документооборота

JWS и влияние параметра b64

В JWS detached режим тесно связан с параметром:

{ b64: false }

Он отключает base64-кодирование payload внутри JWS. Это позволяет использовать «сырой» контент при вычислении подписи, сохраняя возможность передачи данных отдельно.

Структура:

BASE64URL(header) + "." + "" + "." + signature

При embedded режиме payload кодируется и включается в токен:

BASE64URL(header) + "." + BASE64URL(payload) + "." + signature

ASN.1 представление и влияние на CMS структуру

В CMS embedded содержимое хранится в виде OCTET STRING внутри encapContentInfo. Detached режим фактически заменяет его отсутствием, что меняет ASN.1 дерево:

Embedded:

EncapsulatedContentInfo
 ├── eContentType
 └── eContent [OCTET STRING]

Detached:

EncapsulatedContentInfo
 ├── eContentType
 └── (empty)

Эта разница напрямую влияет на сериализацию и итоговую бинарную форму PKCS#7.


Итоговые технические различия в поведении Jsrsasign

Jsrsasign не рассматривает detached и embedded как разные алгоритмы подписи. Оба режима используют одинаковые криптографические операции (RSA/ECDSA + SHA), различие заключается исключительно в структуре данных:

  • место хранения подписываемого контента
  • способ вычисления хеша
  • формат финального контейнера CMS или JWS

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