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

Шифрование: AES-256 (CBC) и AES-256-GCM

Core шифрует файлы Portable Document Format (PDF) с помощью AES-256 (Advanced Encryption Standard с 256-битными ключами) в соответствии со стандартным обработчиком безопасности ISO 32000-2:2020 §7.6. Режим по умолчанию — V=5 / R=6 / AESV3 (AES-256-CBC, Cipher Block Chaining). Опциональный аутентифицированный режим — путь AES-256-GCM (Galois/Counter Mode) по ISO/TS 32003:2023 V=6 / R=7. Эта страница определяет вывод ключа, формат представления, границу разрешений и эксплуатационные ограничения.

Окно терминала
composer require nextpdf/core:^3

Для пути по умолчанию требуется расширение openssl. Путь AES-256-GCM использует openssl или ext-sodium. На узлах без аппаратного AES-NI libsodium отказывается выполнять GCM; Core переключается на более медленную реализацию OpenSSL, не меняя алгоритм.

По умолчанию используется стандартный обработчик безопасности V=5 / R=6 с крипто-фильтром AESV3. При вызове setEncryption() Core генерирует случайный 256-битный ключ файла с помощью криптографического источника случайности платформы (random_bytes()). Ключ имеет размер 32 байта, что соответствует длине ключа FIPS 197. Core шифрует содержимое каждого объекта с помощью AES-256-CBC. Перед каждым шифротекстом он добавляет 16-байтовый вектор инициализации, как предписывает ISO 32000-2:2020 §7.6.4.

Ключ выводится по алгоритму 2.B для ревизии 6. Core сначала нормализует пароль с помощью SASLprep (RFC 4013), затем усекает его до 127 байт UTF-8 на границе символа, как предписывает ISO 32000-2:2020 §7.6.4.3.3. Он вычисляет производный хеш по итеративной процедуре SHA-256 / SHA-384 / SHA-512 с шагом AES-128-CBC, что повышает стоимость офлайн-подбора пароля. Core генерирует соли пользователя, владельца и отдельную соль ключа один раз на экземпляр шифровщика, поэтому один экземпляр формирует детерминированные байты словаря — необходимое условие для многопроходной записи.

useAesGcm() включает опциональный путь AES-256-GCM. Он реализует крипто-фильтр AESV4 по ISO/TS 32003:2023 V=6 / R=7. Шифр — AES-256-GCM с параметрами из NIST SP 800-38D. Для каждого зашифрованного объекта формат представления такой: 12-байтовый IV, шифротекст и 16-байтовый тег аутентификации. Дополнительные аутентифицируемые данные пусты, как предписывает профиль TS 32003 §5.2. Расшифрование проверяет тег и при несовпадении выбрасывает TamperedDataException; после неудачной проверки тега оно никогда не возвращает открытый текст. Этот путь добавляет обнаружение изменений, которого путь CBC по умолчанию сам по себе не обеспечивает.

Путь GCM соблюдает правило уникальности IV из NIST SP 800-38D §8. Старшие 4 байта IV — фиксированное для экземпляра поле, задаваемое из случайного источника при конструировании. Младшие 8 байт — счётчик с порядком байтов big-endian, который увеличивается после каждого выданного IV. Это соответствует подходу детерминированного конструирования из §8.2.1, за исключением того, что фиксированное поле рандомизировано для предотвращения коллизий между документами, а не перечисляется. Дополнительная защита записывает каждый выданный IV в набор коллизий и выбрасывает NonceReuseException, если значение повторяется. Переполнение счётчика также выбрасывает NonceReuseException, поскольку оно ведёт к режиму отказа с повторным использованием IV, от которого предостерегает §8.

