В криптографических подписях 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-теле, а подпись — в заголовках.
Встроенная подпись включает само подписываемое содержимое внутрь криптографической структуры. Такой подход обеспечивает автономность: объект подписи содержит всё необходимое для проверки без внешних данных.
В 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 основана на объекте
KJUR.asn1.cms.SignedData. Он управляет формированием
структуры ASN.1 и поддерживает оба режима.
Ключевым параметром является eContentType и наличие поля
eContent. При embedded-режиме eContent
заполняется данными, при detached — отсутствует или равен null.
Упрощённая структура:
SignedData = {
version,
digestAlgorithms,
encapContentInfo: {
eContentType,
eContent // присутствует только в embedded
},
certificates,
signerInfos
}
При проверке отделённой подписи необходимо предоставить два компонента: саму подпись и оригинальные данные.
const result = KJUR.asn1.cms.CMSUtil.verify({
cms: cmsSigned,
content: "Данные для подписи"
});
Отсутствие исходного контента делает проверку невозможной, поскольку хеш вычисляется заново и сравнивается с подписью.
Отделённые подписи используются в сценариях, где:
Встроенные подписи применяются, когда:
В JWS detached режим тесно связан с параметром:
{ b64: false }
Он отключает base64-кодирование payload внутри JWS. Это позволяет использовать «сырой» контент при вычислении подписи, сохраняя возможность передачи данных отдельно.
Структура:
BASE64URL(header) + "." + "" + "." + signature
При embedded режиме payload кодируется и включается в токен:
BASE64URL(header) + "." + BASE64URL(payload) + "." + signature
В CMS embedded содержимое хранится в виде OCTET STRING внутри encapContentInfo. Detached режим фактически заменяет его отсутствием, что меняет ASN.1 дерево:
Embedded:
EncapsulatedContentInfo
├── eContentType
└── eContent [OCTET STRING]
Detached:
EncapsulatedContentInfo
├── eContentType
└── (empty)
Эта разница напрямую влияет на сериализацию и итоговую бинарную форму PKCS#7.
Jsrsasign не рассматривает detached и embedded как разные алгоритмы подписи. Оба режима используют одинаковые криптографические операции (RSA/ECDSA + SHA), различие заключается исключительно в структуре данных:
В результате одна и та же криптографическая подпись может существовать в двух формах представления без изменения математической основы защиты.