Перейти к содержимому

Как подпись устроена внутри PDF

Spec: ETSI EN 319 142-1 Spec: RFC 5652 Evidence: Standard-backed

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

“Документ подписан” — фраза, на основе которой люди принимают решения. Они связывают её с платежом, одобрением, юридическим обязательством. Если вы не знаете точно, какие байты охватывает подпись, вы не можете сказать, что на самом деле доказывает действительный результат. PDF может содержать полностью действительную подпись и при этом показывать читателю содержимое, которого подписавший никогда не видел, потому что оно было добавлено после подписания, в области, о которой подпись ничего не заявляла. Понимание того, где начинаются и заканчиваются полномочия подписи, — это разница между обоснованным решением и решением, основанным на надежде.

  • Подпись PDF находится в словаре подписи и поле подписи внутри документа, а не в виде внешней оболочки.
  • Подписанные байты объявляются массивом ByteRange: два сегмента (offset, length), которые вместе охватывают весь файл, кроме шестнадцатеричного значения подписи, хранящегося в записи Contents.
  • Криптографическая подпись защищает именно дайджест этих двух соединённых сегментов.
  • Всё, что добавлено позже в новой редакции, находится вне исходного диапазона байтов. Исходная подпись остаётся действительной; она ничего не заявляла о новых байтах.
  • Подпись для одобрения и подпись для сертификации различаются по охвату: сертификация (DocMDP) ограничивает, какие дальнейшие изменения допустимы; одобрение — нет.

NextPDF строит подпись по модели формата, в строгом порядке, поэтому диапазон байтов получается точным, а не приблизительным.

Когда движок записывает подпись, он сначала резервирует слот фиксированного размера для значения Contents и записывает заполнитель ByteRange фиксированной ширины. Он дожидается, пока весь документ будет записан, включая таблицу перекрёстных ссылок и маркер конца файла. Только после этого движок вычисляет два настоящих смещения, записывает их обратно в заполнитель, не сдвигая ни одного байта, хеширует два сегмента и помещает полученный объект CMS в зарезервированный слот. Заполнитель дополняется нулями до постоянной длины именно для того, чтобы подстановка настоящих чисел не сдвинула хешируемые байты. Это единственный порядок, который даёт самосогласованную подпись. Любой сбой в этой последовательности движок рассматривает как жёсткую ошибку, а не как тихий откат.

В профиле PDF 2.0 сам объект подписи представляет собой откреплённую структуру CMS SignedData. Словарь PDF говорит где и как; объект CMS несёт кто и криптографическое доказательство.

  1. Step 1 of 4: ISO 32000-2 §12.8.1 — ByteRange digest & signature dictionary
  2. Step 2 of 4: ISO 32000-2 §12.8.3.3 — ETSI.CAdES.detached SubFilter
  3. Step 3 of 4: ETSI EN 319 142-1 PAdES baseline profile
  4. Step 4 of 4: RFC 5652 CMS SignedData in Contents
Где определяется подпись PDF — от формата контейнера до криптографического объекта: ISO 32000-2 задаёт словарь и механизм диапазона байтов, ETSI EN 319 142-1 профилирует его для PAdES, а RFC 5652 определяет объект CMS SignedData, помещаемый в Contents.

Evidence: Standard-backed Механизм определяется в Spec: ISO 32000-2, §12.8.1 . Дайджест диапазона байтов вычисляется по диапазону байтов, указанному в записи ByteRange. Этот диапазон должен охватывать весь файл, включая словарь подписи, но исключая значение подписи — запись Contents. ByteRange — это массив пар целых чисел — начальное смещение и длина. Разрывные диапазоны используются именно для того, чтобы дайджест мог опустить само значение подписи.

Для профиля PDF 2.0 Spec: ISO 32000-2, §12.8.3.3 указывает, что когда SubFilter равен ETSI.CAdES.detached, значение Contents представляет собой объект CMS SignedData в кодировке DER — ту же структуру, которую Spec: RFC 5652 определяет, — а профиль PAdES этого объекта описан в Spec: ETSI EN 319 142-1 .

Охват у подписей бывает разным. Spec: ISO 32000-2, §12.7.4.5 определяет разрешение MDP: значение 0 делает подпись подписью одобрения, тогда как значения 13 делают её подписью сертификации, которая ограничивает, какие из дальнейших изменений сохраняют соответствие документа. Механизм диапазона байтов тот же; обещание относительно будущего — другое.