Для пути GCM действуют два ограничения длины. Предельный размер открытого текста на объект составляет 2^39 − 256 байт — ограничение на одно обращение из NIST SP 800-38D §5.2.1.1. Более крупный ввод выбрасывает исключение длины с рекомендацией распределить данные по объектам. Граница безопасности по числу обращений — 2^32 вызовов на ключ. assertWithinSafetyBound() — опциональная проверка: она выбрасывает GcmInvocationLimitExceededException, чтобы вызывающая сторона могла сменить ключ документа до порога §8.3. NIST SP 800-57 Part 1 §4 относит это решение о времени жизни ключа к ответственности развёртывания.

Флаги разрешений носят рекомендательный характер. Core записывает битовую маску в зашифрованную запись /Perms и значение /P, а при чтении восстанавливает её с помощью validatePerms(), так что повреждённый маркер приводит к безопасному отказу. Ожидается, что читатель, соответствующий стандарту, соблюдает эти флаги. Эти флаги не обеспечиваются криптографией: обработчик с ключом расшифрования, который игнорирует биты, может читать, копировать или изменять содержимое. Описывайте флаги разрешений как соглашение читателя, а не как контроль доступа.

ТипВидКлючевые членыСтабильностьС версии
Aes256Encryptorclassencrypt(), decrypt(), encryptForObject(), buildEncryptionDictionary(), verifyUserPassword(), verifyOwnerPassword(), validatePerms(), getEncryptionKey()stable1.0.0
Aes256GcmEncryptorclassencrypt(), decrypt(), encryptStream(), assertWithinSafetyBound(), invocationCount(), isAvailable()stable2.18.0
KeyMaterialfinal readonly classgenerate(), exposeKey(), fingerprint()stable2.18.0
EncryptedPayloadSpecfinal readonly classtoDict()stable2.18.0
CryptoCapabilitiesfinal classhasAesGcm(), detectFipsMode(), assertFipsAvailableForProfile()stable2.0.0
NonceReuseExceptionexceptionstable2.18.0
TamperedDataExceptionexceptionstable2.18.0
DecryptionFailedExceptionexceptionstable2.18.0
GcmInvocationLimitExceededExceptionexceptionstable3.0.0
examples/22-protection.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
// AES-256-CBC, V=5/R=6. Call before addPage().
$doc->setEncryption(
userPassword: 'demo',
ownerPassword: 'admin',
permissions: 4, // printing only; copy/modify denied for a conforming reader
);
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 8, 'Confidential', newLine: true);
$doc->save(__DIR__ . '/output/22-protection.pdf');
examples/security/gcm-authenticated-encryption.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Security\CryptoCapabilities;
use NextPDF\Security\Encryption\Aes256GcmEncryptor;
use NextPDF\Security\Exception\TamperedDataException;
use NextPDF\Security\KeyMaterial;
use Psr\Log\LoggerInterface;
final readonly class AuthenticatedBlobCipher
{
public function __construct(private LoggerInterface $logger) {}
/**
* Seal a payload with AES-256-GCM and return the wire-format bytes.
*
* @param non-empty-string $plaintext The payload to protect.
*
* @return non-empty-string IV(12) || ciphertext || tag(16).
*/
public function seal(string $plaintext, KeyMaterial $key): string
{
if (!CryptoCapabilities::hasAesGcm()) {
throw new \RuntimeException('Host cannot perform AES-256-GCM.');
}
$cipher = new Aes256GcmEncryptor($key);
// Opt-in NIST SP 800-38D §8.3 key-rotation guard.
$cipher->assertWithinSafetyBound();
$wire = $cipher->encrypt($plaintext);
$this->logger->info('Payload sealed', [
'key_fingerprint' => $key->fingerprint(),
'invocations' => $cipher->invocationCount(),
]);
return $wire;
}
/**
* Open a sealed payload; a modified payload raises, never returns plaintext.
*
* @param non-empty-string $wire IV(12) || ciphertext || tag(16).
*/
public function open(string $wire, KeyMaterial $key): string
{
try {
return (new Aes256GcmEncryptor($key))->decrypt($wire);
} catch (TamperedDataException $e) {
$this->logger->warning('Tampered payload rejected', [
'key_fingerprint' => $key->fingerprint(),
]);
throw $e;
}
}
}

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

  • Путь AES-256-CBC по умолчанию обеспечивает только конфиденциальность. Сам по себе он не обнаруживает изменённый шифротекст. Используйте путь AES-256-GCM, когда нужно обнаруживать изменения.
  • useAesGcm() выбрасывает исключение, если активен режим PDF/A или если ни openssl, ни ext-sodium не предоставляют AES-256-GCM. Перехватывайте оба случая и выводите сообщение, по которому оператор может принять меры.
  • На узлах без AES-NI libsodium отказывается выполнять GCM. Core переключается на OpenSSL GCM, что корректно, но медленнее; снижается пропускная способность, а не безопасность.
  • Предельный размер открытого текста GCM на объект составляет 2^39 − 256 байт. Более крупный ввод выбрасывает исключение длины; распределите содержимое по нескольким объектам с помощью encryptStream().
  • Экземпляр KeyMaterial должен иметь размер ровно 32 байта. Конструктор отклоняет неверную длину, а не усекает её.
  • Путь чтения (verifyUserPassword(), verifyOwnerPassword(), validatePerms()) использует сравнение с постоянным временем выполнения для криптографического материала и при повреждённом маркере разрешений безопасно отказывает.

