Criptografia Avançada
Esta página documenta a implementação interna de criptografia no TCPDF-Next Pro. Ela cobre o handler AES-256 AESV3, o algoritmo de derivação de chaves, normalização de senhas e manipulação segura de parâmetros. Se você está procurando uso básico de criptografia, veja o exemplo de Criptografia AES-256.
AES-256 com Handler AESV3
O TCPDF-Next Pro implementa o Standard Security Handler do ISO 32000-2 (PDF 2.0) revisão 6, que exige AES-256-CBC para toda criptografia de streams e strings. O handler é identificado por /V 5 e /R 6 no dicionário de criptografia.
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;
$encryptor = new Aes256Encryptor(
ownerPassword: 'Str0ng!OwnerP@ss',
userPassword: 'reader2026',
);Por Que Não RC4 ou AES-128
O TCPDF-Next Pro deliberadamente exclui RC4 (40 bits e 128 bits) e AES-128:
| Algoritmo | Razão da Exclusão |
|---|---|
| RC4-40 | Quebrado desde 1995; atacado trivialmente |
| RC4-128 | Vieses no keystream; proibido pelo PDF 2.0 |
| AES-128 | Substituído pelo AES-256 na revisão 6; não é compatível com versões futuras |
O PDF 2.0 (ISO 32000-2:2020) exige AESV3 para novos documentos. Suportar algoritmos mais fracos comprometeria a postura de segurança e violaria a especificação.
Algoritmo 2.B: Derivação de Chaves
A chave de criptografia do arquivo é derivada da senha usando o Algoritmo 2.B (ISO 32000-2, cláusula 7.6.4.3.4). Este é um processo iterativo que encadeia SHA-256, SHA-384 e 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 keyEste hashing iterativo torna ataques de força bruta computacionalmente caros enquanto permanece rápido o suficiente para uso legítimo.
Componentes-Chave no Dicionário de Criptografia
| Entrada | Comprimento | Finalidade |
|---|---|---|
/O | 48 bytes | Validação de senha do proprietário (hash + salt de validação) |
/U | 48 bytes | Validação de senha do usuário (hash + salt de validação) |
/OE | 32 bytes | Chave de criptografia do arquivo criptografada com a senha do proprietário |
/UE | 32 bytes | Chave de criptografia do arquivo criptografada com a senha do usuário |
/Perms | 16 bytes | Flags de permissão criptografadas com AES-256 |
Normalização de Senhas com SASLprep
Antes de qualquer operação criptográfica, as senhas são normalizadas usando SASLprep (RFC 4013), que é um perfil do stringprep (RFC 3454). Isso garante manipulação consistente de senhas independentemente da forma de normalização Unicode usada pelo sistema operacional ou método de entrada.
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.O Que o SASLprep Faz
- Mapear -- Caracteres comumente mapeados para nada (ex: soft hyphen
U+00AD) são removidos. - Normalizar -- A string é convertida para Unicode NFC (Decomposição Canônica seguida de Composição Canônica).
- Proibir -- Caracteres das Tabelas C.1.2 a C.9 da RFC 3454 são rejeitados (caracteres de controle, uso privado, surrogates, non-characters, etc.).
- Verificação bidirecional -- Strings com caracteres tanto da esquerda para a direita quanto da direita para a esquerda são validadas conforme a cláusula 6 da RFC 3454.
Isso significa que um usuário digitando U+00FC (LATIN SMALL LETTER U WITH DIAERESIS) e outro digitando U+0075 U+0308 (LATIN SMALL LETTER U + COMBINING DIAERESIS) produzirão a mesma chave de criptografia.
Codificação de Permissões
As permissões são armazenadas na entrada /Perms como um bloco criptografado AES-256-ECB de 16 bytes. O layout em texto claro é:
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 paddingAs flags de permissão seguem o mesmo layout de bits definido na Tabela 22 do ISO 32000-2.
Manipulação de Streams Criptografados
Todo stream de conteúdo e string no PDF é individualmente criptografado:
- Um Initialization Vector (IV) único de 16 bytes é gerado por stream/string usando
random_bytes(16). - O IV é preposto ao texto cifrado.
- O padding PKCS#7 é aplicado antes da criptografia.
- O filtro
/Cryptcom/AESV3é definido em todos os parâmetros de decodificação de stream.
// 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
O TCPDF-Next Pro sempre criptografa dados de stream. A flag /EncryptMetadata é true por padrão. Se definida como false, o stream de metadados XMP permanece não criptografado (útil para indexação de busca), mas todos os outros streams ainda são criptografados.
Manipulação de Parâmetros Sensíveis
Todos os métodos que aceitam senhas são anotados com o atributo #[\SensitiveParameter] do PHP 8.2. Isso impede que as senhas apareçam em stack traces, saída de debug e logs de erro:
public function setOwnerPassword(
#[\SensitiveParameter] string $password,
): self {
$this->ownerPassword = SaslPrep::prepare($password);
return $this;
}Se uma exceção ocorrer, o stack trace mostrará Object(SensitiveParameterValue) em vez da string real da senha.
Exemplo Completo
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');