Продвинутое шифрование
На этой странице описана внутренняя реализация шифрования в TCPDF-Next Pro. Она охватывает обработчик AES-256 AESV3, алгоритм деривации ключей, нормализацию паролей и безопасную обработку параметров. Для базового использования шифрования см. Шифрование AES-256.
AES-256 с обработчиком AESV3
TCPDF-Next Pro реализует Standard Security Handler ISO 32000-2 (PDF 2.0) ревизии 6, который обязывает использовать AES-256-CBC для шифрования всех потоков и строк. Обработчик идентифицируется как /V 5 и /R 6 в словаре шифрования.
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;
$encryptor = new Aes256Encryptor(
ownerPassword: 'Str0ng!OwnerP@ss',
userPassword: 'reader2026',
);Почему нет RC4 или AES-128
TCPDF-Next Pro намеренно исключает RC4 (40-бит и 128-бит) и AES-128:
| Алгоритм | Причина исключения |
|---|---|
| RC4-40 | Взломан с 1995 года; тривиально атакуем |
| RC4-128 | Смещения в потоке ключей; запрещён PDF 2.0 |
| AES-128 | Заменён AES-256 в ревизии 6; не совместим вперёд |
PDF 2.0 (ISO 32000-2:2020) требует AESV3 для новых документов. Поддержка более слабых алгоритмов подорвала бы безопасность и нарушила спецификацию.
Algorithm 2.B: деривация ключей
Ключ шифрования файла деривируется из пароля с помощью Algorithm 2.B (ISO 32000-2, clause 7.6.4.3.4). Это итеративный процесс, объединяющий SHA-256, SHA-384 и SHA-512:
function computeHash(password, salt, userKey = ''):
K = SHA-256(password || salt || userKey)
round = 0
lastByte = 0
while round < 64 OR lastByte > round - 32:
K1 = (password || K || userKey) repeated 64 times
E = AES-128-CBC(key = K[0..15], iv = K[16..31], data = K1)
mod3 = (sum of all bytes in E) mod 3
if mod3 == 0: K = SHA-256(E)
elif mod3 == 1: K = SHA-384(E)
else: K = SHA-512(E)
lastByte = E[len(E) - 1]
round += 1
return K[0..31] // 32-byte file encryption keyЭтот итеративный хешинг делает атаки перебором вычислительно затратными, оставаясь при этом достаточно быстрым для легитимного использования.
Компоненты ключей в словаре шифрования
| Запись | Длина | Назначение |
|---|---|---|
/O | 48 байт | Валидация пароля владельца (хеш + соль валидации) |
/U | 48 байт | Валидация пароля пользователя (хеш + соль валидации) |
/OE | 32 байта | Ключ шифрования файла, зашифрованный паролем владельца |
/UE | 32 байта | Ключ шифрования файла, зашифрованный паролем пользователя |
/Perms | 16 байт | Флаги разрешений, зашифрованные AES-256 |
Нормализация паролей SASLprep
Перед любой криптографической операцией пароли нормализуются с помощью SASLprep (RFC 4013), который является профилем stringprep (RFC 3454). Это обеспечивает единообразную обработку паролей независимо от формы нормализации Unicode, используемой операционной системой или методом ввода.
use Yeeefang\TcpdfNext\Pro\Security\SaslPrep;
$normalized = SaslPrep::prepare('P\u{00E4}ssw\u{00F6}rd');
// Normalizes composed/decomposed forms, maps certain characters,
// and rejects prohibited codepoints.Что делает SASLprep
- Маппинг -- Символы, обычно отображаемые в ничто (например, мягкий дефис
U+00AD), удаляются. - Нормализация -- Строка преобразуется в Unicode NFC (каноническая декомпозиция с последующей канонической композицией).
- Запрет -- Символы из RFC 3454 Table C.1.2 -- C.9 отклоняются (управляющие символы, private use, суррогаты, не-символы и т.д.).
- Проверка двунаправленности -- Строки с символами как слева направо, так и справа налево валидируются по RFC 3454 clause 6.
Это означает, что пользователь, вводящий U+00FC (LATIN SMALL LETTER U WITH DIAERESIS), и другой пользователь, вводящий U+0075 U+0308 (LATIN SMALL LETTER U + COMBINING DIAERESIS), получат одинаковый ключ шифрования.
Кодирование разрешений
Разрешения хранятся в записи /Perms как 16-байтовый блок, зашифрованный AES-256-ECB. Структура открытого текста:
Bytes 0-3: Permission flags (little-endian int32)
Bytes 4-7: 0xFFFFFFFF
Byte 8: 'T' if EncryptMetadata is true, 'F' otherwise
Bytes 9-11: 'adb'
Bytes 12-15: Random paddingФлаги разрешений следуют той же битовой раскладке, определённой в ISO 32000-2 Table 22.
Обработка зашифрованных потоков
Каждый поток контента и строка в PDF шифруются индивидуально:
- Уникальный 16-байтовый вектор инициализации (IV) генерируется для каждого потока/строки с помощью
random_bytes(16). - IV предваряет шифротекст.
- Перед шифрованием применяется выравнивание PKCS#7.
- Фильтр
/Cryptс/AESV3устанавливается для всех параметров декодирования потоков.
// Internal -- handled automatically by the writer
$iv = random_bytes(16);
$padded = pkcs7_pad($plaintext, blockSize: 16);
$encrypted = openssl_encrypt($padded, 'aes-256-cbc', $fileKey, OPENSSL_RAW_DATA, $iv);
$output = $iv . $encrypted;WARNING
TCPDF-Next Pro всегда шифрует данные потоков. Флаг /EncryptMetadata по умолчанию равен true. Если установить в false, поток метаданных XMP остаётся незашифрованным (полезно для поисковой индексации), но все остальные потоки по-прежнему шифруются.
Безопасная обработка параметров
Все методы, принимающие пароли, аннотированы атрибутом PHP 8.2 #[\SensitiveParameter]. Это предотвращает появление паролей в стек-трейсах, отладочном выводе и логах ошибок:
public function setOwnerPassword(
#[\SensitiveParameter] string $password,
): self {
$this->ownerPassword = SaslPrep::prepare($password);
return $this;
}При возникновении исключения стек-трейс покажет Object(SensitiveParameterValue) вместо фактической строки пароля.
Полный пример
use Yeeefang\TcpdfNext\Core\Document;
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;
use Yeeefang\TcpdfNext\Pro\Security\Permissions;
$pdf = Document::create()
->setTitle('Confidential Report')
->addPage()
->setFont('Helvetica', size: 12)
->multiCell(0, 6, 'This document is protected with AES-256 encryption.');
$encryptor = new Aes256Encryptor(
ownerPassword: 'Str0ng!OwnerP@ss',
userPassword: 'reader2026',
permissions: new Permissions(
print: true,
printHighQuality: false,
copy: false,
modify: false,
annotate: true,
fillForms: true,
extractForAccess: true,
assemble: false,
),
);
$pdf->encrypt($encryptor)
->save(__DIR__ . '/encrypted.pdf');