Skip to content

Criptografia Avançada

Pro — Commercial License Required
Os detalhes internos de criptografia avançada requerem o pacote Pro.

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.

php
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:

AlgoritmoRazão da Exclusão
RC4-40Quebrado desde 1995; atacado trivialmente
RC4-128Vieses no keystream; proibido pelo PDF 2.0
AES-128Substituí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 key

Este 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

EntradaComprimentoFinalidade
/O48 bytesValidação de senha do proprietário (hash + salt de validação)
/U48 bytesValidação de senha do usuário (hash + salt de validação)
/OE32 bytesChave de criptografia do arquivo criptografada com a senha do proprietário
/UE32 bytesChave de criptografia do arquivo criptografada com a senha do usuário
/Perms16 bytesFlags 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.

php
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

  1. Mapear -- Caracteres comumente mapeados para nada (ex: soft hyphen U+00AD) são removidos.
  2. Normalizar -- A string é convertida para Unicode NFC (Decomposição Canônica seguida de Composição Canônica).
  3. 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.).
  4. 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 padding

As 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:

  1. Um Initialization Vector (IV) único de 16 bytes é gerado por stream/string usando random_bytes(16).
  2. O IV é preposto ao texto cifrado.
  3. O padding PKCS#7 é aplicado antes da criptografia.
  4. O filtro /Crypt com /AESV3 é definido em todos os parâmetros de decodificação de stream.
php
// 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:

php
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

php
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');

Distribuído sob a licença LGPL-3.0-or-later.