Article technique

Signatures Numériques PDF et PAdES dans Delphi avec HotPDF

Une signature PDF relève principalement de la comptabilité d'octets, et la comptabilité d'octets est l'endroit où les choses tournent mal. La cryptographie s'exécute sur un code qui a été audité pendant deux décennies, et cette partie n'échoue presque jamais. Ce qui échoue en production est plus humble : un espace réservé trop petit pour la vraie signature, un hachage effectué sur le mauvais bout de fichier, ou un "enregistrement" après la signature qui a discrètement réécrit des octets que la signature avait déjà figés. Disposez les octets correctement et la coche verte se gère d'elle-même

HotPDF couvre la signature pour Delphi et C++Builder à trois niveaux, et vous choisissez entre eux en répondant à une question : où réside la clé privée ? Un fichier PFX sur le disque nécessite un seul appel de fonction. Une clé verrouillée dans un HSM ou un service de signature à distance nécessite la séquence de réservation-hachage-insertion, car aucune bibliothèque ne peut pénétrer dans un jeton pour en extraire la clé. Une signature qui doit satisfaire à la réglementation européenne nécessite les structures de base PAdES en plus de cela. Les sections ci-dessous suivent cette progression

Comment /ByteRange épingle les octets signés

Une signature doit résider à l'intérieur du fichier qu'elle signe, et elle ne peut pas se signer elle-même. Le PDF contourne le paradoxe en laissant un trou. Avant de signer, l'enregistreur réserve une entrée /Contents de taille fixe remplie de zéros et enregistre un tableau /ByteRange pour les deux segments de chaque côté : tout ce qui précède le trou, tout ce qui le suit. Le signataire hache ces deux segments et écrit le blob CMS résultant dans le trou en hexadécimal. Le piège réside dans le mot fixe. Vous vous engagez sur la taille de ce trou avant de savoir quelle sera la taille de la signature finie, la réservation doit donc être une surestimation confiante. Huit kilo-octets contiennent confortablement une signature CMS détachée avec une courte chaîne de certificats

HotPDF divise les deux cas en deux appels, et les confondre est une erreur courante au début. AddSignatureField dépose un champ vide et visible pour qu'une personne le signe ultérieurement dans une visionneuse. AddSignedSignatureField crée le champ et réserve le trou /Contents, ce qui est celui que vous voulez chaque fois qu'un code, plutôt qu'un humain, complétera la signature. Donnez à un signataire externe un champ vide et il n'a rien à remplir

Le chemin à un appel : signature à partir d'un PFX

Lorsque le certificat et sa clé privée résident dans un fichier PFX/PKCS#12 que votre processus peut lire, l'ensemble du pipeline se réduit à une fonction de classe :

if THotPDF.SignPDFWithPFX('invoice-unsigned.pdf', 'invoice-signed.pdf',
    'company-cert.pfx', 'pfx-password') then
  Writeln('Signed: invoice-signed.pdf')
else
  raise Exception.Create('PFX signing failed');

Lorsque cela échoue, le PDF est rarement le problème. C'est le PFX. HotPDF lit les conteneurs protégés par PBES2, ce qui signifie la dérivation de clé PBKDF2 sur AES-256-CBC. Un PFX exporté par un ancien assistant de certificat Windows, ou par OpenSSL avant la version 3.0, est généralement enveloppé dans l'ancien RC2 ou 3DES à la place, et il ne s'analysera tout simplement pas. La solution consiste à réexporter le conteneur une fois avec une protection moderne ; OpenSSL d'aujourd'hui le fait par défaut, et ce n'est pas une modification de code. Ainsi, lorsque la signature meurt instantanément sur un certificat qui "fonctionne partout ailleurs", regardez comment le PFX a été créé avant de soupçonner votre propre code

Le chemin de réservation-hachage-insertion pour les HSM et les jetons

Le chemin à un appel suppose que votre processus peut lire la clé en tant que fichier. De plus en plus, ce n'est pas possible. La clé réside dans un HSM, sur un jeton USB ou derrière l'API d'un service de signature, et il n'y a aucun moyen pour une bibliothèque d'y accéder directement. HotPDF gère cela en décomposant la signature en étapes au niveau des octets : écrivez un document d'espace réservé, demandez à la bibliothèque les plages de hachage, transmettez l'entrée de hachage à ce qui détient la clé, puis raccordez le CMS renvoyé dans le trou

var
  Doc: THotPDF;
  Fs: TFileStream;
  PdfBytes, HashInput, SigHex: AnsiString;
  R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
  // 1. Write the document with a reserved /Contents hole
  Doc := THotPDF.Create(nil);
  try
    Doc.FileName := 'placeholder.pdf';
    Doc.BeginDoc;
    Doc.CurrentPage.AddSignedSignatureField('Sig1',
      Rect(50, 100, 350, 150), 8192, 'adbe.pkcs7.detached',
      'Contract approval', 'Boston, MA', 'legal@example.com');
    Doc.EndDoc;
  finally
    Doc.Free;
  end;

  // 2. Load the saved bytes; the returned offsets are 0-based
  Fs := TFileStream.Create('placeholder.pdf', fmOpenRead);
  try
    SetLength(PdfBytes, Fs.Size);
    Fs.ReadBuffer(PdfBytes[1], Fs.Size);
  finally
    Fs.Free;
  end;
  THotPDF.PreparePDFForSigning(PdfBytes, R1Start, R1Len, R2Start, R2Len,
    CStart, CLen);

  // 3. Hash both spans and sign externally (HSM, token, service)
  HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
               Copy(PdfBytes, R2Start + 1, R2Len);
  SigHex := SignWithHsm(HashInput);  // your integration: returns CMS as hex

  // 4. Splice the signature into the reserved hole
  THotPDF.InsertSignatureHex(PdfBytes, SigHex);
  Fs := TFileStream.Create('signed.pdf', fmCreate);
  try
    Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
  finally
    Fs.Free;
  end;
