Kurz gesagt. Eine PDF-Signatur ist eine CMS-Signatur (PKCS #7), die im Eintrag /Contents eines Signatur-Dictionarys gespeichert ist. Die Signatur deckt die in /ByteRange aufgeführten Bytes ab, also die gesamte Datei zum Zeitpunkt des Signierens ohne den Wert von /Contents selbst. Validatoren beantworten drei getrennte Fragen. Integrität: Stimmen die signierten Bytes noch überein? Vertrauen: Führt die Zertifikatskette des Unterzeichners zu einem Root-Zertifikat, dem der Validator vertraut? Änderungen: Was wurde nach dem Signieren angehängt, und ist das erlaubt? „Gültig, aber nicht vertrauenswürdig“, das gelbe Fragezeichen in Acrobat, bedeutet fast immer, dass die Bytes intakt sind, die CA des Unterzeichners aber nicht in der Vertrauensliste des PDF-Readers steht. PAdES (ETSI EN 319 142-1) definiert Levels von B-B bis B-LTA. Ein Zeitstempel nach RFC 3161 (B-T) belegt, wann die Signatur existierte, und eingebettete Validierungsdaten plus Dokumentzeitstempel (B-LT, B-LTA) halten sie prüfbar, nachdem Zertifikate abgelaufen sind.
Was in einem signierten PDF steckt
Der folgende Test signiert mit pyHanko 0.38 (CLI 0.6.0) eine einseitige Rechnung; er verwendet einen P-256-Schlüssel einer kleinen Test-CA und einen Zeitstempel der öffentlichen TSA von DigiCert:
$ pyhanko sign addsig --field Sig1 --use-pades --timestamp-url http://timestamp.digicert.com --reason Approved \
pemder --key signer.key --cert signer.pem --chain root.pem --no-pass invoice.pdf invoice-signed.pdf
Die Signiersoftware schreibt die Datei nicht neu, sondern hängt ein inkrementelles Update an: neue Objekte, einen neuen Querverweisabschnitt und einen neuen Trailer hinter dem ursprünglichen %%EOF. Die ursprüngliche 627-Byte-Datei steht weiterhin Byte für Byte am Anfang der signierten Datei. Das Update fügt ein Signaturfeld und ein Signatur-Dictionary hinzu. Das hier gezeigte stammt aus einem anderen Lauf desselben Befehls, deshalb weichen seine Zahlen von denen der Datei ab, die weiter unten untersucht wird (der Hex-Wert von /Contents ist gekürzt):
9 0 obj
<<
/Contents <30821CFC06092A864886F70D010702A0821CED3...(zeros)...0000000>
/ByteRange [0 1331 23601 1118]
/Type /Sig
/Filter /Adobe.PPKLite
/SubFilter /ETSI.CAdES.detached
/M (D\07220261020140517Z)
/Reason (Approved)
/Prop_Build <<
/App <<
/Name /pyHanko#20CLI
/REx (0\05638\0560)
>>
>>
>>
endobj
Der Wert von /M ist das PDF-Datum D:20261020140517Z. \072 ist ein maskierter Doppelpunkt. Ein kleines Skript, das /ByteRange und die Größe von /Contents ausliest, liefert die Zahlen für die Datei, um die es im Rest dieser Seite geht:
file size: 24869 bytes, revisions (%%EOF markers): 2
/ByteRange [0 1331 23751 1118]
signed: bytes 0-1330 and 23751-24868; the 22420-byte gap is the /Contents value
/Type /Sig /SubFilter /ETSI.CAdES.detached
/Contents: 11209 bytes reserved, 7472 bytes used by the CMS object
range ends at end of file: True
So greifen die Teile ineinander:
/ByteRange [a b c d]nennt zwei Byte-Bereiche:bByte aba, danndByte abc. Die Lücke dazwischen ist genau der Hex-String von/Contentseinschließlich der Begrenzungszeichen. Alles andere, was die Datei beim Signieren enthält, fließt in den Hash ein. Weil die Dateigröße erst feststeht, wenn die Signatur an ihrem Platz ist, schreibt die Signiersoftware einen Platzhalter, trägt/ByteRangeein, bildet den Hash, signiert und schreibt die Signatur dann in den reservierten Bereich./Contentsenthält die DER-kodierte CMS-StrukturSignedData, hexadezimal kodiert und mit Nullen aufgefüllt. pyHanko hat 11.209 Byte reserviert und 7.472 davon genutzt. Der Rest ist Padding, das nötig ist, weil der Platz reserviert werden muss, bevor die Größe der Signatur bekannt ist./SubFilternennt das Format.ETSI.CAdES.detachedist das PAdES-Format.adbe.pkcs7.detachedist das ältere Format aus ISO 32000-1. Beide sind abgesetzte (detached) CMS-Signaturen über die ByteRange-Bytes./Mist der vom Unterzeichner angegebene Signaturzeitpunkt. PAdES legt ihn hier ab: Das Baseline-Profil bestimmt für jedes Level, dass das CMS-Attributsigning-time„nicht vorhanden sein darf“. Der Wert ist nur eine unbelegte Angabe, denn wer den Schlüssel hat, kann jede beliebige Zeit eintragen.
Das CMS-Objekt ist ganz gewöhnlich, und OpenSSL prüft es gegen die extrahierten Bereiche:
CMS Verification successful
1 object: contentType (1.2.840.113549.1.9.3)
1 object: id-smime-aa-signingCertificateV2 (1.2.840.113549.1.9.16.2.47)
1 object: id-smime-aa-timeStampToken (1.2.840.113549.1.9.16.2.14)
1 object: messageDigest (1.2.840.113549.1.9.4)
Die signierten Attribute sind der Content Type, der messageDigest der ByteRange-Bytes und signingCertificateV2, ein Hash des Zertifikats des Unterzeichners, der verhindert, dass jemand ein anderes Zertifikat mit demselben Schlüssel unterschiebt. Das nicht signierte Attribut timeStampToken ist der Zeitstempel nach RFC 3161 über den Signaturwert. Der PDF-Signatur-Verifizierer zeigt diese Aufschlüsselung für jedes PDF, einschließlich der Bytes, die jede Signatur abdeckt. Der Zeitstempel-Inspektor dekodiert das eingebettete Token.
Drei Fragen, die ein Validator beantwortet
Der Bericht von pyHanko hält sie auseinander und eignet sich deshalb gut zum Lernen. Hier mit dem Root-Zertifikat der Test-CA als Vertrauensanker:
Trust anchor: "Organization: Example GmbH, Common Name: Example Signing Root CA"
The signer's certificate is trusted.
Integrity
---------
The signature is cryptographically sound.
Signing time
------------
Signing time as reported by signer: 2026-10-20T14:04:36+00:00
Signature timestamp token: 2026-10-20T14:04:37+00:00
The token is guaranteed to be newer than the signature.
This timestamp is backed by a time stamping authority.
The timestamp token is cryptographically sound.
TSA certificate subject: "Common Name: DigiCert SHA256 RSA4096 Timestamp Responder 2026 1; Organization: DigiCert, Inc.; Country: US"
The TSA certificate is trusted.
Modifications
-------------
The signature covers the entire file.
Bottom line
-----------
The signature is judged VALID.
Gültig, aber nicht vertrauenswürdig
Dieselbe Datei, validiert ohne das Root-Zertifikat der Test-CA:
Trust anchor: "No path to trust anchor found."
The signer's certificate is untrusted.
Integrity
---------
The signature is cryptographically sound.
...
Bottom line
-----------
The signature is judged INVALID.
Die Integrität ist intakt, das Vertrauen fehlt. Genau das meldet Acrobat als „Signature validity is UNKNOWN“, mit dem Hinweis, dass die Identität des Unterzeichners unbekannt ist. Das ist die häufigste Beschwerde über signierte PDFs, und meist ist mit dem Dokument alles in Ordnung. PDF-Reader vertrauen Signaturzertifikaten über ihre eigenen Listen. Acrobat nutzt die Adobe Approved Trust List (AATL) und optional die EU-Vertrauenslisten. Hier landen Zertifikate einer Firmen-CA, Zertifikate eines nationalen Personalausweises, den der PDF-Reader nicht kennt, und selbstsignierte Zertifikate. Abhilfe gibt es auf der Seite des Lesers (der ausstellenden CA vertrauen oder die EU-Vertrauenslisten aktivieren) oder auf der Seite des Unterzeichners (mit einem Zertifikat einer CA signieren, der die Empfänger vertrauen). Beachte das eigene Ergebnis für das TSA-Zertifikat: Die Kette der TSA von DigiCert führt zu einem Root-Zertifikat im Trust Store des Systems, deshalb ist der Zeitstempel vertrauenswürdig, auch wenn der Unterzeichner es nicht ist.
Geänderte Bytes im signierten Bereich
Wenn EUR 1,250.00 direkt in der Datei durch EUR 9,250.00 ersetzt wird:
Integrity
---------
The signature is cryptographically unsound.
...
Bottom line
-----------
The signature is judged INVALID.
Daran führt kein Weg vorbei. Jedes Byte in den Bereichen fließt in den Hash ein, deshalb macht jede direkte Änderung die Signatur ungültig.
Nach dem Signieren angehängte Änderungen
Die interessanten Angriffe lassen die signierten Bytes unberührt und fügen eine neue Revision hinzu. PDF erlaubt inkrementelle Updates, und für das Ausfüllen von Formularen, das Hinzufügen von Anmerkungen oder weiteren Signaturen sind sie legitim. Der Test hängt eine Revision an, die den Seiteninhalt durch eine Fassung mit dem neuen Betrag ersetzt:
file size: 26337 bytes, revisions (%%EOF markers): 3
/ByteRange [0 1331 23751 1118]
range ends at end of file: False
...
The signature is cryptographically sound.
...
Modifications
-------------
The signature does not cover the entire file.
Some modifications may be illegitimate, and they appear to be incompatible with the current document modification policy.
Bottom line
-----------
The signature is judged INVALID.
Die Signatur ist kryptografisch weiterhin einwandfrei, denn die signierte Revision ist unverändert. Was der Leser sieht, ist die neue Revision. Das erkennt nur die Differenzanalyse: der Vergleich dessen, was sich nach dem Signieren geändert hat, mit dem, was erlaubt ist. Ein Viewer, der nur „Signatur einwandfrei, Unterzeichner vertrauenswürdig“ prüft, zeigt 9.250 EUR unter einer gültigen Signatur an. Untersuchungen zu PDF-Viewern ab 2019 haben genau diese Lücken in vielen Anwendungen gefunden (Incremental-Saving- und Shadow-Angriffe). Eine zweite Signatur hinzuzufügen ist dagegen eine erlaubte Änderung:
Modifications
-------------
The signature does not cover the entire file.
All modifications relate to signing and form filling operations, and they appear to be compatible with the current document modification policy.
Bottom line
-----------
The signature is judged VALID.
Eine Zertifizierungssignatur (--certify in pyHanko, „Zertifizieren“ in Acrobat) legt in ihren DocMDP-Berechtigungen die Richtlinie des Autors für spätere Änderungen fest: keine Änderungen, nur Ausfüllen von Formularen oder Ausfüllen von Formularen und Anmerkungen. Validatoren setzen sie in der Differenzanalyse durch.
Zeit: Zeitstempel nach RFC 3161
Eine Signatur entsteht mit einem Zertifikat, das abläuft und widerrufen werden kann. Ohne unabhängigen Nachweis, wann die Signatur erstellt wurde, kann ein Validator das Zertifikat nur nach heutigem Stand prüfen. Ist es abgelaufen oder aus irgendeinem Grund widerrufen, sieht die Signatur genauso schlecht aus wie eine, die gestern mit einem gestohlenen Schlüssel erstellt wurde. Der Eintrag /M hilft dabei nicht, denn ihn wählt der Unterzeichner.
Ein Zeitstempel nach RFC 3161 von einem vertrauenswürdigen Zeitstempeldienst (TSA) löst das Problem. Die TSA signiert ein Token, das den Hash des Signaturwerts und ihre eigene Uhrzeit enthält. Im Test lieferte die TSA von DigiCert ein Token für 14:04:37 UTC, eine Sekunde nach dem angegebenen Signaturzeitpunkt. Das belegt, dass die Signatur zu diesem Zeitpunkt bereits existierte, während das Zertifikat gültig war. Das ist PAdES-Level B-T. Zeitstempel lassen sich auch als eigener Dokumentzeitstempel hinzufügen: ein Signatur-Dictionary mit /Type /DocTimeStamp und /SubFilter /ETSI.RFC3161, das das gesamte Dokument in seinem aktuellen Zustand mit einem Zeitstempel versieht.
Langzeitvalidierung: B-LT und B-LTA
Eine Validierung in zehn Jahren braucht mehr als einen Zeitstempel. Die CRLs oder OCSP-Responder der CA gibt es dann womöglich nicht mehr, und die Algorithmen können geschwächt sein. ETSI EN 319 142-1 (V1.2.1, Januar 2024) definiert die Levels:
| Level | Ergänzt | Zweck |
|---|---|---|
| B-B | Die CMS-Signatur mit den vorgeschriebenen signierten Attributen | Basissignatur |
| B-T | Einen vertrauenswürdigen Zeitstempel | Belegt, dass die Signatur zu einem bestimmten Zeitpunkt existierte |
| B-LT | Das gesamte Validierungsmaterial: Zertifikate, CRLs und OCSP-Antworten im DSS des Dokuments | Validierung, ohne etwas abzurufen |
| B-LTA | Dokumentzeitstempel über das Dokument einschließlich dieses Materials, im Lauf der Zeit erneuert | Hält Material und Zeitstempel vertrauenswürdig, während Zertifikate ablaufen und Algorithmen altern |
Das Validierungsmaterial liegt im Document Security Store (/DSS im Dokumentkatalog), der nach dem Signieren in einem inkrementellen Update hinzukommt. Dieselbe Rechnung, signiert auf Level B-LTA, mit einer CRL, die die Test-CA bereitstellt:
file size: 54674 bytes, revisions (%%EOF markers): 4
/ByteRange [0 1331 23745 1099]
/Type /Sig /SubFilter /ETSI.CAdES.detached
range ends at end of file: False
/ByteRange [0 34471 52463 668]
/Type /DocTimeStamp /SubFilter /ETSI.RFC3161
range ends at end of file: False
catalog /DSS: True
/Certs present: True
/CRLs present: True
/OCSPs present: True
/VRI present: True
...
Modifications
-------------
The signature does not cover the entire file.
All modifications relate to signature maintenance, and they appear to be compatible with the current document modification policy.
Bottom line
-----------
The signature is judged VALID.
Vier Revisionen: das Original; die Signatur; ein Update, das den DSS mit den Validierungsdaten hinzufügt und mit dem Dokumentzeitstempel endet, dessen Byte-Bereich bei Byte 53.131 aufhört; und ein letztes Update von 1.543 Byte, das nach dem Zeitstempel angehängt wurde und das der Validator als Signaturpflege (signature maintenance) einstuft. Eine Auflistung des DSS einer auf dieselbe Weise signierten Datei zeigt, was „das gesamte Validierungsmaterial“ in der Praxis bedeutet. Der DSS enthält die Kette des Unterzeichners und die CRL der Test-CA, außerdem die Kette des Zeitstempel-Responders von DigiCert mit zwei OCSP-Antworten, denn auch der Zeitstempel muss prüfbar bleiben:
certs:
Organization: Example GmbH, Common Name: Example Signing Root CA
Organization: Example GmbH, Common Name: Alice Example
Common Name: DigiCert Trusted Root G4, Organizational Unit: www.digicert.com, Organization: DigiCert Inc, Country: US
Common Name: DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1; Organization: DigiCert, Inc.; Country: US
Common Name: DigiCert SHA256 RSA4096 Timestamp Responder 2026 1; Organization: DigiCert, Inc.; Country: US
CRLs:
Organization: Example GmbH, Common Name: Example Signing Root CA
OCSP responses:
ef6f534ae9e4067c7acae29056f62fd449eccb4e
ecd7e382d2715d644cdf2e673fe7ba98ae1c0f4f
Die OCSP-Responder werden über den Hash ihres Schlüssels identifiziert. Der Dokumentzeitstempel wiederum muss erneuert werden, bevor sein TSA-Zertifikat abläuft, und pyhanko sign ltaupdate fügt einen neuen hinzu. Acrobat kennzeichnet Signaturen, deren Validierungsmaterial eingebettet ist, als „LTV enabled“.
Welches Level du erzeugen solltest
- Setze immer einen Zeitstempel. B-B reicht nur für Dokumente, die innerhalb der Laufzeit des Zertifikats gelesen und abgelegt werden, und selbst dann kostet ein Zeitstempel eine einzige HTTP-Anfrage an eine TSA. Nimm B-T als Minimum.
- Nutze B-LTA für alles, was archiviert wird. Verträge, regulierte Aufzeichnungen und Rechnungen, die noch Jahre später prüfbar sein müssen, brauchen eingebettete Validierungsdaten und Dokumentzeitstempel. Führe
ltaupdateplanmäßig aus, und zwar deutlich bevor das TSA-Zertifikat des neuesten Dokumentzeitstempels abläuft und bevor seine Algorithmen als schwach gelten. - Die Rechtswirkung ist eine eigene Frage. Nach der eIDAS-Verordnung (EU) Nr. 910/2014 braucht eine qualifizierte elektronische Signatur ein qualifiziertes Zertifikat eines Vertrauensdiensteanbieters, der auf den EU-Vertrauenslisten steht, und eine qualifizierte elektronische Signaturerstellungseinheit. PAdES beschreibt das Dateiformat, nicht diese rechtliche Stufe. Die Optionen
--eutl-territoriesund--require-qualifiedvon pyHanko prüfen den Teil, der die Vertrauenslisten betrifft. - Signiere mit einer CA, der deine Leser vertrauen. Für externe Empfänger heißt das: eine CA auf der AATL oder den EU-Vertrauenslisten. Eine interne CA genügt, wenn sich bei allen Lesern einrichten lässt, dass sie ihr vertrauen.
So prüfst du ein signiertes PDF
- Sieh dir alle Signaturen und Revisionen an, nicht nur das grüne Banner. Wie viele Revisionen folgen auf die letzte Signatur, und was ändern sie?
- Trenne die Ergebnisse. Integrität fehlgeschlagen → die signierten Bytes haben sich geändert. Vertrauen fehlgeschlagen → eine Vertrauensfrage auf der Seite des Lesers. Änderungen gemeldet → nach dem Signieren wurde Inhalt hinzugefügt, und du musst nachsehen, welcher.
- Prüfe die Zeitnachweise. Gibt es einen Zeitstempel von einer TSA, der du vertraust, oder nur den Eintrag
/Mdes Unterzeichners? - Prüfe bei Archiven LTV. Ist das Validierungsmaterial eingebettet, und liegt der neueste Dokumentzeitstempel noch innerhalb der Gültigkeit seines TSA-Zertifikats?
- Nutze einen Validator, der all das meldet. In Skripten:
pyhanko sign validate --pretty-printmit explizitem--trustoderpyhanko sign adesverifyim AdES-Stil. pyHanko 0.38 warnt, dass der Rückgriff auf den Trust Store des Betriebssystems veraltet ist und in einem künftigen Release entfernt wird („is deprecated and will be removed in a future release“). Gib deine Vertrauensanker deshalb explizit an. Im Browser leistet der PDF-Signatur-Verifizierer dasselbe, ohne das Dokument hochzuladen.
Häufige Fehler
- Dem sichtbaren Signaturbild vertrauen. Der Stempel auf der Seite ist Dekoration. Die Signatur ist das CMS-Objekt, und ein Bild beweist nichts.
- „Signatur gültig“ mit „Dokument unverändert“ gleichsetzen. Unverändert ist das Dokument nur, wenn die Signatur „die gesamte Datei abdeckt“ oder die Differenzanalyse ohne Befund bleibt.
- Das eigene Zertifikat des Unterzeichners als vertrauenswürdig hinzufügen, damit eine Warnung verschwindet. Vertraue der ausstellenden CA, nachdem du sie geprüft hast, oder gar nicht.
- Kein Zeitstempel. Ohne Zeitstempel einer TSA endet die Beweiskraft der Signatur mit dem Ablauf des Zertifikats.
- Ein signiertes PDF in einem Tool erneut speichern, das die Datei neu schreibt. Viele Editoren speichern vollständig statt inkrementell, und das macht jede vorhandene Signatur ungültig.
Quellen
- ISO 32000-2 (PDF 2.0), §12.8 Digitale Signaturen. ISO 32000-1 definiert
adbe.pkcs7.detachedund das Signatur-Dictionary. - ETSI EN 319 142-1 V1.2.1 (2024-01): PAdES-Bausteine und Baseline-Levels, Dokumentzeitstempel.
- RFC 5652 (CMS), RFC 3161 (Zeitstempel), RFC 5035 (signingCertificateV2).
- pyHanko 0.38.0, Dokumentation und CLI.
- PDF Insecurity: Forschung zu Incremental-Saving-, Signature-Wrapping- und Shadow-Angriffen.
Alle Dateien auf dieser Seite wurden am 20. Oktober 2026 in node:24.22.0-trixie erstellt, mit pyHanko 0.38.0 (pyhanko-cli 0.6.0, pyhanko-certvalidator 0.33.0) in einem virtualenv und OpenSSL 3.5.7, mit Zeitstempeln von http://timestamp.digicert.com.