Zum Inhalt springen

Inkrementelle Aktualisierungen und ihre Bedeutung

Evidence: Standard-backed

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 revision Header, body, xref section, trailer — the original bytes.
  2. Sign A /ByteRange digest covers the whole revision except the signature value itself.
  3. Edit and save Changed objects + a new xref section are appended; originals are untouched.
  4. New trailer chains back The appended trailer carries /Prev = offset of the previous xref section.
  5. Verify The 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.

Die Append-only-Regel ist normativ. Spec: ISO 32000-2, §7.5.6 legt fest, dass der Inhalt eines PDF inkrementell aktualisiert werden kann, ohne die gesamte Datei neu zu schreiben, und dass Änderungen dabei an das Ende der Datei angehängt werden müssen, während der ursprüngliche Inhalt unverändert bleibt. Evidence: Standard-backed

Dieselbe Klausel definiert die Mechanik. Ein Querverweisabschnitt für eine inkrementelle Aktualisierung enthält nur Einträge für Objekte, die geändert, ersetzt oder gelöscht wurden. Gelöschte Objekte verbleiben in der Datei, werden aber über ihre Querverweiseinträge als gelöscht markiert. Der hinzugefügte Trailer muss einen /Prev-Eintrag enthalten, der die Position des vorherigen Querverweisabschnitts angibt. Für ein geändertes Objekt enthält der Eintrag der Aktualisierung den Byte-Offset der neuen Kopie und überschreibt damit den alten Offset. Ein Reader baut seine Querverweisinformationen so auf, dass auf die neueste Kopie jedes Objekts zugegriffen wird.

Die Konsequenz für die Signatur ergibt sich unmittelbar aus Spec: ISO 32000-2, §12.8.1 : Ein Byte-Range-Digest wird über einen Bereich der Datei berechnet – normalerweise über die gesamte Datei, ausgenommen der Signaturwert (der /Contents-Eintrag). Der Standard merkt dann an, dass die Daten, die dem Byte-Bereich der ursprünglichen Signatur entsprechen, erhalten bleiben, wenn ein signiertes Dokument geändert und per inkrementeller Aktualisierung gespeichert wird, sodass bei gültiger Signatur der Zustand des Dokuments zum Signaturzeitpunkt wiederhergestellt werden kann. Append-only ist kein Komfortmerkmal. Es ist die Eigenschaft, auf die das Signaturmodell angewiesen ist.

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.