Articolo tecnico

Crittografia PDF AES-256 in Delphi: configurazione HotPDF e insidie

Il ticket di supporto diceva: "Gli estratti si aprono senza problemi sui nostri desktop, ma il sistema di records management del cliente segnala ogni file come illeggibile". I PDF erano crittografati con AES-256, esattamente come richiedeva il contratto. La causa era in un singolo booleano: i documenti erano stati scritti con la revisione di crittografia 6, la variante PDF 2.0 di ISO 32000-2, mentre la toolchain di archiviazione del cliente capiva solo la revisione 5. Stesso algoritmo, stessa lunghezza chiave, stesse password — ma un handshake di derivazione chiave diverso e un errore bloccante che non appariva su nessuna macchina di sviluppo con Acrobat aggiornato.

La crittografia è una delle poche funzionalità PDF in cui una configurazione sbagliata non produce sintomi visibili in locale e fallisce completamente a distanza. HotPDF, componente PDF VCL nativo per Delphi e C++Builder, espone l'intero modello di protezione ISO 32000 tramite poche proprietà; questo articolo collega ogni proprietà alla decisione che controlla davvero.

Che cosa promettono davvero le due password

La crittografia PDF definisce due credenziali con compiti diversi, e confonderle è l'errore di design più comune nel codice che produce output protetto. La user password controlla la decifratura: senza di essa, o senza la owner password, un reader conforme non può ricostruire la chiave del file e il contenuto è crittograficamente illeggibile. La owner password controlla le impostazioni di permesso: un reader che la riceve concede accesso completo indipendentemente dai flag di restrizione.

I permessi stessi — stampa, estrazione del contenuto, compilazione dei form — sono una promessa più debole. Sono flag che un viewer legge e accetta di rispettare (ISO 32000-2 §7.6.4). La crittografia protegge i byte; i flag di permesso istruiscono semplicemente il software conforme. Un utente che apre legittimamente un documento con la user password ha il contenuto decifrato in memoria, quindi "no copy" e "no print" sono segnali di policy per viewer ben educati, non garanzie crittografiche. Costruisci il threat model attorno a questa separazione: la riservatezza viene dalla user password, mentre i flag di permesso modellano il comportamento nei viewer mainstream e nulla di più.

Ordine di configurazione: tutto prima di BeginDoc

HotPDF stabilisce il dizionario di crittografia e la chiave del file quando viene eseguito BeginDoc. Ogni proprietà di protezione deve quindi essere assegnata prima — soprattutto CryptKeyLength, che seleziona lo schema tramite i valori THPDFKeyType k40, k128, aes128 e aes256. Assegnarla dopo BeginDoc non solleva eccezioni; il documento mantiene semplicemente i parametri con cui è partito, proprio il tipo di divergenza silenziosa che emerge mesi dopo come finding di compliance.

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'statement.pdf';
    Pdf.ActivateProtection := True;
    Pdf.CryptKeyLength := aes256;        // must be set before BeginDoc
    Pdf.UserPassword := 'open-secret';
    Pdf.OwnerPassword := 'admin-secret';
    Pdf.UseAES256R6 := False;            // R=5: widest viewer support
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 720, 0, 'Account statement, June 2026');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Le password sono UTF-8 e hanno un limite di 127 byte, allineato al limite ISO 32000-2 per gli schemi AES-256. Se la tua policy genera segreti più lunghi, tronca deliberatamente dalla tua parte invece di lasciare che la libreria e un futuro viewer non concordino sul punto di taglio.

Revisione 5 o revisione 6: un booleano, due ecosistemi

UseAES256R6 sceglie tra i due handshake AES-256. Con False, HotPDF scrive la revisione 5, lo schema AES-256 introdotto come estensione di PDF 1.7 e supportato da circa quindici anni di viewer. Con True, scrive la revisione 6, l'algoritmo di derivazione chiave rinforzato standardizzato in ISO 32000-2 per PDF 2.0 — la variante che chiude la debolezza nota nel passaggio di verifica password della revisione 5.

Il compromesso ingegneristico è compatibilità contro standardizzazione. La revisione 6 richiede viewer costruiti per PDF 1.7 Extension Level 3 o PDF 2.0; sistemi di archiviazione più vecchi, renderer incorporati e strumenti line-of-business non mantenuti non aprono affatto il file, esattamente come nel ticket all'inizio dell'articolo. A meno che una policy di sicurezza nomini esplicitamente ISO 32000-2 revisione 6, distribuisci la revisione 5 e documenta la decisione: è il default più prudente, e potrai rivederlo quando il consumer più lento ad aggiornarsi sarà migrato.

Lo stesso ragionamento vale un livello sotto. THPDFKeyType offre ancora k40, k128 e aes128 per compatibilità con toolchain datate, ma tutti e tre appartengono alla manutenzione di sistemi legacy, non a nuovi progetti: RC4 a 40 bit è attaccabile con hardware comune, e gli schemi a 128 bit precedono le revisioni AES-256 che le revisioni di sicurezza attuali si aspettano di vedere. Per qualsiasi documento prodotto nel 2026, lo spazio decisionale realistico è AES-256 revisione 5 contro revisione 6: i tipi di chiave più vecchi esistono per riprodurre archivi storici, non per scriverne di nuovi.