Шифрование AES-256-CBC одного объекта — это один вызов OpenSSL со сложностью O(n) по размеру тела объекта. При выводе ключа итеративная процедура алгоритма 2.B выполняется один раз на экземпляр шифровщика; стоимость ограничена и постоянна для документа. Потоковый путь AES-256-GCM разбивает ввод на блоки по 16 MiB, удерживая активное использование кучи примерно на уровне 64 MB независимо от общего размера ввода и оставаясь в пределах документированного пикового бюджета 64 MB. Каждый объект GCM добавляет 28 байт накладных расходов (12-байтовый IV плюс 16-байтовый тег). Аппаратный AES-NI существенно повышает пропускную способность GCM; без него снижается только пропускная способность.

У этой поверхности шифрования явная модель угроз. Нормализация SASLprep вместе с итеративным выводом ключа ревизии 6 повышает стоимость офлайн-подбора пароля, но слабый пароль остаётся главным остаточным риском. Никакой алгоритм вывода ключа не устраняет этот риск. Путь GCM обнаруживает изменение шифротекста через проверку тега; путь CBC по умолчанию этого не делает. В пути GCM счётчик вместе с набором коллизий предотвращает повторное использование IV, что согласуется с правилом IV из NIST SP 800-38D §8.1. При переполнении счётчика происходит отказ, а не зацикливание. Маскирование KeyMaterial и атрибут #[\SensitiveParameter] на паролях снижают риск раскрытия ключа через логи. Производный ключевой материал обнуляется после использования там, где это позволяет платформа.

Граница тоже явно задана. Core применяет шифрование AES-256, как определено в ISO 32000-2:2020 §7.6, а для опционального пути — в ISO/TS 32003:2023 §5.2. Эффективная защита зависит от стойкости пароля, управления ключами, среды развёртывания и читателя, который обрабатывает документ. Читатели, соответствующие стандарту, соблюдают флаги разрешений, но криптография их не обеспечивает. Шаг AES-ECB, используемый для значения /Perms, предписан ISO 32000-2:2020 §7.6.4.4.10 для одного 16-байтового блока. Это не режим общего назначения. Core предоставляет проверку, позволяющую сменить ключ до достижения границы в 2^32 обращений, но по умолчанию её не обеспечивает; эта смена — ответственность развёртывания.

Шифрование и расшифрование выполняются внутри процесса; ни байты документа, ни пароль, ни значение ключа не покидают узел через эту поверхность. Набор коллизий IV для GCM использует в качестве ключа необратимый отпечаток ключа, а не байты ключа. Если развёртывание размещает ключ во внешней системе управления ключами или токене PKCS#11, за резидентность отвечает этот бэкенд; OASIS PKCS#11 v3.1 C_GenerateKey — точка контракта для генерации ключа, размещаемого на токене.

