PKCS
PDF-подпись в формате PKCS#7 обычно используется в режиме detached signature: хэш документа вычисляется отдельно, затем подписывается приватным ключом, после чего формируется CMS-структура, которая встраивается в PDF или передаётся отдельно как внешняя подпись.
PKCS#7 (CMS SignedData) включает несколько ключевых компонентов:
SignedData
В PDF-контексте важно, что поле
encapContentInfo.eContent чаще всего отсутствует, поскольку
документ уже существует, и подписывается его хэш.
Jsrsasign предоставляет низкоуровневые средства для формирования CMS через:
KJUR.asn1.cms.SignedDataKJUR.crypto.CMSОднако в реальных сценариях чаще используется ручная сборка через
KJUR.crypto.Signature + ASN.1-структуры.
PDF не подписывается целиком. Формируется специальная структура:
/Contents)/ByteRange, например:[0 123456 234567 89012]
Это означает:
Фактически подписываются два фрагмента файла:
PDF_part1 + PDF_part2
Jsrsasign работает с бинарными строками и hex-данными:
const sha256 = new KJUR.crypto.MessageDigest({alg: "sha256", prov: "cryptojs"});
sha256.updateHex(part1Hex);
sha256.updateHex(part2Hex);
const digest = sha256.digest();
В PDF-подписи важно, что хэш считается не от текста, а от байтового представления файла.
После получения хэша формируется структура SignedData.
const cms = new KJUR.asn1.cms.SignedData({
version: 1,
digestAlg: ["sha256"],
contentInfo: {
type: "data",
content: null
},
certs: [certPem],
signerInfo: [{
version: 1,
sid: {type: "issuerAndSerialNumber", cert: certObj},
digestAlg: "sha256",
signAlg: "SHA256withRSA",
signature: signatureHex
}]
});
const cmsHex = cms.getContentInfoEncodedHex();
В реальной PDF-подписи signatureHex не вычисляется
напрямую из CMS — он получается отдельно через криптографическую подпись
хэша.
Jsrsasign предоставляет универсальный интерфейс
KJUR.crypto.Signature:
const sig = new KJUR.crypto.Signature({alg: "SHA256withRSA"});
sig.init(privateKeyPem);
sig.updateHex(digest);
const signatureHex = sig.sign();
Этот шаг является критическим: PDF требует подпись именно хэша ByteRange, а не всей структуры CMS.
После получения:
формируется PKCS#7 контейнер.
Пример через CMS SignedData:
const sd = new KJUR.asn1.cms.SignedData({
version: 1,
digestAlg: ["sha256"],
contentInfo: {type: "data"},
certs: [certPem],
signerInfo: [{
version: 1,
sid: {type: "issuerAndSerialNumber", cert: certObj},
digestAlg: "sha256",
signAlg: "SHA256withRSA",
signature: signatureHex
}]
});
const pkcs7Hex = sd.getContentInfoEncodedHex();
Результат — DER-кодированный PKCS#7, который может быть вставлен в
PDF как /Contents.
PDF требует фиксированного размера поля подписи. Обычно:
Пример логики:
pdf.replaceSignature({
byteRange: [0, offset1, offset2, offset3],
signature: pkcs7Hex
});
Важно, что PKCS#7 часто кодируется в HEX и дополняется нулями до
размера поля /Contents.
Jsrsasign редко используется изолированно в PDF-подписании. Чаще применяется связка с внешними инструментами для подготовки или валидации.
Создание ключей:
openssl genrsa -out key.pem 2048
openssl req -new -key key.pem -out req.csr
openssl x509 -req -in req.csr -signkey key.pem -out cert.pem
Генерация PKCS#7:
openssl smime -sign -binary -in hash.bin -signer cert.pem -inkey key.pem -outform DER -nodetach -out signature.p7s
Этот вариант часто используется как эталонный для сравнения с Jsrsasign.
PDF-экосистема ожидает строгую структуру:
Jsrsasign позволяет формировать совместимые структуры, но требует точного соответствия ByteRange и encoding.
Современные PDF требуют:
Jsrsasign поддерживает алгоритмы:
SHA256withRSASHA384withRSASHA256withECDSAПример выбора алгоритма:
const sig = new KJUR.crypto.Signature({alg: "SHA256withRSA"});
Любое смещение байтов делает подпись недействительной. Даже изменение пробела ломает проверку.
PDF требует фиксированного размера поля. Если PKCS#7 меньше, остаток
заполняется 0x00 или 0xFF.
PKCS#7 должен быть DER (hex), а не PEM. Jsrsasign часто возвращает hex-строку, которую нужно корректно вставлять в PDF.
Некоторые валидаторы требуют полный chain:
Jsrsasign позволяет передать массив certs.
В распределённых системах PDF подписывается внешним сервисом:
Пример серверной подписи:
const sig = new KJUR.crypto.Signature({alg: "SHA256withRSA"});
sig.init(privateKey);
sig.updateHex(hash);
const signed = sig.sign();
Возвращается:
{
"pkcs7": "308203..."
}
Jsrsasign поддерживает два подхода:
Signature + ручная сборка CMSKJUR.asn1.cms.SignedDataCMS предпочтительнее, так как:
Проверка включает:
Jsrsasign может использоваться и для проверки:
const cms = new KJUR.asn1.cms.SignedData({hex: pkcs7Hex});
const result = cms.verify();
В корпоративных сценариях приватный ключ не используется напрямую:
Такая архитектура повышает безопасность и соответствует требованиям PKI-инфраструктур.
Общий процесс выглядит как последовательность:
/ContentsКаждый шаг критичен, но наибольшая чувствительность приходится на согласованность ByteRange и CMS-структуры.