Ir al contenido

Actualizaciones incrementales y por qué importan

Evidence: Standard-backed

Cuando un PDF cambia después de escribirse, la forma correcta de guardarlo no es reescribir el archivo. En su lugar, se añaden al final los objetos modificados y una nueva sección de referencias cruzadas, dejando cada byte original exactamente donde estaba. Esta página explica cómo funciona y por qué esto permite que una firma digital sobreviva a una edición posterior.

Una firma protege un rango de bytes. Si al guardar el cambio de una sola palabra se reescribiera el archivo, todos los desplazamientos de byte se moverían. El rango firmado dejaría de describir el mismo contenido. La firma se rompería, aunque el contenido firmado en sí no se hubiera tocado.

Las actualizaciones incrementales existen para que eso no ocurra. Los bytes originales, incluidos los bytes que cubre una firma, se mantienen en su sitio. Un revisor puede tomar un documento firmado y editado después, y verificar la primera firma contra la revisión original. El revisor ve exactamente lo que se firmó y, por separado, lo que cambió después. Si esto se hace mal, o bien se invalidan firmas válidas o, peor aún, se pierde la capacidad de demostrar qué fue lo que una firma realmente certificó.

  • Una actualización incremental añade objetos nuevos y modificados, una nueva sección de referencias cruzadas y un nuevo tráiler, todo al final del archivo.
  • El contenido del archivo original se deja intacto — no se modifica en su ubicación original.
  • El nuevo tráiler lleva una entrada /Prev: el desplazamiento de byte de la sección de referencias cruzadas anterior. Las secciones forman una cadena hacia atrás.
  • Un lector construye su índice recorriendo esa cadena de la más reciente a la más antigua. Para cualquier número de objeto, prevalece la entrada más reciente.
  • Como nada se sobrescribió, el rango de bytes cubierto por una firma anterior sigue siendo idéntico byte a byte — por eso la firma sigue verificándose y el documento se puede recuperar exactamente como se firmó.

NextPDF escribe el documento base tal como se describe en la página anterior y luego expone las tres cosas que necesita una actualización incremental.

Tras build(), el escritor (src/Writer/PdfWriter.php) conserva:

  • el búfer de salida, que se puede recuperar mediante getBuffer(), para que una actualización pueda añadirse exactamente al final de los bytes existentes;
  • el desplazamiento de byte de la última sección de referencias cruzadas, mediante getLastXrefOffset(), que se convierte en el valor /Prev de la nueva sección;
  • las entradas del diccionario de catálogo, mediante getCatalogEntries(), para que una actualización que deba volver a emitir el catálogo (por ejemplo, para adjuntar una referencia de firma) no pierda ninguna clave anterior.

Una revisión añadida asigna nuevos números de objeto (o reutiliza los existentes para los objetos que reemplaza) usando el mismo ObjectRegistry, de modo que la numeración de objetos se mantenga coherente entre revisiones. La nueva sección de referencias cruzadas enumera solo los objetos que esta revisión tocó. El nuevo tráiler repite las entradas del tráiler anterior y añade /Prev, que apunta de vuelta a la sección anterior. Esa cadena es lo que sigue un lector.

Donde esto importa con mayor claridad es en la firma. El ByteRangeCalculator de NextPDF (src/Security/Signature/ByteRangeCalculator.php) calcula el array /ByteRange como dos segmentos: todo lo anterior al valor de la firma y todo lo posterior a él — de modo que la firma cubre toda la revisión excepto sus propios bytes. Como una edición posterior se añade en lugar de sobrescribir esos bytes, ese rango nunca se mueve.

  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.
Cómo un PDF firmado que se edita después sigue siendo verificable: cada guardado añade contenido, de modo que los bytes firmados originalmente nunca se sobrescriben y la cadena de tráileres registra el historial.

La regla de añadir únicamente es normativa. Spec: ISO 32000-2, §7.5.6 establece que el contenido de un PDF puede actualizarse de forma incremental sin reescribir todo el archivo, y que, al hacerlo, los cambios deberán añadirse al final del archivo, dejando intacto el contenido original. Evidence: Standard-backed

La misma cláusula define la mecánica. Una sección de referencias cruzadas para una actualización incremental contiene entradas solo para los objetos que se modificaron, reemplazaron o eliminaron. Los objetos eliminados se dejan en el archivo, pero se marcan como eliminados a través de sus entradas de referencias cruzadas. El tráiler añadido deberá contener una entrada /Prev que indique la ubicación de la sección de referencias cruzadas anterior. La entrada de la actualización para un objeto modificado lleva el desplazamiento de byte de la nueva copia, anulando el desplazamiento antiguo. Un lector construye su información de referencias cruzadas de modo que la copia más reciente de cada objeto sea aquella a la que se accede.

