Article technique

Chiffrement PDF AES-256 dans Delphi : configuration HotPDF et pièges

Le ticket de support disait : « Les relevés s'ouvrent correctement sur nos postes, mais le système de gestion documentaire du client marque chaque fichier comme illisible. » Les PDF étaient chiffrés en AES-256, exactement comme le contrat l'exigeait. La cause se cachait dans un seul booléen : les documents avaient été écrits avec la révision de chiffrement 6, la variante PDF 2.0 d'ISO 32000-2, alors que la chaîne d'archivage du client ne comprenait que la révision 5. Même algorithme, même longueur de clé, mêmes mots de passe : une poignée de main de dérivation de clé différente, et un échec net qui n'était jamais apparu sur aucune machine de développement équipée d'un Acrobat récent.

Le chiffrement fait partie des rares fonctions PDF où une mauvaise configuration ne produit aucun symptôme visible localement et un échec total à distance. HotPDF, composant PDF VCL natif pour Delphi et C++Builder, expose tout le modèle de protection ISO 32000 par une poignée de propriétés ; cet article relie chaque propriété à la décision qu'elle contrôle réellement.

Ce que promettent vraiment les deux mots de passe

Le chiffrement PDF définit deux identifiants aux rôles différents, et les confondre est l'erreur de conception la plus fréquente dans le code de sortie protégée. Le mot de passe utilisateur gouverne le déchiffrement : sans lui, ou sans le mot de passe propriétaire, un lecteur conforme ne peut pas reconstruire la clé de fichier et le contenu reste cryptographiquement illisible. Le mot de passe propriétaire gouverne les paramètres de permissions : un lecteur qui le reçoit accorde l'accès complet, quels que soient les flags de restriction.

Les permissions elles-mêmes, impression, extraction de contenu, remplissage de formulaire, sont une promesse plus faible. Ce sont des flags qu'un lecteur lit et accepte d'honorer (ISO 32000-2 §7.6.4). Le chiffrement protège les octets ; les flags de permission ne font qu'instruire les logiciels conformes. Un utilisateur qui ouvre légitimement un document avec le mot de passe utilisateur possède le contenu déchiffré en mémoire ; « no copy » et « no print » sont donc des signaux de stratégie pour lecteurs bien élevés, pas des garanties cryptographiques. Construisez le modèle de menace autour de cette séparation : la confidentialité vient du mot de passe utilisateur, tandis que les flags de permission influencent le comportement des lecteurs courants, rien de plus.

Ordre de configuration : tout avant BeginDoc

HotPDF établit le dictionnaire de chiffrement et la clé de fichier quand BeginDoc s'exécute. Chaque propriété de protection doit donc être affectée d'abord, surtout CryptKeyLength, qui choisit le schéma par les valeurs THPDFKeyType k40, k128, aes128 et aes256. L'affecter après BeginDoc ne déclenche pas d'exception ; le document conserve simplement les paramètres avec lesquels il a démarré, exactement le type de divergence silencieuse qui réapparaît des mois plus tard sous forme de constat de conformité.

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;

Les mots de passe sont en UTF-8 et limités à 127 octets, conformément à la limite ISO 32000-2 pour les schémas AES-256. Si votre politique génère des secrets plus longs, tronquez-les explicitement de votre côté au lieu de laisser la bibliothèque et un futur lecteur diverger sur l'endroit où se situe la coupure.

Révision 5 ou révision 6 : un booléen, deux écosystèmes

UseAES256R6 choisit entre les deux poignées de main AES-256. Avec False, HotPDF écrit la révision 5, schéma AES-256 introduit comme extension de PDF 1.7 et pris en charge par environ quinze ans de versions de lecteurs. Avec True, il écrit la révision 6, l'algorithme renforcé de dérivation de clé normalisé dans ISO 32000-2 pour PDF 2.0, variante qui ferme la faiblesse connue de l'étape de vérification de mot de passe de la révision 5.

L'arbitrage d'ingénierie oppose compatibilité et normalisation. La révision 6 exige des lecteurs conçus pour PDF 1.7 Extension Level 3 ou PDF 2.0 ; les anciens systèmes d'archivage, moteurs embarqués et outils métier non maintenus échouent complètement à ouvrir le fichier, comme dans le ticket cité au début. Sauf si une politique de sécurité nomme explicitement ISO 32000-2 révision 6, livrez en révision 5 et consignez la décision : c'est le défaut le plus sûr, et vous pourrez le réexaminer quand le consommateur le plus lent aura été mis à niveau.

