Articolo tecnico

Cifratura PDF AES-256 in Delphi: HotPDF e trabocchetti

Un flag di permesso PDF non è una serratura. È una richiesta che il file rivolge a qualunque cosa lo apra, e un visualizzatore è libero di ignorarla. Questo solo fatto decide come dovreste ragionare su ogni altra scelta di questa pagina. La riservatezza vera viene da un unico posto: la cifratura AES-256 con chiave derivata da una password che il lettore non possiede. Tutto il resto, le caselle «vietata la stampa» e «vietata la copia», è politica che il software conforme accetta di rispettare e quello ostile no. Confondete i due strati e consegnate qualcosa che sembra sicuro in una dimostrazione e perde dati sul campo

HotPDF è un componente PDF VCL nativo per Delphi e C++Builder, ed espone il modello di protezione ISO 32000 attraverso un piccolo insieme di proprietà. Le proprietà sono facili da impostare. La parte difficile è sapere quale vi compra una protezione crittografica e quale vi compra un garbato suggerimento, e azzeccare l'ordine di assegnazione affinché la cifratura che avete chiesto sia davvero la cifratura che ottenete

Che cosa promettono davvero le due password

La cifratura PDF definisce due credenziali con compiti diversi, e confonderle è l'errore di progetto più comune nel codice che produce documenti protetti. La password utente controlla la decifratura. Senza di essa, o senza la password proprietario, un lettore conforme non può ricostruire la chiave del file e il contenuto resta illeggibile dal punto di vista crittografico. La password proprietario controlla invece le impostazioni dei permessi: un lettore che riceve la password proprietario ottiene accesso pieno, qualunque cosa dicano i flag di restrizione

I bit di permesso poggiano su un terreno più fragile. Stampa, estrazione del contenuto, compilazione dei moduli: ciascuno è un flag che un visualizzatore legge e sceglie di rispettare (ISO 32000-2 §7.6.4). La cifratura protegge i byte. I flag di permesso danno soltanto istruzioni al software conforme, e gliele danno a cose fatte. Chiunque apra il documento con la password utente tiene già in memoria il contenuto decifrato, quindi «niente copia» e «niente stampa» significano qualcosa per un visualizzatore beneducato e nulla per uno determinato. Costruite il modello di minaccia attorno a questa linea. La riservatezza vive nella password utente. I permessi modellano ciò che i visualizzatori più diffusi offrono, e questo è tutto ciò che fanno

Diagramma HotPDF delle credenziali di un PDF cifrato in cui la password utente deriva la chiave del file e sblocca la decifratura, la password proprietario concede accesso pieno scavalcando i flag di permesso, e una fascia di avviso ricorda che i bit di ProtectOptions sono richieste rispettate solo dal software conforme
La password utente porta la riservatezza mentre quella proprietario si limita a togliere le restrizioni; i flag di permesso guidano i visualizzatori conformi e non vincolano nessun avversario

Ordine di configurazione: tutto prima di BeginDoc

HotPDF costruisce il dizionario di cifratura e deriva la chiave del file nel momento in cui gira BeginDoc. Ciò che le proprietà di protezione contengono in quell'istante è ciò che il documento riceve, e cambiarle dopo non cambia nulla. La proprietà che qui conta di più è CryptKeyLength, che sceglie lo schema fra i valori THPDFKeyType k40, k128, aes128 e aes256. Assegnatela dopo BeginDoc e non otterrete alcuna eccezione, alcun avviso, soltanto un file che ha silenziosamente tenuto ciò con cui era partito. Quel tipo di divergenza silenziosa è la peggiore: supera ogni test locale e riaffiora mesi più tardi come rilievo di conformità sulla scrivania di un cliente