La consecuencia para la firma se enuncia directamente en Spec: ISO 32000-2, §12.8.1 : un resumen de rango de bytes se calcula sobre un rango del archivo — normalmente el archivo entero, excluyendo el valor de la firma (la entrada /Contents). A continuación, el estándar señala que, si un documento firmado se modifica y se guarda mediante una actualización incremental, se conservan los datos correspondientes al rango de bytes de la firma original, de modo que, si la firma es válida, puede recrearse el estado del documento en el momento de la firma. Añadir únicamente no es un detalle accesorio. Es la propiedad de la que depende el modelo de firma.

Así se ve, estructuralmente, un PDF firmado y editado después. La revisión original termina en su propio %%EOF. La segunda revisión se añade debajo.

%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

Un validador lee el último tráiler, ve /Prev 920 y obtiene la cadena completa. Puede verificar la firma contra los bytes hasta el primer %%EOF, que no han cambiado. Luego puede informar por separado de que la revisión 2 añadió una anotación. El historial está en el archivo. No se ocultó nada mediante sobrescritura.

La trampa es «una actualización incremental significa que el cambio es pequeño, así que es inofensivo». Añadir tiene que ver con la preservación de bytes, no con el tamaño. Una actualización incremental puede añadir una gran cantidad de contenido. Lo que la convierte en una actualización incremental es que no toca los bytes que ya estaban ahí. El corolario también suele pasarse por alto: una herramienta que «optimiza» o «lineariza» un PDF firmado reescribiéndolo desde cero producirá un archivo más pequeño y limpio, pero también una firma rota, porque el rango de bytes firmado ya no existe. No es lo mismo guardar un PDF firmado que volver a guardarlo.

Añadir únicamente protege los bytes. No indica, por sí solo, si los cambios añadidos estaban autorizados. Una segunda revisión puede añadir legítimamente una segunda firma, o puede añadir contenido que el primer firmante nunca pretendió. Decidir cuál es el caso corresponde a la validación de firmas y a la política de detección de modificaciones (DocMDP). Añadir es la base que hace posible ese análisis, no el análisis en sí.

Esta página tampoco cubre cómo se calculan y se enlazan los dos rangos de bytes de una firma, ni qué comprueba una validación completa. Esos son temas separados. Y la garantía descrita aquí se aplica a archivos escritos y actualizados por un escritor conforme: un archivo cuyas revisiones anteriores ya estaban mal formadas no pasa a estar bien formado simplemente por añadirle contenido.

¿Cómo sé cuántas revisiones tiene un PDF? Contar los marcadores %%EOF y seguir la cadena /Prev desde el último tráiler. Cada sección de referencias cruzadas alcanzada es una revisión guardada.

¿Eliminar un objeto lo quita del archivo? No. Una actualización incremental marca el objeto como eliminado en su entrada de referencias cruzadas, pero los bytes del objeto permanecen en revisiones anteriores. «Eliminado» significa «no referenciado por la revisión actual», no «borrado».

¿Puede una actualización incremental cambiar la versión del PDF? Sí, estableciendo la entrada /Version en el catálogo de la revisión añadida. La cabecera permanece tal como se escribió. La entrada /Version del catálogo tiene prioridad si indica una versión posterior.

  • Actualización incremental — guardar un cambio añadiendo los objetos modificados, una nueva sección de referencias cruzadas y un nuevo tráiler al final del archivo, sin alterar los bytes existentes.
  • /Prev — la entrada del tráiler (o del flujo de referencias cruzadas) que contiene el desplazamiento de byte de la sección de referencias cruzadas anterior. Enlaza las revisiones en una cadena hacia atrás.
  • Revisión — el estado del archivo capturado por una sección de referencias cruzadas y su tráiler. Un archivo con N secciones de referencias cruzadas tiene N revisiones.
  • /ByteRange — el array de un diccionario de firma que indica los dos segmentos de bytes que cubre el resumen de la firma (todo excepto el valor de la firma en sí).
  • Rango de bytes firmado — los bytes exactos sobre los que se calculó el resumen de la firma. Las actualizaciones incrementales existen para que estos bytes nunca se muevan ni se sobrescriban.