Шифрование PDF и ограничение разрешений
Этот рецепт шифрует документ с помощью стандартного обработчика безопасности Advanced Encryption Standard (AES)-256. Вы задаёте пользовательский пароль (нужен для открытия), владельческий пароль (полный доступ) и битовую маску разрешений, ограничивающую операции. Считайте эти разрешения зависящими от поведения средства чтения: шифрование обеспечивает конфиденциальность, а не целостность, и только совместимое программное обеспечение соблюдает биты разрешений. Рецепт следует examples/22-protection.php.
Граница доверия (помните о ней при каждом утверждении о разрешениях). Шифрование PDF защищает конфиденциальность содержимого от сторон, не имеющих пароля (ISO 32000-2 §7.6). Оно не защищает целостность: оно не обнаруживает и не предотвращает изменение. Запись разрешений
P— это 32-битный беззнаковый набор флагов, который просит совместимые средства чтения соблюдать ограничения; это не управление доступом. Несоответствующий инструмент или любой инструмент, используемый с владельческим паролем, может выполнить любую “запрещённую” операцию. Не описывайте зашифрованный PDF как “безопасный”, “защищённый от подделки” или “защищённый от копирования”.
Установка
Заголовок раздела «Установка»composer require nextpdf/core:^3Включите PHP-расширение openssl. Шифровальщик AES-256 использует его для шифрования и формирования ключа.
Концептуальный обзор
Заголовок раздела «Концептуальный обзор»Коды V/R в словаре шифрования выбирают стандартный обработчик безопасности (ISO 32000-2 §7.6). NextPDF в своём Aes256Encryptor реализует крипто-фильтр AESV3 для редакции 6 обработчика безопасности (V=5/R=6). Он использует случайный 256-битный ключ шифрования файла, формирование ключа итеративным хешированием с солью (Algorithm 2.B) и пообъектное шифрование AES-256-CBC со случайным вектором инициализации. Cipher Block Chaining (CBC) — это режим конфиденциальности (NIST SP 800-38A). Его векторы инициализации должны быть непредсказуемыми.
Вектор инициализации формируется заново для каждого объекта и каждого запуска, поэтому необработанные байты различаются между запусками. Поэтому профиль воспроизводимости — structural. Перед сравнением двух запусков средство тестирования приводит к канонической форме вектор инициализации шифрования, порядок объектов и /ID трейлера. Этот профиль строже, чем профиль рецепта без шифрования.
Битовая маска разрешений задаёт запись P. Бит 3 разрешает печать, а бит 6 разрешает annotation/form-fill. Значение — это документированная 32-битная беззнаковая величина.
Поверхность API
Заголовок раздела «Поверхность API»NextPDF\Core\Concerns\HasSecurity (подключён к Document):
setEncryption(#[SensitiveParameter] string $userPassword, #[SensitiveParameter] string $ownerPassword = '', int $permissions = -1): static— настраивает шифрование AES-256 через стандартный обработчик.permissions = -1предоставляет все разрешения. ЕслиownerPasswordпуст, пользовательский пароль повторно используется как владельческий пароль. Вызывайте передaddPage().getEncryptor(): ?Aes256Encryptor— настроенный шифровальщик илиnull.useAesGcm(?bool $enabled = true): static— включает AES-256-GCM по ISO/TS 32003; выбрасывает исключение, если хостовый OpenSSL/libsodium не предоставляет шифр.
Оба параметра пароля помечены #[SensitiveParameter], поэтому PHP скрывает их в трассировках стека.
Биты разрешений (запись P, младшие биты 3–6 в типичном использовании):
| Бит | Значение | Операция |
|---|---|---|
| 3 | 4 | Печать документа |
| 4 | 8 | Изменение содержимого документа |
| 5 | 16 | Копирование / извлечение текста и графики |
| 6 | 32 | Добавление или изменение аннотаций и заполнение полей форм |
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setTitle('Confidential Memo');
// Grant printing only (bit 3 = 4). MUST run before addPage().$doc->setEncryption( userPassword: 'open-me', ownerPassword: 'owner-secret', permissions: 4,);
$doc->addPage();$doc->setFont('helvetica', '', 12);$doc->cell(0, 10, 'Encrypted with AES-256; printing allowed only.', newLine: true);
$doc->save(__DIR__ . '/confidential.pdf');echo "Wrote confidential.pdf\n";Пример кода — рабочая среда
Заголовок раздела «Пример кода — рабочая среда»Полный пример ниже повторяет examples/22-protection.php и записывает результат в NEXTPDF_COOKBOOK_OUTPUT для средства тестирования.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$userPassword = 'demo';$ownerPassword = 'admin';
// Grant ONLY printing (bit 3 = 4); deny copy/modify/annotate.$permissions = 4;
$doc = Document::createStandalone();$doc->setTitle('Encrypted Document — Restricted Permissions');$doc->setAuthor('NextPDF Example');
// setEncryption() MUST be called before addPage().$doc->setEncryption( userPassword: $userPassword, ownerPassword: $ownerPassword, permissions: $permissions,);
$doc->addPage();$doc->setFont('helvetica', 'B', 20);$doc->cell(0, 14, 'Encrypted PDF Document', newLine: true);$doc->ln(8);
$doc->setFont('helvetica', '', 11);$doc->multiCell(0, 7, 'This document is protected with AES-256 encryption ' . '(standard security handler, revision 6). The user password is required ' . 'to open it; the owner password grants full access. The permission ' . 'bits below are honoured by conforming readers only.');$doc->ln(5);
$permissionTable = [ ['Bit 3 (4)', 'Printing', 'ALLOWED'], ['Bit 4 (8)', 'Content modification', 'DENIED'], ['Bit 5 (16)', 'Text copying / extraction', 'DENIED'], ['Bit 6 (32)', 'Annotations / form fields', 'DENIED'],];$doc->setFont('helvetica', 'B', 10);$doc->cell(30, 7, 'Flag');$doc->cell(60, 7, 'Operation');$doc->cell(0, 7, 'Status', newLine: true);foreach ($permissionTable as [$bit, $operation, $status]) { $doc->setFont('courier', '', 9); $doc->cell(30, 7, $bit); $doc->setFont('helvetica', '', 10); $doc->cell(60, 7, $operation); $doc->setFont('helvetica', 'B', 10); $doc->cell(0, 7, $status, newLine: true);}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$doc->save($out !== false ? $out : __DIR__ . '/encrypted.pdf');
echo "Wrote encrypted PDF (AES-256, printing only)\n";Ожидаемый вывод:
Wrote encrypted PDF (AES-256, printing only)При открытии файла средство чтения запрашивает пароль. Пользовательский пароль открывает файл с ограниченным набором разрешений. Владельческий пароль открывает его с полным доступом.
Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»- Порядок вызовов.
setEncryption()послеaddPage()не шифрует задним числом ранее добавленное содержимое. Сначала настройте шифрование; движок шифрует тело каждого объекта при записи. - Владельческий пароль по умолчанию. Пустой владельческий пароль заставляет движок повторно использовать пользовательский пароль как владельческий. Это фактически не оставляет привилегированной роли. Задавайте разные пароли, когда две роли должны различаться.
- Семантика разрешений носит рекомендательный характер. Только совместимые средства чтения соблюдают биты. Они не обеспечиваются криптографически: несовместимый инструмент или любой инструмент, используемый с владельческим паролем, может выполнять ограниченные операции. Считайте разрешения сигналом политики для сотрудничающего программного обеспечения, но никогда — управлением доступом, которое устоит перед стороной, целенаправленно обходящей ограничения.
- Нет гарантии целостности. Шифрование — это конфиденциальность, а не целостность. Злоумышленник без пароля не может прочитать содержимое, но сам формат не обнаруживает подделки. Для защиты целостности нужен отдельный механизм, например цифровая подпись или MAC документа по ISO/TS 32004.
- Конфликт с PDF/A. PDF/A запрещает ключ трейлера
Encrypt. ВызовsetEncryption()для документа PDF/A в любом порядке выбрасывает исключение несовместимости. - Включение AES-256-GCM.
useAesGcm()выбирает массовое шифрование GCM по ISO/TS 32003, когда хостовый OpenSSL или libsodium его предоставляет. В противном случае метод выбрасываетInvalidConfigException. Он несовместим с PDF/A по той же причине. - Шифрование с открытым ключом пока не подключено.
setPublicKeyEncryption()фиксирует поверхность API, ноsave()выбрасывает исключение, пока не будет подключён записывающий модуль (известный дефект). Не используйте его в рабочей среде на Core.
Производительность
Заголовок раздела «Производительность»Формирование ключа выполняет итеративное хеширование Algorithm 2.B один раз на документ. Пообъектное AES-256-CBC линейно зависит от размера тела объекта. Для типичных документов затраты с большим запасом укладываются в бюджет 1500 мс / 64 МБ. Очень большие документы требуют пропускной способности AES для каждого объекта. Galois/Counter Mode (GCM) с AES-NI работает быстрее на хостах с такой поддержкой.
Заметки по безопасности
Заголовок раздела «Заметки по безопасности»- Только конфиденциальность. Повторим границу доверия: шифрование не даёт прочитать содержимое сторонам, не имеющим пароля. Оно не доказывает, что файл не изменён, а биты разрешений зависят от поведения средства чтения.
- Стойкость пароля — на вас. Обработчик настолько же стоек, насколько стойки пароли. Как только кто-то получает файл, слабый пользовательский пароль можно подобрать офлайн; формат не может ограничивать частоту попыток.
- Владельческий пароль — это главный ключ. Любой, у кого есть владельческий пароль, обходит любое ограничение. Относитесь к нему как к учётным данным root; никогда не поставляйте его вместе с документом и не записывайте в журнал.
#[SensitiveParameter]— это дополнительный уровень защиты. Он скрывает пароли в трассировках стека PHP, но вы по-прежнему должны не допускать их попадания в собственные журналы, сообщения об исключениях и отчёты о сбоях.
Размещение данных и меры по защите ПДн
Заголовок раздела «Размещение данных и меры по защите ПДн»Библиотека выполняет шифрование внутри процесса. Она никуда не передаёт документ или пароли. Движок не записывает на диск ни пароль, ни ключ, ни байт документа, кроме сохраняемого вами зашифрованного вывода. Где размещается выходной файл и как хранятся пароли — это вопросы развёртывания, за которые отвечает интегратор. Библиотека не гарантирует размещение данных. Если незашифрованный документ содержит персональные данные, эти данные защищены лишь настолько, насколько стоек самый слабый пароль и насколько верна изложенная выше оговорка о поведении средства чтения. Шифрование не заменяет минимизацию объёма персональных данных (ПДн), которые вы помещаете в документ.
Безопасная телеметрия и очистка журналов
Заголовок раздела «Безопасная телеметрия и очистка журналов»Шифрование порождает EncryptionAppliedEvent, который несёт только имя алгоритма (AES-256) и три логических значения, обобщающих, разрешены ли print/copy/modify — ни пароль, ни ключ, ни соль, ни вектор инициализации никогда не помещаются в событие (src/Event/Security/EncryptionAppliedEvent.php). Путь OpenTelemetry направляет атрибуты span через очиститель на основе списка разрешений (src/Telemetry/AttributeSanitizer.php), который безусловно отклоняет пароли и пути к файлам; сохраняются только ключи из списка разрешённых со скалярными значениями. Не добавляйте пароли или ключевой материал в span, журналы или сообщения об исключениях в собственном интеграционном коде. Маркеры #[SensitiveParameter] защищают трассировки стека, но не строки, которые вы формируете сами.
Модель угроз
Заголовок раздела «Модель угроз»В области рассмотрения: злоумышленник, который получает зашифрованный файл, но не пароли. При условии стойкости пароля он не может прочитать содержимое, и файл не раскрывает открытый текст. Вне области рассмотрения: злоумышленник, у которого есть пользовательский или владельческий пароль; несовместимое средство чтения, которое игнорирует биты разрешений; офлайн-подбор слабого пароля; обнаружение подделки (шифрование обеспечивает конфиденциальность, а не целостность); побочные каналы в хостовой сборке OpenSSL; а также хранение ключей, которое целиком является ответственностью интегратора. Документирование этих угроз не утверждает отсутствие уязвимостей.
Поведение в режиме FIPS
Заголовок раздела «Поведение в режиме FIPS»Хостовая сборка OpenSSL предоставляет криптографические примитивы, поэтому статус FIPS — это свойство хоста, а не настройка библиотеки. CryptoCapabilities::detectFipsMode() возвращает трёхзначный FipsModeDetection (src/Security/FipsModeDetection.php): FIPS_ACTIVE, FIPS_ABSENT или INDETERMINATE. PHP-расширение openssl не предоставляет привязки к провайдерной модели OpenSSL 3, поэтому проверка выполняется по мере доступных возможностей. INDETERMINATE трактуется как “FIPS не подтверждён” (fail-closed) и различим в телеметрии так, чтобы оператор мог принять меры. NextPDF не заявляет о валидации по FIPS 140; запуск на OpenSSL, прошедшем валидацию FIPS, является ответственностью оператора, а результат обнаружения носит рекомендательный характер.
Соответствие
Заголовок раздела «Соответствие»| Утверждение | Спецификация | Пункт | reference_id (идентификатор) |
|---|---|---|---|
Код V словаря шифрования выбирает алгоритм шифрования. | ISO 32000-2 | §7.6 | |
Метод крипто-фильтра AESV3 именуется записью CFM. | ISO 32000-2 | §7.6 | |
Запись P — это 32-битная беззнаковая величина прав доступа. | ISO 32000-2 | §7.6 | |
| Бит разрешений 3 управляет печатью. | ISO 32000-2 | §7.6 | |
| Бит разрешений 6 управляет аннотированием / заполнением форм. | ISO 32000-2 | §7.6 | |
| Шифрование защищает содержимое от несанкционированного доступа (конфиденциальность). | ISO 32000-2 | §7.6 | |
| Формирование ключа редакции 6 использует итеративное хеширование с солью (Algorithm 2.B). | ISO 32000-2 | §7.6 | |
| CBC — это режим конфиденциальности (не режим целостности). | NIST SP 800-38A (стандарт) | §6.2 | |
| Векторы инициализации CBC должны быть непредсказуемыми. | NIST SP 800-38A (стандарт) | Прил. C |
NextPDF реализует процитированные пункты. Он не заявляет о полном соответствии ISO 32000-2, валидации FIPS 140 или каком-либо правовом либо договорном обязательстве по конфиденциальности. “Поддержка стандартного обработчика безопасности” не является сертификацией безопасности в вашем развёртывании. Фактическая безопасность зависит от хранения паролей и политики проверяющей стороны за пределами библиотеки.