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

Enterprise редакция

Цифровое нанесение водяных знаков и стеганографическое встраивание

NextPDF Enterprise встраивает скрытую зашифрованную полезную нагрузку в сформированный PDF, делая небольшие управляемые корректировки расстояния между парами букв. Вы предоставляете полезную нагрузку — обычно идентификатор для каждого получателя — и секретный ключ; кодировщик записывает полезную нагрузку как незаметные отклонения от естественного кернинга текста. Соответствующий декодировщик, получив тот же ключ, восстанавливает полезную нагрузку. Эта страница на уровне поведения: она излагает, что записывает кодировщик, какую криптографию использует и где проходит граница метода.

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

Предварительные требования указаны в начале страницы и повторены в разделе Предварительные требования.

Эта возможность поставляется в NextPDF Enterprise (nextpdf/enterprise) и активируется лицензионным конвертом уровня Enterprise. Развёртывание без этого права доступа не загружает классы возможности. Возможность работает полностью внутри процесса во время генерации документа; никакое содержимое документа не покидает хост. Сравните редакции и получите лицензию.

Текст PDF, нарисованный с массивом кернинга, несёт числовую корректировку между глифами. Кодировщик рассматривает каждую позицию корректировки как носитель нескольких бит:

  1. Он шифрует полезную нагрузку шифром с аутентифицированным шифрованием со связанными данными (AEAD) — AES-256-GCM по умолчанию или ChaCha20-Poly1305. AEAD обеспечивает и конфиденциальность, и целостность, поэтому подделанный носитель приводит к сбою расшифровки, а не выдаёт молча неверную полезную нагрузку.
  2. Он выводит 32-байтовый ключ шифрования из вашего секретного ключа и ключа шрифта с помощью функции формирования ключа на основе HMAC (HKDF) с SHA-256. HKDF извлекает псевдослучайный ключ фиксированной длины из входного ключевого материала, затем расширяет его до требуемой длины, согласно RFC 5869 §2.
  3. Он генерирует свежий случайный 12-байтовый вектор инициализации (IV) для каждого шифрования. AES-GCM требует, чтобы IV был уникальным для данного ключа, иначе гарантия аутентификации теряется, согласно NIST SP 800-38D §5.2.1.
  4. Он сопоставляет зашифрованные байты с битовой последовательностью и распределяет биты по доступным позициям пар букв, кодируя один или два бита на позицию. Отклонение, которое он добавляет к естественному кернингу, ограничено настраиваемой долей em — достаточно малой, чтобы оставаться визуально незаметной.

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

Ёмкость растёт с длиной текста: каждая позиция пары букв несёт один или два бита, поэтому полезная нагрузка должна уместиться в позиции, которые предоставляет текст. Кодировщик вызывает типизированную ошибку переполнения, когда полезная нагрузка превышает ёмкость.

Режим совместимости с PDF/A вдвое уменьшает максимальное отклонение, чтобы оставаться ниже порога допуска по ширине валидатора, обменивая ёмкость на более строгое соответствие.

Ключевой выбор — прятать маркер в кернинге, а не в видимом наложении или поле метаданных. Маркер в метаданных тривиально удалить, а видимый штамп изменяет страницу. Отклонения кернинга вместо этого едут внутри текста, который получатель обязан сохранить, и остаются незаметными. Аутентифицированное шифрование — второй столп: подделанный или частичный носитель не проходит аутентификацию, поэтому декодировщик возвращает ничто, а не неверного получателя. Ключ выводится для каждого шрифта с помощью HKDF, привязывая маркер к контексту документа, а не к голому общему секрету. Отсюда прямо следует честное раскрытие устойчивости: маркер переживает обычное перераспределение, но не намеренную перезапись потока содержимого, поэтому заявленная область — внутреннее отслеживание утечек, а не состязательная стеганография.

Предыстория проектирования: Редактирование — это не чёрный прямоугольник.

  1. Установите NextPDF Core и пакет Enterprise и имейте активную лицензию Enterprise.
  2. Сформируйте документ со шрифтом, предоставляющим метрики пар кернинга; кодировщик читает естественный кернинг из метрик шрифта.
  3. Предоставьте секретный ключ из вашего менеджера секретов, а не из исходного кода. Тот же ключ требуется для декодирования.
  4. Определите глубину бит (один или два бита на позицию) и нужна ли совместимость с PDF/A, исходя из ваших потребностей в ёмкости и соответствии.

Конфигурация кодирования неизменяема и проверяется при конструировании:

  • Глубина бит — один или два бита на позицию пары букв. Большая глубина даёт больше ёмкости, но большие отклонения.
  • Максимальное соотношение корректировки — потолок отклонения как доля em, в ограниченном диапазоне. Большие значения дают больше запаса, но рискуют видимостью.
  • Шифр — AES-256-GCM (по умолчанию) или ChaCha20-Poly1305. Оба — AEAD.
  • Совместимость с PDF/A — при включении вдвое уменьшает эффективное максимальное отклонение.