Flag di permesso senza password di apertura

Un requisito frequente è l'inverso della segretezza: chiunque può leggere il documento, ma stampa o estrazione devono essere limitate. La configurazione è una user password vuota più una owner password non vuota — modalità open-password — con le operazioni consentite elencate in ProtectOptions.

Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.UserPassword := '';                      // anyone can open the file
Pdf.OwnerPassword := 'rotate-me-quarterly';  // guards the permission set
Pdf.ProtectOptions := [prPrint, prPrint12bit, prExtractContent];
Pdf.BeginDoc;
// ... page content ...
Pdf.EndDoc;

Il set THPDFProtectOptions copre i bit di permesso ISO: prPrint, prPrint12bit per la stampa ad alta risoluzione, prInformationCopy per copia ed estrazione generali, prExtractContent per l'estrazione da parte di tecnologie assistive, prModifyStructure, prEditAnnotations, prFillAnnotations e prAssemble. Due meritano un consiglio specifico. Tieni prExtractContent abilitato in quasi tutti i profili: è il bit che permette a screen reader e altre tecnologie assistive di raggiungere il contenuto, e revocarlo trasforma una decisione sui diritti in un difetto di accessibilità. Nota anche che prPrint senza prPrint12bit produce stampa degradata in alcuni viewer, che gli utenti finali segnalano come bug di rendering invece che come permesso.

La verifica è rapida e vale la pena automatizzarla nei controlli di release: apri un campione dell'output di ogni profilo in Acrobat e leggi la scheda Security delle Document Properties, che nomina l'algoritmo ("AES 256-bit") ed elenca una per una le operazioni consentite. Poi ripeti l'apertura nel viewer più vecchio che i tuoi clienti usano davvero. I cinque minuti richiesti da quel secondo controllo sono esattamente il divario che ha permesso al ticket sulla revisione 6 di arrivare in produzione.

Rimuovere la protezione da file esistenti

La decifratura è lo stesso modello di proprietà al contrario: carichi il documento con la sua credenziale, disattivi la protezione e salvi il risultato.

var
  Pdf: THotPDF;
  PageCount: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('encrypted.pdf', 'open-secret');
    if PageCount > 0 then
    begin
      Pdf.ActivateProtection := False;   // drop encryption on save
      Pdf.SaveLoadedDocument('plain.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Questo percorso analizza l'intero documento, cosa accettabile per file ordinari. Per input da centinaia di megabyte esiste una strada più economica: DecryptFile decifra durante una copia a livello file, seguendo un percorso diretto di riscrittura AES-256 che evita di costruire l'albero degli oggetti quando l'input lo consente. Fa parte della Direct File API descritta nell'articolo collegato sull'elaborazione di PDF di grandi dimensioni da Delphi.

Vincoli che interagiscono con la crittografia

I profili di archiviazione entrano in conflitto diretto: ISO 19005 vieta la crittografia nei file PDF/A, quindi un workflow che cifra un documento e dichiara conformità PDF/A è invalido per costruzione. Quando esistono entrambi i requisiti, consegna due artefatti — una copia di distribuzione cifrata e una copia archivistica non cifrata — invece di tentare di soddisfarli in un unico file.

Non esiste neppure un percorso di recupero. La crittografia PDF non ha un meccanismo di escrow: una user password persa su un file R5 o R6 significa brute force o nulla. Tratta owner secret e user secret come credenziali di produzione — generate, custodite, ruotate — mai come costanti in una unit, dove finiscono nel controllo versione e in ogni working copy degli sviluppatori.

FAQ: proteggere PDF da Delphi

Disabilitare prInformationCopy impedisce alle persone di copiare testo?

Impedisce ai viewer conformi di offrire i comandi di copia. Chiunque possa aprire il documento ha già il plaintext in memoria, quindi considera il flag come guida di workflow, non come prevenzione della perdita dati.

Un nuovo progetto dovrebbe abilitare UseAES256R6?

Solo quando ogni consumer è stato verificato per gestire la crittografia PDF 2.0. La revisione 5 fornisce la stessa crittografia dei contenuti AES-256 con una copertura viewer molto più ampia, ed è per questo che è il default.

Posso modificare i permessi di un PDF che non ho creato?

Sì: caricalo con la sua password tramite LoadFromFile, regola ProtectOptions o le password, e scrivi il risultato con SaveLoadedDocument, esattamente come nell'esempio di decifratura sopra.

Le proprietà di protezione mostrate qui fanno parte dello standard HotPDF Component per Delphi e C++Builder; la pagina prodotto contiene il riferimento completo alla crittografia, inclusa l'intera enumerazione dei permessi.