Skip to content

Crittografia Avanzata

Pro — Commercial License Required
I dettagli interni della crittografia avanzata richiedono il pacchetto Pro.

Questa pagina documenta l'implementazione interna della crittografia in TCPDF-Next Pro. Copre l'handler AES-256 AESV3, l'algoritmo di derivazione chiave, normalizzazione password e gestione parametri sicuri. Se cerchi l'utilizzo base della crittografia, vedi l'esempio Crittografia AES-256.

AES-256 con Handler AESV3

TCPDF-Next Pro implementa lo ISO 32000-2 (PDF 2.0) Standard Security Handler revisione 6, che impone AES-256-CBC per tutta la crittografia di stream e stringhe. L'handler è identificato da /V 5 e /R 6 nel dizionario crittografia.

php
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;

$encryptor = new Aes256Encryptor(
    ownerPassword: 'Str0ng!OwnerP@ss',
    userPassword:  'reader2026',
);

Perché Non RC4 o AES-128

TCPDF-Next Pro esclude deliberatamente RC4 (40-bit e 128-bit) e AES-128:

AlgoritmoRagione Esclusione
RC4-40Compromesso dal 1995; attaccabile banalmente
RC4-128Bias nel keystream; proibito da PDF 2.0
AES-128Superato da AES-256 in revisione 6; non forward-compatible

PDF 2.0 (ISO 32000-2:2020) richiede AESV3 per nuovi documenti. Supportare algoritmi più deboli comprometterebbe la postura di sicurezza e violerebbe la specifica.

Algorithm 2.B: Derivazione Chiave

La chiave di crittografia file è derivata dalla password usando Algorithm 2.B (ISO 32000-2, clausola 7.6.4.3.4). È un processo iterativo che concatena 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) ripetuto 64 volte
        E  = AES-128-CBC(key = K[0..15], iv = K[16..31], data = K1)

        mod3 = (somma di tutti i byte 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]   // chiave crittografia file 32-byte

Questo hashing iterativo rende gli attacchi brute-force computazionalmente costosi rimanendo abbastanza veloce per uso legittimo.

Componenti Chiave nel Dizionario Crittografia

EntryLunghezzaScopo
/O48 byteValidazione password owner (hash + validation salt)
/U48 byteValidazione password user (hash + validation salt)
/OE32 byteChiave crittografia file criptata owner
/UE32 byteChiave crittografia file criptata user
/Perms16 byteFlag permessi criptati AES-256

Normalizzazione Password SASLprep

Prima di ogni operazione crittografica, le password sono normalizzate usando SASLprep (RFC 4013), che è un profilo di stringprep (RFC 3454). Questo garantisce gestione password consistente indipendentemente dalla forma normalizzazione Unicode usata dal sistema operativo o metodo input.

php
use Yeeefang\TcpdfNext\Pro\Security\SaslPrep;

$normalized = SaslPrep::prepare('P\u{00E4}ssw\u{00F6}rd');
// Normalizza forme composte/decomposte, mappa certi caratteri,
// e rifiuta codepoint proibiti.

Cosa Fa SASLprep

  1. Map -- Caratteri comunemente mappati-a-niente (es. soft hyphen U+00AD) sono rimossi.
  2. Normalize -- La stringa è convertita a Unicode NFC (Canonical Decomposition seguita da Canonical Composition).
  3. Prohibit -- Caratteri da RFC 3454 Table C.1.2 fino C.9 sono rifiutati (caratteri controllo, uso privato, surrogati, non-caratteri, ecc.).
  4. Bidirectional check -- Stringhe con caratteri sia left-to-right che right-to-left sono validate per RFC 3454 clausola 6.

Questo significa che un utente che digita U+00FC (LATIN SMALL LETTER U WITH DIAERESIS) e un altro che digita U+0075 U+0308 (LATIN SMALL LETTER U + COMBINING DIAERESIS) produrranno la stessa chiave crittografia.

Codifica Permessi

I permessi sono memorizzati nell'entry /Perms come blocco criptato AES-256-ECB da 16-byte. Il layout plaintext è:

Byte 0-3:   Flag permessi (little-endian int32)
Byte 4-7:   0xFFFFFFFF
Byte 8:     'T' se EncryptMetadata è true, 'F' altrimenti
Byte 9-11:  'adb'
Byte 12-15: Padding casuale

I flag permessi seguono lo stesso layout bit definito in ISO 32000-2 Table 22.

Gestione Stream Criptati

Ogni content stream e stringa nel PDF è criptato individualmente:

  1. Un Initialization Vector (IV) unico di 16-byte è generato per stream/stringa usando random_bytes(16).
  2. L'IV è anteposto al ciphertext.
  3. Il padding PKCS#7 è applicato prima della crittografia.
  4. Il filtro /Crypt con /AESV3 è impostato su tutti i parametri decode stream.
php
// Interno -- gestito automaticamente dal 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 cripta sempre i dati stream. Il flag /EncryptMetadata predefinito è true. Se impostato a false, lo stream metadati XMP rimane non criptato (utile per indicizzazione ricerca), ma tutti gli altri stream sono comunque criptati.

Gestione Parametri Sensibili

Tutti i metodi che accettano password sono annotati con l'attributo #[\SensitiveParameter] di PHP 8.2. Questo previene che le password appaiano in stack trace, output debug e log errori:

php
public function setOwnerPassword(
    #[\SensitiveParameter] string $password,
): self {
    $this->ownerPassword = SaslPrep::prepare($password);
    return $this;
}

Se si verifica un'eccezione, lo stack trace mostrerà Object(SensitiveParameterValue) invece della stringa password effettiva.

Esempio Completo

php
use Yeeefang\TcpdfNext\Core\Document;
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;
use Yeeefang\TcpdfNext\Pro\Security\Permissions;

$pdf = Document::create()
    ->setTitle('Rapporto Confidenziale')
    ->addPage()
    ->setFont('Helvetica', size: 12)
    ->multiCell(0, 6, 'Questo documento è protetto con crittografia AES-256.');

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

Rilasciato sotto licenza LGPL-3.0-or-later.