Eine PDF-Signatur ist größtenteils Buchführung über Bytes, und genau dort geht sie schief. Die Kryptografie läuft auf Code, der seit zwei Jahrzehnten geprüft wird, und dieser Teil versagt fast nie. Was im Produktivbetrieb versagt, ist bescheidener: ein Platzhalter, der für die echte Signatur zu klein reserviert wurde, ein Hash über den falschen Abschnitt der Datei oder ein „Speichern“ nach dem Signieren, das still Bytes umgeschrieben hat, die die Signatur bereits eingefroren hatte. Legen Sie die Bytes richtig aus, und um das grüne Häkchen kümmert sich der Rest von selbst
HotPDF deckt das Signieren für Delphi und C++Builder auf drei Ebenen ab, und Sie wählen zwischen ihnen, indem Sie eine Frage beantworten: Wo liegt der private Schlüssel? Eine PFX-Datei auf der Platte braucht einen einzigen Funktionsaufruf. Ein Schlüssel, der in einem HSM oder einem entfernten Signaturdienst eingeschlossen ist, braucht die Folge aus Reservieren, Hashen und Einsetzen, denn keine Bibliothek kann in einen Token hineingreifen und den Schlüssel herausziehen. Eine Signatur, die europäisches Recht erfüllen muss, braucht darüber hinaus die PAdES-Baseline-Strukturen. Die folgenden Abschnitte gehen diesen Weg entlang
Wie /ByteRange die signierten Bytes festnagelt
Eine Signatur muss in der Datei liegen, die sie signiert, und sie kann sich nicht selbst signieren. PDF umgeht das Paradox, indem es ein Loch lässt. Vor dem Signieren reserviert der Schreiber einen /Contents-Eintrag fester Größe voller Nullen und hält in einem /ByteRange-Feld die beiden Bereiche zu seinen Seiten fest: alles davor, alles danach. Der Signierer hasht diese beiden Bereiche und schreibt den entstehenden CMS-Block hexadezimal in das Loch. Die Falle steckt im Wort fest. Sie legen sich auf die Größe des Lochs fest, bevor Sie wissen, wie groß die fertige Signatur wird, also muss die Reservierung eine beherzt großzügige Schätzung sein. Acht Kilobyte fassen eine losgelöste CMS-Signatur mit kurzer Zertifikatskette bequem
HotPDF trennt die beiden Fälle in zwei Aufrufe, und sie zu verwechseln ist ein häufiger Anfängerfehler. AddSignatureField setzt ein leeres, sichtbares Feld, das ein Mensch später in einem Viewer signiert. AddSignedSignatureField legt das Feld an und reserviert das /Contents-Loch, und das ist der Aufruf, den Sie wollen, sobald Code und nicht ein Mensch die Signatur vollendet. Reichen Sie einem externen Signierer ein leeres Feld, hat er nichts zu füllen
Der Weg mit einem Aufruf: aus einer PFX signieren
Wenn Zertifikat und privater Schlüssel in einer PFX/PKCS#12-Datei liegen, die Ihr Prozess lesen kann, schrumpft die ganze Kette auf eine Klassenfunktion:
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');
Wenn das fehlschlägt, liegt es selten am PDF. Es liegt an der PFX. HotPDF liest Container, die mit PBES2 geschützt sind, also mit PBKDF2-Schlüsselableitung über AES-256-CBC. Eine PFX, die ein älterer Windows-Zertifikatsassistent oder OpenSSL vor Version 3.0 exportiert hat, steckt meist stattdessen in altem RC2 oder 3DES und lässt sich schlicht nicht zerlegen. Die Abhilfe ist, den Container einmal mit moderner Absicherung neu zu exportieren; das heutige OpenSSL tut das von sich aus, und es ist keine Codeänderung. Wenn das Signieren also sofort an einem Zertifikat stirbt, das „überall sonst funktioniert“, sehen Sie sich an, wie die PFX entstanden ist, bevor Sie Ihren eigenen Code verdächtigen
Reservieren, hashen, einsetzen für HSMs und Token
Der Weg mit einem Aufruf setzt voraus, dass Ihr Prozess den Schlüssel als Datei lesen kann. Zunehmend kann er das nicht. Der Schlüssel liegt in einem HSM, auf einem USB-Token oder hinter der API eines Signaturdienstes, und keine Bibliothek kommt direkt an ihn heran. HotPDF löst das, indem es das Signieren in Schritte auf Byte-Ebene zerlegt: ein Dokument mit Platzhalter schreiben, die Hash-Bereiche von der Bibliothek erfragen, die Hash-Eingabe an denjenigen reichen, der den Schlüssel hält, und dann das zurückgegebene CMS wieder in das Loch einsetzen
var
Doc: THotPDF;
Fs: TFileStream;
PdfBytes, HashInput, SigHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
// 1. Das Dokument mit reserviertem /Contents-Loch schreiben
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. Die gespeicherten Bytes laden; die Offsets zählen ab 0
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. Beide Bereiche hashen und extern signieren (HSM, Token, Dienst)
HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
Copy(PdfBytes, R2Start + 1, R2Len);
SigHex := SignWithHsm(HashInput); // Ihre Integration: liefert CMS als Hex
// 4. Die Signatur in das reservierte Loch einsetzen
THotPDF.InsertSignatureHex(PdfBytes, SigHex);
Fs := TFileStream.Create('signed.pdf', fmCreate);
try
Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
finally
Fs.Free;
end;
end;
Zwei Details dieser Folge verursachen die meisten sporadischen Fehlschläge. Das erste ist, dass PreparePDFForSigning auf den Bytes einer fertigen Datei arbeitet. Der Platzhalter muss vollständig geschrieben und gespeichert sein, bevor die Offsets etwas bedeuten; berechnen Sie sie gegen einen Stream, der noch zusammengebaut wird, passen sie nicht zu den Bytes, die Sie später hashen. Das zweite ist wieder die Reservierungsgröße. Die 8192 Bytes, um die Sie gebeten haben, müssen das endgültige CMS fassen, und eine Signatur mit Zwischenzertifikaten oder eine, die ein Dienst mit signierten Attributen schmückt, kann darüber hinauslaufen. InsertSignatureHex vergrößert das Loch nicht, um Platz zu schaffen. Das Anzeichen ist eine Kette, die mit einem Zertifikat sauber signiert und mit dem nächsten scheitert; die Kur ist, den Platzhalter mit einer Reservierung neu zu erzeugen, die an einer echten Signatur des tatsächlichen Signierers gemessen und nicht geraten wurde
PAdES-Baselines und die Zeitstempel, die eine Signatur am Leben halten
Wenn Sie unter europäischen Regeln signieren, ist ETSI EN 319 142-1 der maßgebliche Standard, der vier PAdES-Baseline-Stufen aufeinanderstapelt. B-B ist die schlichte Signatur. B-T ergänzt einen vertrauenswürdigen Zeitstempel, der beweist, wann sie entstand. B-LT bettet das Prüfmaterial, also Zertifikate und Sperrdaten, ins Dokument ein, sodass es sich Jahre später noch prüfen lässt. B-LTA legt regelmäßige Dokumentzeitstempel darüber, sodass der Nachweis die Algorithmen überlebt, auf denen er gebaut wurde. HotPDF gibt die dokumentseitigen Strukturen für jede Stufe aus:
// PAdES-Baseline-Signaturfeld (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
'Contract approval', 'Boston, MA', 'legal@example.com');
// Dokumentzeitstempel: größere Reservierung für TSA-Token und Kette
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);
Die Reservierung von 16384 Bytes für den Zeitstempel ist Absicht. Eine Zeitstempelstelle liefert einen Token zurück, der seine eigene Zertifikatskette mitschleppt, also braucht er regelmäßig mehr Platz als die 8 KB, mit denen eine schlichte Signatur zufrieden ist. Diese Dokumentzeitstempel sind auch die Mechanik hinter B-LTA: Eine archivierte Signatur alle paar Jahre mit noch aktuellen Algorithmen neu zu stempeln, ist das, was ein 2026 signiertes Dokument im Jahr 2040 prüfbar hält
Ein Wort zu den Zeichenketten für Grund, Ort und Kontakt, die beide Feldaufrufe entgegennehmen: Sie sind bequeme Metadaten und mehr nicht. HotPDF legt sie als schlichte Dictionary-Einträge ab und malt sie in die sichtbare Signaturdarstellung, doch kein Prüfer gleicht sie gegen irgendetwas ab. Füllen Sie sie einheitlich aus Ihren Ablaufdaten, denn Prüfer lesen sie durchaus, und halten Sie sie dann nie für einen Beweis. Die eigentliche kryptografische Aussage steckt vollständig im CMS und seiner Zertifikatskette, und ein Prüfprogramm ignoriert den sichtbaren Text vollkommen
Nach dem Signieren darf die Datei nur wachsen
In dem Moment, in dem eine Signatur existiert, sind die Bytes innerhalb ihrer Bereiche eingefroren. Der einzige rechtmäßige Weg, die Datei danach zu ändern, ist ein inkrementelles Update nach ISO 32000-1 §7.5.6, das neue und geänderte Objekte hinter die ursprünglichen Bytes anhängt und einen frischen Querverweisabschnitt daran kettet. So gemacht, bleibt die Signatur für ihre Revision gültig, und ein Viewer meldet den ehrlichen Zustand: Die signierte Revision ist unversehrt, das Dokument wurde danach erweitert. Serialisieren Sie stattdessen die ganze Datei neu, schreiben Sie die signierten Bereiche um, und das zerstört die Signatur, selbst wenn sich nichts Sichtbares geändert hat. Derselbe Revisionsmechanismus ist auch der Weg, auf dem ein Dokument mehrere Signaturen trägt: Jede neue Signatur landet in ihrem eigenen inkrementellen Update, und ihre Bereiche decken alles davor ab, samt der früheren Signaturen. Die anhängende Mechanik und die Frage, wann man sie gefahrlos verdichten darf, behandelt der Artikel zu Objektströmen und inkrementellen Updates
Zwei Grenzen sollten Sie beim Entwurf im Kopf behalten. Der PDF/A-Ausgabemodus von HotPDF weist Signaturfelder rundheraus zurück, also müssen Archivkonformität und eingebettete Signatur als getrennte Dateien ausgeliefert werden. Und Signieren sagt nichts über Geheimhaltung: Es beweist, wer ein Dokument erzeugt hat und dass es sich seither nicht geändert hat, doch lesen kann es weiterhin jeder. Den Inhalt zu verbergen ist eine eigene Aufgabe, die AES-256-Verschlüsselung und Rechterichtlinie übernimmt
Was Sie auch bauen, testen Sie es mit etwas anderem als dem Code, der die Datei geschrieben hat. Öffnen Sie die Ausgabe in der Signaturleiste von Acrobat und bestätigen Sie drei Dinge: Die Signatur ist gültig, die Identität kettet zur erwarteten Wurzel, und die Leiste meldet keine Änderungen seit dem Signieren. Kippen Sie dann in einer Wegwerfkopie ein einzelnes Byte innerhalb des signierten Bereichs und bestätigen Sie, dass die Leiste das Dokument nun als verändert bezeichnet. Eine Signaturkette, bei der Sie nie zugesehen haben, wie sie eine manipulierte Datei ablehnt, ist eine, deren Prüfung nie wirklich getestet wurde
Alle drei Signaturebenen kommen mit der HotPDF Delphi Component für Delphi und C++Builder; die Produktseite verlinkt die vollständige Referenz der Signatur-API