Comparar dois PDFs e verificar se nada mudou

Quando você guarda o original, a verificação deixa de ser adivinhação. O que cada nível de comparação prova, como executá-lo à mão e a versão via API.

Todo guia sobre como identificar um PDF editado — incluindo o nosso — termina com a mesma admissão: a verificação mais confiável é comparar o arquivo com o original. Bancos guardam os extratos que emitiram. Empregadores guardam os holerites. Credores guardam a cópia da primeira solicitação. E ainda assim quase todo mundo faz a comparação no olho, com duas janelas abertas. Esta página é sobre fazer isso direito: o que a comparação em nível de bytes pode provar, em ordem decrescente de força, e onde ela honestamente se esgota.

A direção importa. Tudo abaixo compara um candidato — o arquivo sendo verificado — contra um original em que você já confia: um que você gerou, recebeu em primeira mão ou mantém em registro. Comparar dois arquivos em que você confia igualmente responde a uma pergunta diferente e mais fraca.

Nível 1: calcule o hash — a única prova

Dois comandos, uma resposta

$ shasum -a 256 statement-on-record.pdf statement-resubmitted.pdf
8f2c…a91b  statement-on-record.pdf
8f2c…a91b  statement-resubmitted.pdf

Hashes iguais encerram a investigação: cada byte confere, então conteúdo, metadados e estrutura estão todos inalterados. Este é o único resultado de comparação em todo este campo que é uma prova, e não um sinal — exatamente por isso vale a pena montar fluxos de trabalho em que você guarda os originais. Hashes diferentes, porém, provam quase nada por si sós — um novo download por um pipeline diferente, um novo salvamento, uma assinatura adicionada mudam bytes sem tocar em um único caractere visível. Os dois níveis seguintes tratam de ler que tipo de diferença é.

Nível 2: o original dentro do candidato

O PDF tem uma propriedade que nenhum outro formato comum de documento compartilha: edições feitas no próprio arquivo são anexadas. Uma atualização incremental adiciona novos objetos, uma nova seção de referências cruzadas e um novo %%EOF ao final do arquivo — e deixa cada byte original exatamente onde estava. O que significa que um arquivo editado dessa forma literalmente contém o documento a partir do qual foi editado, byte a byte, como um prefixo que termina em uma fronteira de revisão.

O original é um prefixo de bytes do candidato?

$ cmp -n "$(stat -f%z statement-on-record.pdf)" \
      statement-on-record.pdf statement-resubmitted.pdf \
  && echo "candidate = original + appended bytes"

Quando isso se confirma — e a junção cai sobre um %%EOF que a própria cadeia de revisões do candidato reconhece — você sabe algo notavelmente forte: o candidato é o seu original, mais uma edição registrada. Não "parecido com". Não "provavelmente derivado de". O original intocado está sentado dentro do arquivo sob suspeita, e os bytes anexados são a edição completa e inspecionável. A direção inversa é igualmente informativa: um candidato que é um prefixo do seu original é uma revisão anterior — alguém enviou o documento como ele era antes de uma mudança posterior, como antes de ser assinado.

O que os bytes anexados tocaram

"Revisão anexada" ainda não é um achado — assinar um documento anexa uma revisão, e preencher um campo de formulário também. A pergunta que decide a severidade é quais objetos a revisão anexada sobrescreve. Uma atualização que adiciona um objeto de assinatura não toca em nada que era visível. Uma atualização que reescreve um número de objeto que já existia no seu original — e esse objeto carrega dados de página, de stream de conteúdo ou de imagem — mudou o que o documento mostra depois que você o emitiu. Essa distinção é legível a partir das seções de referências cruzadas, mecanicamente: esta é a forma mais forte do sinal de atualizações incrementais, porque com o original em mãos não há ambiguidade sobre como era a revisão anterior.

Nível 3: quando os dois arquivos foram reescritos

A ancestralidade se quebra no momento em que qualquer um dos arquivos é salvo de novo por inteiro — um imprimir-em-PDF, uma exportação, a maioria das ferramentas online. Os bytes então não compartilham nenhum prefixo, e a comparação honesta se degrada para a estrutura interpretada, campo a campo:

  • Metadados, os dois repositórios. Dicionário Info e XMP, comparados campo a campo — producer, creator, título, os dois pares de datas. Veja Forense de metadados de PDF (em inglês) para o que cada divergência significa.
  • O par /ID. O primeiro elemento é atribuído na criação e obrigado a sobreviver a todo salvamento; dois arquivos cujos IDs permanentes coincidem afirmam descender do mesmo original. Autodeclarado, portanto evidência de apoio — nunca prova.
  • Contagem e geometria das páginas. Re-exportações preservam quantas páginas existem e seu tamanho físico; um MediaBox alterado na página 2 é um fato que exige explicação.
  • Os conjuntos de fontes. Uma fonte presente em um arquivo e ausente no outro significa que texto foi recomposto por uma ferramenta diferente — ou que texto novo chegou em um novo tipo de letra, o mesmo mecanismo do sinal de anomalias de fontes.

Cada um desses itens é um fato sobre diferença, não um veredicto sobre intenção. Pipelines de documentos em múltiplos estágios produzem divergência estrutural de forma legítima e constante.

O que nenhuma comparação de bytes pode provar

Que dois arquivos reescritos parecem idênticos. Provar a aparência renderizada exige de fato renderizar os dois documentos e comparar pixels — o que significa rodar um renderizador de PDF completo sobre entrada não confiável, com os falsos positivos de anti-aliasing e de substituição de fontes por cima. O recurso Comparar arquivos do Acrobat vive nesse nível e, para uma comparação visual lado a lado de dois documentos específicos, é a ferramenta certa. Uma comparação em nível de bytes para um passo honesto antes: ela diz se e onde os arquivos diferem estruturalmente, e afirma com clareza que limpeza estrutural não é identidade visual.

Automatizando: uma única chamada de API

A Tamperlens roda os três níveis como uma única requisição: POST /api/v1/compare com o arquivo em registro e o arquivo sendo verificado. O relatório abre com a relação (relationship) — identical, candidate-extends-original, candidate-truncates-original ou rewritten — seguida da classificação da revisão anexada, do diff de metadados e do diff estrutural, cada um como achados que carregam evidência.

Verifique um extrato reenviado contra o que está em registro

curl -s https://tamperlens.com/api/v1/compare \
  -H "Authorization: Bearer $TAMPERLENS_KEY" \
  -F [email protected] \
  -F [email protected]

Os dois arquivos são interpretados em memória e nunca armazenados; uma comparação é contabilizada como dois documentos na cota mensal. Detalhes, o schema completo do relatório e os códigos de erro estão no guia rápido da API.

Guarda originais? Verifique contra eles.

Uma chave de API com 25 documentos por mês — doze comparações — é grátis: crie uma conta ou comece pelo guia rápido da API. Sem cartão.

A Tamperlens reporta sinais de risco e fatos em nível de bytes, não veredictos de autenticidade. Só a identidade de bytes é uma prova; todo o resto precisa do seu julgamento.

Leitura relacionada