Zum Inhalt springen
getnextpdf.com

Inkrementelle Aktualisierungen und ihre Bedeutung

Wenn ein PDF nach dem Erstellen geändert wird, ist es beim Speichern nicht korrekt, die Datei neu zu schreiben. Stattdessen hängen Sie die geänderten Objekte und einen neuen Querverweisabschnitt am Ende an und lassen jedes ursprüngliche Byte exakt an seiner Position. Diese Seite erklärt, wie das funktioniert und weshalb genau dadurch eine digitale Signatur eine spätere Bearbeitung überstehen kann.

Eine Signatur schützt einen Byte-Bereich. Wenn schon das Speichern der Änderung eines einzigen Worts die Datei neu schreiben würde, würde sich jeder Byte-Offset verschieben. Der signierte Bereich würde nicht mehr denselben Inhalt beschreiben. Die Signatur würde ungültig, obwohl der signierte Inhalt selbst unberührt bliebe.

Inkrementelle Aktualisierungen sollen genau das verhindern. Die ursprünglichen Bytes, einschließlich der Bytes, die eine Signatur abdeckt, bleiben unverändert. Ein Prüfer kann ein signiertes und anschließend bearbeitetes Dokument nehmen und die erste Signatur anhand der ursprünglichen Revision verifizieren. Der Prüfer sieht genau, was signiert wurde, und separat, was sich danach geändert hat. Wird das falsch umgesetzt, werden entweder gültige Signaturen ungültig oder es geht – schlimmer noch – die Möglichkeit verloren, nachzuweisen, was eine Signatur tatsächlich bezeugt hat.

  • Bei einer inkrementellen Aktualisierung wird angehängt: neue und geänderte Objekte, dann ein neuer Querverweisabschnitt, dann ein neuer Trailer, alles am Ende der Datei.
  • Der ursprüngliche Dateiinhalt bleibt unverändert – er wird nicht an Ort und Stelle bearbeitet.
  • Der neue Trailer enthält einen /Prev-Eintrag: den Byte-Offset des vorherigen Querverweisabschnitts. Die Abschnitte bilden eine rückwärtsgerichtete Kette.
  • Ein Reader baut seinen Index auf, indem er diese Kette beginnend mit dem neuesten Eintrag durchläuft. Für jede Objektnummer gilt der neueste Eintrag.
  • Da nichts überschrieben wird, ist der Byte-Bereich, den eine frühere Signatur abgedeckt hat, weiterhin Byte für Byte identisch – die Signatur lässt sich also weiterhin verifizieren, und Sie können das Dokument genau so wiederherstellen, wie es signiert wurde.

NextPDF schreibt das Basisdokument wie auf der vorherigen Seite beschrieben und stellt dann die drei Dinge bereit, die eine inkrementelle Aktualisierung benötigt.

Nach build() behält der Writer (src/Writer/PdfWriter.php) Folgendes:

  • den Ausgabepuffer, abrufbar über getBuffer(), damit eine Aktualisierung exakt an das Ende der bestehenden Bytes angehängt werden kann;
  • den Byte-Offset des letzten Querverweisabschnitts über getLastXrefOffset(), der zum /Prev-Wert des neuen Abschnitts wird;
  • die Einträge des Katalog-Dictionarys über getCatalogEntries(), damit eine Aktualisierung, die den Katalog erneut ausgeben muss (zum Beispiel um eine Signaturreferenz anzuhängen), keine bereits vorhandenen Schlüssel verliert.

Eine angehängte Revision vergibt neue Objektnummern (oder verwendet bestehende Objektnummern für Objekte, die sie ersetzt, erneut) unter Verwendung derselben ObjectRegistry, sodass die Objektnummerierung über die Revisionen hinweg konsistent bleibt. Der neue Querverweisabschnitt führt nur die Objekte auf, die diese Revision verändert hat. Der neue Trailer wiederholt die Einträge des vorherigen Trailers und fügt /Prev hinzu, das auf den vorherigen Abschnitt zurückverweist. Ein Reader folgt dieser Kette.

Am deutlichsten zeigt sich das beim Signieren. Der ByteRangeCalculator von NextPDF (src/Security/Signature/ByteRangeCalculator.php) berechnet das /ByteRange-Array in zwei Segmenten: alles vor dem Signaturwert und alles danach – sodass die Signatur die gesamte Revision mit Ausnahme ihrer eigenen Bytes abdeckt. Da eine spätere Bearbeitung angehängt wird und nicht in diese Bytes hineinschreibt, verschiebt sich dieser Bereich nie.

  1. Write base revisionHeader, body, xref section, trailer — the original bytes.
  2. SignA /ByteRange digest covers the whole revision except the signature value itself.
  3. Edit and saveChanged objects + a new xref section are appended; originals are untouched.
  4. New trailer chains backThe appended trailer carries /Prev = offset of the previous xref section.
  5. VerifyThe first signature still covers the same unchanged bytes; the chain shows what came after.
Wie ein signiertes PDF, das später bearbeitet wird, überprüfbar bleibt: Jeder Speichervorgang hängt an, sodass die ursprünglich signierten Bytes nie überschrieben werden und die Trailer-Kette die Historie aufzeichnet.

