Atualizações incrementais e por que elas são importantes
ISO 32000-2 §7.5.6 Evidence: Standard-backed
Em resumo
Seção intitulada “Em resumo”Quando um PDF muda depois de gravado, a maneira segura de salvá-lo não é reescrever o arquivo. Em vez disso, você anexa os objetos alterados e uma nova seção de referência cruzada ao final, deixando cada byte original exatamente onde estava. Esta página explica como isso funciona e por que esse é o motivo pelo qual uma assinatura digital pode sobreviver a uma edição posterior.
Por que isso importa
Seção intitulada “Por que isso importa”Uma assinatura protege um intervalo de bytes. Se salvar uma alteração de uma única palavra reescrevesse o arquivo, cada deslocamento de byte mudaria. O intervalo assinado deixaria de descrever o mesmo conteúdo. A assinatura seria invalidada, mesmo que o conteúdo assinado em si não tivesse sido tocado.
As atualizações incrementais existem para impedir que isso aconteça. Os bytes originais, incluindo os bytes cobertos por uma assinatura, permanecem no lugar. Um revisor pode pegar um documento que foi assinado e depois editado e verificar a primeira assinatura em relação à revisão original. O revisor vê exatamente o que foi assinado e, separadamente, o que mudou depois. Se você errar nesse ponto, vai invalidar assinaturas boas ou, pior, perder a capacidade de provar o que uma assinatura de fato atestou.
A versão resumida
Seção intitulada “A versão resumida”- Uma atualização incremental anexa: objetos novos e alterados, depois uma nova seção de referência cruzada e, em seguida, um novo trailer, tudo ao final do arquivo.
- O conteúdo original do arquivo é mantido intacto — não editado no local.
- O novo trailer carrega uma entrada
/Prev: o deslocamento de byte da seção de referência cruzada anterior. As seções formam uma cadeia para trás. - Um leitor constrói o índice percorrendo essa cadeia do mais recente para o mais antigo. Para qualquer número de objeto, prevalece a entrada mais recente.
- Como nada foi sobrescrito, o intervalo de bytes que uma assinatura anterior cobria continua exatamente igual, byte a byte — então a assinatura ainda é verificável, e você pode recuperar o documento exatamente como foi assinado.
Como o NextPDF aborda isso
Seção intitulada “Como o NextPDF aborda isso”O NextPDF grava o documento base como descrito na página anterior e, em seguida, expõe as três informações de que uma atualização incremental precisa.
Após build(), o writer (src/Writer/PdfWriter.php) mantém:
- o buffer de saída, recuperável via
getBuffer(), para que uma atualização possa ser anexada exatamente ao fim dos bytes existentes; - o deslocamento de byte da última seção de referência cruzada, via
getLastXrefOffset(), que se torna o valor/Prevda nova seção; - as entradas do dicionário do catálogo, via
getCatalogEntries(), para que uma atualização que precise reemitir o catálogo (por exemplo, para anexar uma referência de assinatura) não perca nenhuma chave anterior.
Uma revisão anexada aloca novos números de objeto (ou reutiliza os existentes para os objetos que substitui) contra o mesmo ObjectRegistry, de modo que a numeração de objetos permaneça consistente entre as revisões. A nova seção de referência cruzada lista apenas os objetos que esta revisão tocou. O novo trailer repete as entradas do trailer anterior e adiciona /Prev, apontando de volta para a seção anterior. É essa cadeia que o leitor segue.
O lugar em que isso fica mais evidente é a assinatura. O NextPDF, com seu ByteRangeCalculator (src/Security/Signature/ByteRangeCalculator.php), calcula o array /ByteRange como dois segmentos: tudo antes do valor da assinatura e tudo depois dele — de modo que a assinatura cobre toda a revisão exceto os próprios bytes. Como uma edição posterior é anexada em vez de gravada por cima desses bytes, esse intervalo nunca muda de posição.
- Write base revision Header, body, xref section, trailer — the original bytes.
- Sign A /ByteRange digest covers the whole revision except the signature value itself.
- Edit and save Changed objects + a new xref section are appended; originals are untouched.
- New trailer chains back The appended trailer carries /Prev = offset of the previous xref section.
- Verify The first signature still covers the same unchanged bytes; the chain shows what came after.
O que a evidência diz
Seção intitulada “O que a evidência diz”A regra de anexação exclusiva é normativa. Spec: ISO 32000-2, §7.5.6 ISO 32000-2 §7.5.6 estabelece que o conteúdo de um PDF pode ser atualizado de forma incremental sem reescrever o arquivo inteiro e que, ao fazê-lo, as alterações devem (shall) ser anexadas ao final do arquivo, deixando o conteúdo original intacto. Evidence: Standard-backed
A mesma cláusula define a mecânica. Uma seção de referência cruzada para uma atualização incremental contém entradas apenas para os objetos que foram alterados, substituídos ou excluídos. Os objetos excluídos permanecem no arquivo, mas são marcados como excluídos por meio de suas entradas de referência cruzada. O trailer adicionado deve (shall) conter uma entrada /Prev que informe a localização da seção de referência cruzada anterior. A entrada da atualização para um objeto alterado carrega o deslocamento de byte da cópia nova, substituindo o deslocamento antigo. Um leitor constrói suas informações de referência cruzada de modo que a cópia mais recente de cada objeto seja a acessada.
A consequência para a assinatura é declarada diretamente em
Spec: ISO 32000-2, §12.8.1 ISO 32000-2 §12.8.1 : um digest de intervalo de bytes
é calculado sobre um intervalo do arquivo — normalmente o arquivo inteiro, excluindo
o valor da assinatura (a entrada /Contents). A norma então observa que
se um documento assinado for modificado e salvo por atualização incremental, os dados
correspondentes ao intervalo de bytes da assinatura original são preservados. Assim, se
a assinatura for válida, o estado do documento no momento da assinatura pode ser
recriado. A anexação exclusiva não é um luxo; é a propriedade da qual o modelo de assinatura
depende.
Exemplo prático
Seção intitulada “Exemplo prático”Um PDF assinado e depois editado, visto pela estrutura. A revisão original termina em seu próprio %%EOF. A segunda revisão é anexada abaixo dela.
%PDF-2.0... original objects, including the signature dictionary ...xref0 8... entries for the original revision ...trailer<< /Size 8 /Root 1 0 R >>startxref920%%EOF <-- end of revision 1: the signed bytes stop here9 0 obj <-- revision 2, appended<< /Type /Annot /Subtype /Text /Contents (added after signing) >>endobjxref0 19 0 obj-entry...8 90000001740 00000 ntrailer<< /Size 10 /Root 1 0 R /Prev 920 >>startxref1980%%EOFUm validador lê o último trailer, vê /Prev 920 e passa a ter toda a cadeia. Ele pode verificar a assinatura em relação aos bytes até o primeiro %%EOF, que permanecem inalterados. Ele pode, então, relatar separadamente que a revisão 2 adicionou uma anotação. O histórico está no arquivo. Nada foi ocultado por sobrescrita.
Equívoco comum
Seção intitulada “Equívoco comum”A armadilha é pensar que “atualização incremental significa que a alteração é pequena, portanto é inofensiva.” A anexação tem a ver com preservação de bytes, não com tamanho. Uma atualização incremental pode adicionar uma grande quantidade de conteúdo. O que a torna uma atualização incremental é que ela não toca nos bytes que já estavam lá. O corolário também surpreende muita gente: uma ferramenta que “otimiza” ou “lineariza” um PDF assinado reescrevendo-o do zero produzirá um arquivo menor e mais limpo e uma assinatura quebrada, porque o intervalo de bytes assinado deixa de existir. Salvar um PDF assinado e salvá-lo novamente não são a mesma operação.
Limites e fronteiras
Seção intitulada “Limites e fronteiras”A anexação exclusiva protege os bytes. Ela não diz, por si só, se as alterações anexadas foram autorizadas. Uma segunda revisão pode legitimamente adicionar uma segunda assinatura, ou pode adicionar conteúdo que o primeiro signatário nunca pretendeu. Decidir qual é o caso é tarefa da validação de assinatura e da política de detecção de modificações (DocMDP). A anexação é a base que torna essa análise possível, não a análise em si.
Esta página também não cobre como os dois intervalos de bytes de uma assinatura são calculados e unidos, nem o que uma validação completa verifica. Esses são tópicos separados. E a garantia aqui vale para arquivos gravados e atualizados por um writer em conformidade: um arquivo cujas revisões anteriores já estavam malformadas não se torna bem formado por ser anexado.
Mini-FAQ
Seção intitulada “Mini-FAQ”Como sei quantas revisões um PDF tem? Conte os marcadores %%EOF e siga a cadeia /Prev a partir do último trailer. Cada seção de referência cruzada encontrada é uma revisão salva.
Excluir um objeto o remove do arquivo? Não. Uma atualização incremental marca o objeto como excluído em sua entrada de referência cruzada, mas os bytes do objeto permanecem nas revisões anteriores. “Excluído” significa “não referenciado pela revisão atual”, não “apagado.”
Uma atualização incremental pode mudar a versão do PDF? Sim, definindo a entrada /Version no catálogo na revisão anexada. O cabeçalho permanece como foi gravado. O /Version do catálogo tem precedência quando nomeia uma versão posterior.
Documentos relacionados
Seção intitulada “Documentos relacionados”- O que um PDF realmente é — o modelo de objetos e a única seção de referência cruzada que uma atualização estende.
- Como as assinaturas se encaixam em um PDF — o mecanismo de intervalo de bytes que as atualizações incrementais existem para proteger.
- Validando uma assinatura corretamente — o que uma validação correta verifica ao longo do histórico de revisões de um arquivo.
Glossário
Seção intitulada “Glossário”- Atualização incremental — salvar uma alteração anexando os objetos alterados, uma nova seção de referência cruzada e um novo trailer ao fim do arquivo, sem alterar os bytes existentes.
/Prev— a entrada do trailer (ou do stream de referência cruzada) que contém o deslocamento de byte da seção de referência cruzada anterior. Ela liga as revisões em uma cadeia para trás.- Revisão — o estado do arquivo capturado por uma seção de referência cruzada e seu trailer. Um arquivo com N seções de referência cruzada tem N revisões.
/ByteRange— o array em um dicionário de assinatura que informa os dois segmentos de bytes que o digest da assinatura cobre (tudo, exceto o próprio valor da assinatura).- Intervalo de bytes assinado — os bytes exatos sobre os quais o digest de uma assinatura foi calculado. As atualizações incrementais existem para que esses bytes nunca sejam movidos ou sobrescritos.