Движок NextPDF реализует именно это: заполнитель ByteRange фиксированной ширины, дайджест двух соединённых сегментов и откреплённый объект CMS в зарезервированном слоте Contents, который финализируется только после того, как файл полностью готов.

Вы редко собираете ByteRange вручную. Смысл примера — показать форму результата, чтобы вы могли узнать её при осмотре подписанного файла.

<?php
declare(strict_types=1);
use NextPDF\Security\Signature\ByteRangeCalculator;
// Offsets the engine knows only after the whole PDF is written:
// $contentsStart — byte just before the '<' of the hex signature
// $contentsEnd — byte just after the '>' that closes it
// $fileLength — total file size in bytes
$range = ByteRangeCalculator::calculate(
contentsStart: $contentsStart,
contentsEnd: $contentsEnd,
fileLength: $fileLength,
);
// $range === [0, $contentsStart, $contentsEnd, $fileLength - $contentsEnd]
// Segment 1: file start → just before the signature value
// Segment 2: just after the signature value → end of file
// The signature value itself is the gap. It is never hashed.
$signedMessage = ByteRangeCalculator::extractSignedData($pdfBytes, $range);
// $signedMessage is segment 1 concatenated with segment 2 — exactly the
// bytes the cryptographic digest is computed over.

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

Ловушка — считать, что действительная подпись означает, будто подписан был весь файл, который вы видите. Это не так. Это означает, что байты внутри объявленного диапазона не изменены. Более поздняя редакция может законно добавить содержимое — вторую подпись, данные формы, материал проверки — вне этого диапазона. Первая подпись остаётся действительной и ничего не говорит о добавлении. Корректная программа просмотра сообщает вам, что подпись охватывает “документ в том виде, в каком он существовал на момент подписания”, а не “каждый байт на экране”. Если считать эти два понятия одним и тем же, подписанный документ получает неподписанное содержимое, которое выглядит подписанным.

На этой странице объясняется структура, а не доверие. Корректно сформированные ByteRange и объект CMS сообщают вам, что байты не изменены и какой ключ их подписал. Сами по себе они не говорят, принадлежит ли этот ключ тому, кого вы предполагаете, был ли его сертификат действителен на момент подписания и не был ли он впоследствии отозван. Это относится к работе с путём сертификации и отзывом, рассмотренной в Правильная проверка подписи. Эта страница также не охватывает то, когда произошло подписание, с участием какой-либо независимой стороны. Самостоятельно заявленное время подписания не является доверенным временем — см. Метки времени и доверенное время. NextPDF строит структуру, описанную здесь; сертификаты, якоря доверия и служба меток времени предоставляются вашей средой развёртывания, а не движком.

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

Структура подписи PAdES (диапазон байтов, словарь подписи, откреплённый CMS) — edition availability
Edition Availability
Core

PAdES B-B: словарь подписи, ByteRange фиксированной ширины и откреплённый объект CMS SignedData, описанный на этой странице.

Pro

Добавляет PAdES B-T — доверенную метку времени для значения подписи — поверх той же структуры.

Enterprise

Добавляет долгосрочные профили (B-LT, B-LTA): встроенный материал проверки и метки времени документа, наложенные на ту же основу диапазона байтов.

  • Словарь подписи — словарь PDF, который именует обработчик подписи, SubFilter, ByteRange и значение Contents.
  • ByteRange — массив пар целых чисел (offset, length), объявляющий точные байты, которые охватывает дайджест подписи.
  • Contents — шестнадцатеричная запись, хранящая значение подписи (для PDF 2.0 — откреплённый объект CMS SignedData); она исключается из собственного дайджеста.
  • CMS SignedData — структура Cryptographic Message Syntax (RFC 5652), несущая сертификат подписавшего и байты подписи.
  • PAdES — PDF Advanced Electronic Signatures: профиль ETSI подписей CMS для PDF, определённый в серии ETSI EN 319 142.
  • Подпись одобрения — подпись с разрешением MDP, равным 0; она удостоверяет содержимое, не ограничивая дальнейшие изменения.
  • Подпись сертификации — подпись с разрешением DocMDP (MDP 13), которая ограничивает, какие из дальнейших изменений сохраняют соответствие документа.