Due flussi Delphi affiancati che mostrano proprietà di protezione come CryptKeyLength aes256 assegnate prima di BeginDoc con esito un file AES-256, mentre la stessa assegnazione dopo BeginDoc lascia intatto lo schema originale senza eccezione né avviso
BeginDoc congela il dizionario di cifratura, così la corsia con ordine corretto produce la revisione AES-256 richiesta mentre quella con ordine sbagliato consegna in silenzio lo schema predefinito
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'statement.pdf';
    Pdf.ActivateProtection := True;
    Pdf.CryptKeyLength := aes256;        // va impostata prima di BeginDoc
    Pdf.UserPassword := 'open-secret';
    Pdf.OwnerPassword := 'admin-secret';
    Pdf.UseAES256R6 := False;            // R=5: massima compatibilità
    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 tetto di 127 byte, che è il limite ISO 32000-2 per gli schemi AES-256. Se la vostra politica sulle password vi consegna segreti più lunghi, fate voi il troncamento, dalla vostra parte, dove controllate esattamente dove cade il taglio. Lasciatelo al caso e la libreria e qualche visualizzatore futuro potranno non essere d'accordo sul punto di taglio, il che produce un file che a voi si apre e altrove rifiuta la stessa password

Revisione 5 o revisione 6: un booleano, due ecosistemi

UseAES256R6 sceglie fra i due handshake AES-256, e la scelta pesa più di quanto il tipo booleano lasci intendere. Lasciatelo a False e HotPDF scrive la revisione 5, lo schema AES-256 arrivato come estensione del PDF 1.7 e apribile da qualcosa come quindici anni di visualizzatori. Mettetelo a True e ottenete la revisione 6, la derivazione di chiave irrobustita standardizzata in ISO 32000-2 per il PDF 2.0, che chiude una debolezza nota nel modo in cui la revisione 5 verifica la password

La revisione 6 è quindi la storia migliore dal lato crittografico. È anche quella che rompe le cose. Un file in revisione 6 richiede un visualizzatore costruito per il PDF 1.7 Extension Level 3 o per il PDF 2.0, e molto software installato non è né l'uno né l'altro: archivi di gestione documentale, renderer incorporati in altri prodotti, strumenti gestionali che nessuno tocca da anni. Quelli rifiuteranno il file di netto, e lo faranno sulla macchina del cliente, mai sulla vostra. Il valore predefinito pratico è perciò la revisione 5. Puntate alla revisione 6 solo quando una politica di sicurezza nomina ISO 32000-2 per revisione, e solo quando avete davvero verificato che ogni destinatario riesca a leggerla. In un modo o nell'altro, mettete per iscritto quale avete scelto e perché, perché la prossima persona che metterà mano a questo codice se lo chiederà

I tipi di chiave più vecchi meritano una frase perché sappiate di doverli saltare. THPDFKeyType elenca ancora k40, k128 e aes128, ma esistono per riprodurre archivi storici, non per proteggerne di nuovi. L'RC4 a 40 bit cade davanti a hardware da supermercato, e gli schemi a 128 bit precedono le revisioni AES-256 che qualunque revisione di sicurezza attuale si aspetta. Per un documento che create nel 2026 la domanda vera è soltanto revisione 5 contro revisione 6; se vi ritrovate a raggiungere i tipi legacy in un progetto nuovo, qualcosa a monte è andato storto

Flag di permesso senza password di apertura

Spesso il requisito è l'opposto della segretezza. Chiunque deve poter leggere il documento, ma la stampa o l'estrazione vanno limitate. Lo esprimete con una password utente vuota e una password proprietario non vuota, ciò che il PDF chiama modalità a password di apertura, ed elencate le operazioni che volete consentire in ProtectOptions

Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.UserPassword := '';                      // chiunque può aprire il file
Pdf.OwnerPassword := 'rotate-me-quarterly';  // protegge il set di permessi
Pdf.ProtectOptions := [prPrint, prPrint12bit, prExtractContent];
Pdf.BeginDoc;
// ... contenuto della pagina ...
Pdf.EndDoc;