Ein signiertes und anschließend bearbeitetes PDF in seiner Struktur. Die ursprüngliche Revision endet an ihrem eigenen %%EOF. Die zweite Revision wird darunter angehängt.

%PDF-2.0
... original objects, including the signature dictionary ...
xref
0 8
... entries for the original revision ...
trailer
<< /Size 8 /Root 1 0 R >>
startxref
920
%%EOF
<-- end of revision 1: the signed bytes stop here
9 0 obj <-- revision 2, appended
<< /Type /Annot /Subtype /Text /Contents (added after signing) >>
endobj
xref
0 1
9 0 obj-entry...
8 9
0000001740 00000 n
trailer
<< /Size 10 /Root 1 0 R /Prev 920 >>
startxref
1980
%%EOF

Ein Validator liest den letzten Trailer, sieht /Prev 920 und hat damit die gesamte Kette. Er kann die Signatur anhand der unveränderten Bytes bis zum ersten %%EOF verifizieren. Anschließend kann er separat melden, dass Revision 2 eine Annotation hinzugefügt hat. Die Historie steht in der Datei. Durch Überschreiben wurde nichts verborgen.

Die Falle lautet: „Inkrementelle Aktualisierung bedeutet, dass die Änderung klein und daher harmlos ist.“ Beim Anhängen geht es um die Erhaltung von Bytes, nicht um die Größe. Eine inkrementelle Aktualisierung kann sehr viel Inhalt hinzufügen. Entscheidend ist, dass sie die bereits vorhandenen Bytes nicht antastet. Auch der umgekehrte Fall wird häufig übersehen: Ein Werkzeug, das ein signiertes PDF „optimiert“ oder „linearisiert“, indem es die Datei von Grund auf neu schreibt, erzeugt eine kleinere, sauberere Datei und eine ungültige Signatur, weil der signierte Byte-Bereich nicht mehr existiert. Das Speichern eines signierten PDF und ein erneutes Speichern sind nicht dieselbe Operation.

Append-only schützt die Bytes. Für sich genommen sagt es nicht aus, ob die angehängten Änderungen autorisiert waren. Eine zweite Revision kann legitimerweise eine zweite Signatur hinzufügen, oder sie kann Inhalt hinzufügen, den der erste Unterzeichner nie beabsichtigt hat. Zu entscheiden, welcher der beiden Fälle vorliegt, ist Aufgabe der Signaturvalidierung und der Richtlinie zur Änderungserkennung (DocMDP). Das Anhängen ist die Grundlage, die diese Analyse möglich macht, nicht die Analyse selbst.

Diese Seite behandelt auch nicht, wie die beiden Byte-Bereiche einer Signatur berechnet und zusammengefügt werden, und auch nicht, was eine vollständige Validierung prüft. Das sind eigene Themen. Die Garantie bezieht sich hier auf Dateien, die von einem konformen Writer geschrieben und aktualisiert werden: Eine Datei, deren frühere Revisionen bereits fehlerhaft waren, wird nicht dadurch wohlgeformt, dass an sie angehängt wird.

Woran erkenne ich, wie viele Revisionen ein PDF hat? Zählen Sie die %%EOF-Marker und folgen Sie der /Prev-Kette ab dem letzten Trailer. Jeder erreichte Querverweisabschnitt ist eine gespeicherte Revision.

Entfernt das Löschen eines Objekts das Objekt aus der Datei? Nein. Eine inkrementelle Aktualisierung markiert das Objekt in seinem Querverweiseintrag als gelöscht, aber die Bytes des Objekts bleiben in früheren Revisionen erhalten. „Gelöscht“ bedeutet „von der aktuellen Revision nicht referenziert“, nicht „getilgt“.

Kann eine inkrementelle Aktualisierung die PDF-Version ändern? Ja, indem in der angehängten Revision der /Version-Eintrag im Katalog gesetzt wird. Der Header bleibt wie geschrieben. Das /Version im Katalog hat Vorrang, wenn es eine spätere Version nennt.

  • Inkrementelle Aktualisierung – das Speichern einer Änderung durch Anhängen der geänderten Objekte, eines neuen Querverweisabschnitts und eines neuen Trailers an das Ende der Datei, ohne bestehende Bytes zu verändern.
  • /Prev – der Eintrag im Trailer (oder im Querverweis-Stream), der den Byte-Offset des vorherigen Querverweisabschnitts enthält. Er verkettet die Revisionen zu einer rückwärtsgerichteten Kette.
  • Revision – der Dateizustand, der von einem Querverweisabschnitt und seinem Trailer erfasst wird. Eine Datei mit N Querverweisabschnitten hat N Revisionen.
  • /ByteRange – das Array in einem Signatur-Dictionary, das die zwei Byte-Segmente angibt, die der Signatur-Digest abdeckt (alles außer dem Signaturwert selbst).
  • Signierter Byte-Bereich – die exakten Bytes, über die der Digest einer Signatur berechnet wurde. Inkrementelle Aktualisierungen gibt es, damit diese Bytes nie verschoben oder überschrieben werden.