Логируйте имя политики и 8-символьный отпечаток ключа, но никогда — сам ключ или пароль. KeyMaterial::__toString() и __debugInfo() возвращают замаскированную заглушку. Исключения этой поверхности содержат метку операции и отпечаток, а не байты ключа. Счётчик обращений GCM — безопасная телеметрия для панелей мониторинга смены ключей.

УгрозаМеры в CoreОстаточная граница
Офлайн-подбор пароляSASLprep плюс итеративный вывод ревизии 6Слабый пароль по-прежнему главный риск
Изменение шифротекстаПроверка тега GCM (опциональный путь)Путь CBC обеспечивает только конфиденциальность
Повторное использование IV (GCM)Случайное фиксированное поле плюс счётчик плюс набор коллизий; переполнение отказывает
Слишком длинный открытый текст GCMПроверка длины на 2^39 − 256; рекомендация по разбиениюВызывающая сторона должна передавать большой ввод потоком
Избыточное использование ключа (GCM)assertWithinSafetyBound() на 2^32Опционально; по умолчанию не обеспечивается
Обход флагов разрешенийНет — флаги носят рекомендательный характерНесоответствующий стандарту читатель игнорирует эти флаги
Раскрытие ключа через логиМаскирование KeyMaterial; #[\SensitiveParameter]Вызывающая сторона, логирующая exposeKey(), сводит это на нет

Core не является криптографическим модулем, валидированным по FIPS, и не сертифицирован по FIPS. CryptoCapabilities::detectFipsMode() — это проба по принципу наилучших усилий, которая сообщает “активен”, “отсутствует” или “не определено”. assertFipsAvailableForProfile() безопасно отказывает, когда профиль FIPS выбран на узле, который не подтверждает наличие провайдера FIPS. Поверхность шифрования работает в FIPS-совместимом режиме, когда выполняется на сборке OpenSSL узла, загрузившей провайдер, прошедший валидацию FIPS. Валидированная, сертифицированная конфигурация — задача Enterprise.

УтверждениеСтандартПунктСвидетельство
Каждый IV для GCM уникален для каждого обращения благодаря детерминированному конструированию “фиксированное поле плюс счётчик”.NIST SP 800-38D§8.2.1
Правило конструирования IV предотвращает повторное использование между обращениями на одном ключе.NIST SP 800-38D§8.1
Предельный размер открытого текста на объект соответствует ограничению длины на одно обращение.NIST SP 800-38D§5.2.1.1
Криптопериод ключа и его смена — ответственность развёртывания.NIST SP 800-57 Часть 1, ред. 5§4
Ключ файла AES имеет размер 256 бит, что соответствует длине ключа стандарта.FIPS 197§4.2.1
Генерация ключа, размещаемого на токене, — точка интеграции с внешним хранилищем ключей.OASIS PKCS#11 v3.1C_GenerateKey

ISO 32000-2:2020 §7.6 и ISO/TS 32003:2023 §5.2 — нормативная основа для обработчиков, описанных здесь. Их текст ограничен лицензией. Эта страница пересказывает эти стандарты, ссылается на пункты по номерам и не цитирует ни один из них. Стандартный тест алгоритма 2.B и фикстура внешнего оракула в трейлере свидетельств страницы дают проверяемое во время выполнения свидетельство побайтово точного вывода ключа.

Core поставляет путь AES-256-CBC по умолчанию, опциональный путь AES-256-GCM, поверхность локальных ключей и шлюз криптополитики. Издание Enterprise добавляет бэкенд хранения ключей HSM/PKCS#11 и профиль криптополитики режима FIPS за теми же контрактами. Публичный API идентичен; различаются бэкенд хранения ключей и реализация политики.

  • Security — обзор модуля безопасности и граница разрешений.
  • Contracts / Security Policy — контракт криптополитики, который управляет доступом к шифру.
  • Security / Signing — подписи и метки времени, родственная криптографическая поверхность.
  • Conformance — запрет PDF/A на ключ Encrypt.