Используйте одну и ту же конфигурацию для кодирования и декодирования; несовпадение не даёт восстановленной полезной нагрузки.

  1. Прочитайте секретный ключ из вашего менеджера секретов.
  2. Постройте конфигурацию кодирования (глубина бит, соотношение отклонения, шифр, флаг PDF/A).
  3. Вычислите корректировки кернинга для текста, который вы собираетесь отрисовать, передав полезную нагрузку, текст, ключ шрифта, метрики шрифта, секретный ключ и конфигурацию.
  4. Примените возвращённые корректировки при записи текстового фрагмента, чтобы маркер встраивался во время генерации.
  5. Чтобы отследить утёкшую копию, запустите декодировщик по потоку содержимого подозрительного документа с тем же ключом шрифта, метриками шрифта, секретным ключом и конфигурацией и прочитайте восстановленную полезную нагрузку.
examples/enterprise/steganography-capacity-guard.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
/**
* Reject a payload that cannot fit the carrier text before encoding.
*
* Each letter-pair position carries $bitDepth bits. Guarding capacity up
* front turns an unencodable payload into a clear caller-side error instead
* of relying on the encoder's overflow exception alone.
*
* @param non-empty-string $payload The bytes to embed (already minimal).
* @param positive-int $textLength The character count of the carrier text.
* @param int<1, 2> $bitDepth Bits encoded per letter-pair position.
*
* @throws \OverflowException When the payload cannot fit the available positions.
*/
function assertPayloadFits(string $payload, int $textLength, int $bitDepth): void
{
$positions = $textLength - 1;
$capacityBytes = \intdiv($positions * $bitDepth, 8);
if (\strlen($payload) > $capacityBytes) {
throw new \OverflowException(\sprintf(
'Payload of %d bytes exceeds carrier capacity of %d bytes.',
\strlen($payload),
$capacityBytes,
));
}
}
examples/enterprise/steganography-decode-guarded.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Enterprise\Security\Steganography\SteganographyDecoder;
use NextPDF\Enterprise\Security\Steganography\SteganographyConfig;
use NextPDF\Typography\FontMetrics;
use Psr\Log\LoggerInterface;
final readonly class LeakTracer
{
public function __construct(private LoggerInterface $logger) {}
/**
* Recover the embedded marker from a suspect document's content stream.
*
* Decoding returns null on a wrong key or a destroyed carrier rather than
* a wrong payload, so the caller treats null as "no marker recovered".
*
* @param string $contentStream The suspect content-stream bytes.
* @param non-empty-string $fontKey The font key used at generation.
* @param FontMetrics $metrics Font metrics with kerning pairs.
* @param string $secretKey The same secret key used to encode.
* @param SteganographyConfig $config The same configuration used to encode.
*
* @return string|null The recovered marker, or null when none is found.
*/
public function trace(
string $contentStream,
string $fontKey,
FontMetrics $metrics,
string $secretKey,
SteganographyConfig $config,
): ?string {
$marker = SteganographyDecoder::decodeFromContentStream(
$contentStream,
$fontKey,
$metrics,
$secretKey,
$config,
);
if ($marker === null) {
$this->logger->info('No steganographic marker recovered from content stream.');
}
return $marker;
}
}
  1. Закодируйте известную полезную нагрузку в известный текстовый фрагмент, затем декодируйте её обратно тем же ключом и конфигурацией; подтвердите, что восстановленная полезная нагрузка совпадает.
  2. Декодируйте с заведомо неверным ключом и подтвердите, что результат — null, а не неверная полезная нагрузка — это работа гарантии целостности AEAD.
  3. Осмотрите отрисованную страницу и подтвердите, что изменение расстояния не заметно визуально при настроенном соотношении отклонения.
  4. Когда требуется совместимость с PDF/A, проверьте вывод по вашему профилю PDF/A и подтвердите, что допуск по ширине не нарушен.
  • Аутентифицированное шифрование. Полезная нагрузка шифруется AES-256-GCM или ChaCha20-Poly1305. Подделанный или усечённый носитель приводит к сбою аутентификации при расшифровке; он не выдаёт неверной полезной нагрузки.
  • IV для каждого шифрования. Свежий случайный 12-байтовый IV генерируется для каждого шифрования, удовлетворяя требованию уникальности AES-GCM согласно NIST SP 800-38D §5.2.1.
  • Выведенный ключ. Ключ шифрования выводится с помощью HKDF-SHA-256 из вашего секрета и ключа шрифта (RFC 5869 §2). Храните секрет в вашем менеджере секретов; относитесь к нему как к любому секрету подписания.
  • Маркер — это содержимое документа. Встроенные байты являются частью содержимого страницы, а не содержимого журнала. Не записывайте полезную нагрузку или секретный ключ в журналы.

Эта страница касается криптографического встраивания. Каждый нормативный источник приведён в пересказе; нормативный текст не воспроизводится. ### Раскрытие устойчивости

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

  • Полезная нагрузка слишком велика. Кодировщик вызывает типизированную ошибку переполнения, когда полезная нагрузка превышает ёмкость текста. Сократите полезную нагрузку или удлините текст-носитель.
  • Слишком мало текста-носителя. Текст короче двух символов не предлагает позиции носителя и вызывает ошибку.
  • Неверный ключ при декодировании. Декодирование возвращает null. Рассматривайте null как «маркер не восстановлен», а не как частичный результат.
  • Несовпадение конфигурации. Кодирование и декодирование должны использовать одну и ту же глубину бит, соотношение отклонения, шифр и флаг PDF/A; несовпадение не даёт восстановленной полезной нагрузки.

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