Le même raisonnement s'applique un niveau plus bas. THPDFKeyType propose encore k40, k128 et aes128 pour compatibilité avec les anciennes chaînes d'outils, mais les trois relèvent de la maintenance de systèmes hérités, pas de nouveaux designs : RC4 40 bits se casse sur du matériel courant, et les schémas 128 bits précèdent les révisions AES-256 attendues par les revues de sécurité actuelles. Pour tout document produit en 2026, l'espace de décision réaliste est AES-256 révision 5 contre révision 6 ; les anciens types de clé existent pour reproduire des archives historiques, pas pour en créer de nouvelles.

Flags de permission sans mot de passe d'ouverture

Une exigence fréquente est l'inverse du secret : tout le monde peut lire le document, mais l'impression ou l'extraction doit être restreinte. La configuration est un mot de passe utilisateur vide plus un mot de passe propriétaire non vide, mode open-password, avec les opérations autorisées listées dans 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;

L'ensemble THPDFProtectOptions couvre les bits de permission ISO : prPrint, prPrint12bit pour l'impression haute résolution, prInformationCopy pour la copie et l'extraction générales, prExtractContent pour l'extraction par technologies d'assistance, prModifyStructure, prEditAnnotations, prFillAnnotations et prAssemble. Deux méritent une recommandation précise. Gardez prExtractContent activé dans presque tous les profils : c'est le bit qui permet aux lecteurs d'écran et autres technologies d'assistance d'atteindre le contenu, et le révoquer transforme une décision de droits en défaut d'accessibilité. Notez aussi que prPrint sans prPrint12bit produit une impression dégradée dans certains lecteurs, que les utilisateurs finaux signalent comme un bug de rendu plutôt que comme une permission.

La vérification est rapide et mérite d'être automatisée dans les contrôles de publication : ouvrez un échantillon de la sortie de chaque profil dans Acrobat et lisez l'onglet Sécurité des Propriétés du document, qui nomme l'algorithme (« AES 256-bit ») et détaille les opérations autorisées une par une. Répétez ensuite l'ouverture dans le plus ancien lecteur réellement utilisé par vos clients. Les cinq minutes de ce second contrôle correspondent exactement au trou qui a laissé le ticket de révision 6 atteindre la production.

Retirer la protection de fichiers existants

Le déchiffrement est le même modèle de propriétés en sens inverse : chargez le document avec son identifiant, désactivez la protection, enregistrez le résultat.

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;

Cette route parse le document complet, ce qui convient aux fichiers ordinaires. Pour des entrées de plusieurs centaines de mégaoctets, il existe un chemin moins coûteux : DecryptFile déchiffre pendant une copie au niveau fichier, en empruntant un chemin de réécriture AES-256 direct qui évite de construire l'arbre d'objets lorsque l'entrée le permet. Il appartient à la Direct File API décrite dans l'article compagnon sur le traitement de grands PDF depuis Delphi.

Contraintes qui interagissent avec le chiffrement

Les profils d'archivage sont en conflit direct : ISO 19005 interdit le chiffrement dans les fichiers PDF/A ; un workflow qui chiffre un document tout en revendiquant la conformité PDF/A est donc invalide par construction. Quand les deux exigences existent, livrez deux artefacts, une copie de distribution chiffrée et une copie d'archivage non chiffrée, plutôt que d'essayer de les satisfaire dans un seul fichier.

Il n'existe pas non plus de voie de récupération. Le chiffrement PDF n'a pas de mécanisme d'entiercement : un mot de passe utilisateur perdu sur un fichier R5 ou R6 signifie force brute ou rien. Traitez les secrets propriétaire et utilisateur comme des identifiants de production, générés, stockés en coffre, renouvelés, jamais comme des constantes dans une unité, où ils finissent dans le contrôle de version et dans chaque copie de travail développeur.

FAQ : protéger des PDF depuis Delphi

La désactivation de prInformationCopy empêche-t-elle les gens de copier du texte ?

Elle empêche les lecteurs conformes de proposer les commandes de copie. Toute personne capable d'ouvrir le document possède déjà le texte clair en mémoire ; traitez donc le flag comme un guidage de workflow, pas comme une prévention des fuites de données.

Un nouveau projet doit-il activer UseAES256R6 ?

Uniquement lorsque chaque consommateur est vérifié comme capable de gérer le chiffrement PDF 2.0. La révision 5 fournit le même chiffrement de contenu AES-256 avec une couverture lecteurs beaucoup plus large, ce qui explique son usage par défaut.

Puis-je changer les permissions d'un PDF que je n'ai pas créé ?

Oui : chargez-le avec son mot de passe par LoadFromFile, ajustez ProtectOptions ou les mots de passe, puis écrivez le résultat avec SaveLoadedDocument, exactement comme dans l'exemple de déchiffrement ci-dessus.

Les propriétés de protection présentées ici font partie du HotPDF Component standard pour Delphi et C++Builder ; la page produit contient la référence complète du chiffrement, y compris l'énumération complète des permissions.