end;

Deux détails dans cette séquence causent la plupart des défaillances intermittentes. Le premier est que PreparePDFForSigning travaille sur les octets d'un fichier terminé. L'espace réservé doit être écrit et enregistré dans son intégralité avant que les décalages n'aient un sens ; calculez-les par rapport à un flux en cours d'assemblage et ils ne s'aligneront pas avec les octets que vous hacherez en fin de compte. Le second est la taille de la réservation, encore une fois. Les 8192 octets que vous avez demandés doivent contenir le CMS final, et une signature portant des certificats intermédiaires, ou une signature qu'un service décore avec des attributs signés, peut le dépasser. InsertSignatureHex n'agrandira pas le trou pour faire de la place. Le symptôme révélateur est un pipeline qui signe correctement avec un certificat et échoue avec le suivant ; le remède consiste à régénérer l'espace réservé avec une réservation mesurée à partir d'une signature réelle produite par le signataire réel, sans la deviner

Les bases PAdES, et les horodatages qui maintiennent une signature en vie

Si vous signez selon les règles européennes, la norme en jeu est ETSI EN 319 142-1, qui empile quatre niveaux de base PAdES. B-B est la signature simple. B-T ajoute un horodatage de confiance qui prouve quand elle a été faite. B-LT intègre le matériel de validation, les certificats et les données de révocation, à l'intérieur du document afin qu'il puisse encore être vérifié des années plus tard. B-LTA superpose des horodatages de documents périodiques, de sorte que les preuves survivent aux algorithmes sur lesquels elles ont été construites. HotPDF émet les structures côté document pour chaque niveau :

// PAdES baseline signature field (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
  'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
  'Contract approval', 'Boston, MA', 'legal@example.com');

// Document timestamp: larger reservation for the TSA token and chain
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);

La réservation de 16384 octets sur l'horodatage est délibérée. Une autorité d'horodatage renvoie un jeton qui entraîne sa propre chaîne de certificats, de sorte qu'elle nécessite régulièrement plus de place que les 8 Ko dont une simple signature se contente. Ces horodatages de documents sont également la machinerie derrière B-LTA : re-horodater une signature archivée toutes les quelques années, avec des algorithmes toujours d'actualité, c'est ce qui maintient un document que vous avez signé en 2026 vérifiable en 2040

Un mot sur les chaînes de motif, d'emplacement et de contact que les deux appels de champ acceptent : ce sont des métadonnées de commodité et rien de plus. HotPDF les stocke en tant que simples entrées de dictionnaire et les peint dans l'apparence visible de la signature, mais aucun validateur ne les vérifie par rapport à quoi que ce soit. Remplissez-les systématiquement à partir de vos données de flux de travail, puisque les auditeurs les lisent, et ne les confondez jamais avec des preuves. La véritable affirmation cryptographique réside entièrement dans le CMS et sa chaîne de certificats, et un vérificateur ignore complètement le texte visible

Après la signature, le fichier ne peut que s'agrandir

Dès l'instant où une signature existe, les octets situés à l'intérieur de ses plages sont figés. La seule façon légitime de modifier le fichier par la suite est une mise à jour incrémentielle selon la norme ISO 32000-1 §7.5.6, qui ajoute les objets nouveaux et modifiés après les octets d'origine et enchaîne une nouvelle section de référence croisée vers ceux-ci. Fait de cette manière, la signature reste valide pour sa révision et une visionneuse signale l'état honnête : la révision signée est intacte, le document a été étendu par la suite. Re-sérialisez l'intégralité du fichier à la place et vous réécrivez les segments signés, ce qui détruit la signature même lorsque rien de visible n'a changé. Le même mécanisme de révision est également la manière dont un document porte plusieurs signatures : chaque nouvelle signature atterrit dans sa propre mise à jour incrémentielle, et ses plages couvrent tout ce qui la précède, y compris les signatures antérieures. La mécanique d'ajout uniquement (append-only), et à quel moment il est sûr de les compacter, sont couvertes dans l'article sur les flux d'objets et les mises à jour incrémentielles

Deux limites valent la peine d'être gardées à l'esprit lors de votre conception. Le mode de sortie PDF/A de HotPDF rejette d'emblée les champs de signature, de sorte que la conformité archivistique et une signature intégrée doivent être expédiées dans des fichiers distincts. Et la signature ne dit rien sur le secret : elle prouve qui a produit un document et qu'il n'a pas changé depuis, mais tout le monde peut encore le lire. Masquer le contenu est un travail distinct, géré par le chiffrement AES-256 et la stratégie d'autorisation

Quoi que vous construisiez, testez-le avec autre chose que le code qui a écrit le fichier. Ouvrez la sortie dans le panneau de signature d'Acrobat et confirmez trois choses : la signature est valide, l'identité s'enchaîne à la racine que vous attendiez, et le panneau ne signale aucune modification depuis la signature. Inversez ensuite un seul octet à l'intérieur de la plage signée d'une copie jetable et confirmez que le panneau signale maintenant que le document a été altéré. Un pipeline de signature que vous n'avez jamais vu rejeter un fichier altéré est un pipeline dont la vérification n'a pas vraiment été testée

Les trois niveaux de signature sont fournis avec le Composant HotPDF pour Delphi et C++Builder ; la page du produit lie la référence complète de l'API de signature