L'insieme THPDFProtectOptions corrisponde ai bit di permesso ISO: prPrint e prPrint12bit per la stampa ad alta risoluzione, prInformationCopy per copia ed estrazione generiche, prExtractContent per l'estrazione da parte delle tecnologie assistive, più prModifyStructure, prEditAnnotations, prFillAnnotations e prAssemble. Due di essi meritano un avvertimento. Lasciate prExtractContent attivo in quasi ogni profilo che costruite. È il bit di cui uno screen reader ha bisogno per raggiungere il testo, e azzerarlo converte in silenzio una decisione sui diritti in un difetto di accessibilità che una persona con disabilità incontra e voi non vedete mai. L'altra trappola è prPrint da solo, senza prPrint12bit: diversi visualizzatori rispondono degradando la qualità di stampa, e i vostri utenti lo segnaleranno come un difetto di rendering anziché come l'impostazione di permesso che in realtà è

La verifica richiede cinque minuti e appartiene alla vostra lista di controllo di rilascio. Aprite un campione di ciascun profilo in Acrobat, aprite le proprietà del documento e leggete la scheda Sicurezza, che dichiara l'algoritmo («AES 256-bit») ed elenca una per una le operazioni consentite. Poi aprite lo stesso file nel visualizzatore più vecchio che i vostri clienti usano davvero, non nel più recente presente sulla vostra macchina. Quella seconda apertura è l'assicurazione a basso costo contro un file in revisione 6 che attraversa indenne lo sviluppo e muore da un cliente che non ha mai aggiornato

Rimuovere la protezione da file esistenti

La decifratura percorre lo stesso modello di proprietà al contrario. Caricate il documento con una credenziale valida, disattivate la protezione e salvate il risultato senza di essa

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;   // elimina la cifratura al salvataggio
      Pdf.SaveLoadedDocument('plain.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Quella via analizza l'intero documento in memoria, il che va bene per file ordinari ed è sprecone per quelli enormi. Quando l'ingresso arriva a centinaia di megabyte, DecryptFile è l'opzione più economica: decifra durante una copia a livello di file, prendendo un percorso diretto di riscrittura AES-256 che evita di costruire l'albero completo degli oggetti ogni volta che l'ingresso lo consente. Fa parte della Direct File API trattata nell'articolo di accompagnamento sull'elaborazione di PDF di grandi dimensioni da Delphi

Vincoli che interagiscono con la cifratura

Due limiti vanno conosciuti prima di progettare attorno alla cifratura, non dopo. Il primo è la conformità archivistica. La ISO 19005 vieta la cifratura nel PDF/A, quindi qualsiasi flusso che cifra un documento e insieme dichiara la conformità PDF/A è contraddittorio per costruzione; HotPDF non vi lascerà avere entrambe le cose in un solo file. Quando vi servono davvero entrambe, la risposta sono due artefatti: una copia cifrata per la distribuzione e una copia separata non cifrata per l'archivio

Il secondo limite è più netto. La cifratura PDF non prevede deposito fiduciario né recupero. Perdete la password utente di un file R5 o R6 e le vostre opzioni sono la forza bruta o la rinuncia. Trattate quindi i segreti proprietario e utente come qualsiasi credenziale di produzione. Generateli, conservateli in un vault, ruotateli secondo un calendario. L'unica cosa da non fare mai è scriverli come costanti dentro una unit, dove finiscono dritti nel controllo di versione e restano per sempre nella copia di lavoro di ogni sviluppatore

Un ultimo riflesso da costruire. Cambiare la protezione di un file che non avete creato usa lo stesso meccanismo della decifratura, non una funzione a parte: caricatelo con la sua password tramite LoadFromFile, modificate ProtectOptions o le password sul posto e riscrivetelo con SaveLoadedDocument. Se riuscite a decifrare un file, riuscite anche a ridefinirne i permessi, e il codice somiglia quasi in tutto all'esempio qui sopra

Le proprietà di protezione mostrate qui fanno parte dello standard HotPDF Delphi Component per Delphi e C++Builder; la pagina di prodotto riporta il riferimento completo sulla cifratura, compresa l'enumerazione integrale dei permessi