Lewati ke konten
getnextpdf.com

Byte yang sama setiap kali: PDF yang dapat direproduksi

Spec: ISO 32000-2, §14.4Spec: ISO 32000-2, §14.3.3

Bangun sebuah PDF dari input yang sama dua kali dan Anda akan mengharapkan berkas yang sama. Sebagian besar pustaka PDF tidak dapat menjanjikan itu — bangun ulang dan diff, dan byte-nya bergeser. NextPDF dapat mematok dua hal yang bergerak, sehingga input yang sama menghasilkan byte yang sama, setiap kali.

Keluaran yang identik per byte bukanlah metrik kesombongan. Ia adalah fondasi di bawah tiga hal yang benar-benar diinginkan tim.

Yang pertama adalah caching. Jika sebuah build adalah fungsi murni dari input-nya, hash keluarannya adalah sebuah kunci cache. Input yang sama, hash yang sama, lewati pekerjaannya dan sajikan berkas yang tersimpan. Ketika byte mengembara, hash mengembara, dan cache tidak pernah kena.

Yang kedua adalah tamper-evidence. Sebuah pipeline yang dapat membangkitkan ulang berkas persis yang ia kirim dapat membuktikan, kemudian, bahwa sebuah dokumen yang diarsipkan tidak diubah: bangun ulang, hash keduanya, bandingkan. Jika bahkan satu byte berbeda karena sebuah jam tersemat, buktinya hilang dan Anda kembali ke “percaya saja”.

Yang ketiga adalah CI yang dapat dipercaya. Sebuah golden-file test merekam sebuah keluaran yang diketahui-baik dan gagal ketika sebuah perubahan mengubahnya. Sinyal itu hanya bermakna jika sebuah mesin yang tak berubah mereproduksi sebuah berkas yang tak berubah. Jika setiap jalankan berbeda pada sebuah timestamp, golden file itu adalah derau, dan tim belajar mengabaikan sebuah build merah — kebiasaan paling mahal dalam pengujian.

Dalam profil deterministik NextPDF, dua field yang dikendalikan-mesin yang selain itu akan bergeser di antara build yang identik adalah tanggal-tanggal dan /ID. Ini mengasumsikan sisa pipeline sudah stabil — input yang sama, dan sebuah serialisasi yang tidak bervariasi dengan sendirinya (lebih lanjut tentang itu di bawah):

  • Tanggal tersemat. Document information dictionary membawa CreationDate dan ModDate (Spec: ISO 32000-2, §14.3.3), dan metadata XMP mencerminkannya. Tangkap “sekarang” pada saat build dan setiap build ulang berbeda.
  • File identifier. Array /ID adalah sepasang byte string yang mengidentifikasi berkas (Spec: ISO 32000-2, §14.4), disimpan di trailer dictionary (Spec: ISO 32000-2, §7.5.5). Pustaka biasanya menurunkannya dari waktu saat ini ditambah byte acak, sehingga ia berbeda di setiap jalankan menurut desainnya.

Patok keduanya — sebuah timestamp tetap dan sebuah seed tetap untuk /ID — dan keluarannya menjadi sebuah fungsi deterministik dari kontennya. Biarkan kontennya tak berubah dan berkasnya identik byte-demi-byte. Ini adalah disiplin yang sama yang ditetapkan proyek Reproducible Builds untuk perangkat lunak terkompilasi, diterapkan pada lapisan dokumen.

Determinisme di NextPDF adalah sebuah objek konfigurasi, bukan sebuah trik tes. Mesin mengekspos sebuah objek nilai DeterministicSettings di namespace NextPDF\Core. Ia bersifat final readonly, immutable, dan ia mematok persis dua sumber pergeseran yang diturunkan-jam dan diturunkan-acak yang disebut di atas: tanggal-tanggal dan /ID. Mematoknya menghilangkan dua sumber pergeseran yang paling umum, tetapi itu tidak dengan sendirinya menjamin keluaran yang identik per byte. Perilaku serialisasi mesin lainnya — pengurutan objek, font subsetting, dan pengaturan kompresi — juga harus deterministik agar keluarannya dapat direproduksi, dan NextPDF menjaganya tetap stabil menurut desainnya.

Constructor-nya menerima dua argumen:

public function __construct(
public DateTimeImmutable $timestamp,
public string $fileIdSeed,
) {
// ...
}

$timestamp adalah satu-satunya instan tetap yang ditulis ke setiap field tanggal — CreationDate, ModDate, dan cerminan XMP-nya. Berikan satu DateTimeImmutable dan dokumen berhenti bertanya kepada jam dinding sekarang pukul berapa. $fileIdSeed adalah input yang mematok trailer /ID: sebuah string heksadesimal 32 karakter. Berikan seed yang sama dan mesin menurunkan file identifier yang sama alih-alih mengambil sampel jam dan sebuah sumber acak.

Objek itu memvalidasi input-nya sendiri. Seed harus persis 32 karakter heksadesimal; apa pun selain itu ditolak pada saat konstruksi dengan sebuah InvalidConfigException alih-alih diam-diam menghasilkan sebuah /ID yang tampak berbeda. Ini adalah sikap menolak-menebak yang sama yang diambil sisa mesin — sebuah input yang ambigu gagal dengan keras alih-alih diam-diam mengubah byte-nya.

Dengan keduanya dipatok, resepnya adalah yang dibuat akrab oleh proyek Reproducible Builds: bangun ulang, diff, dan diff-nya kosong.

  1. Fix the inputsThe same content, fonts, and settings that produced the original document.
  2. Pin the timestampOne DateTimeImmutable feeds CreationDate, ModDate, and the XMP dates — no wall clock.
  3. Pin the /ID seedA 32-character hex seed derives the trailer /ID instead of a clock-plus-random value.
  4. BuildThe output is now a pure function of content; the two moving parts are held still.
  5. Rebuild and diffRegenerate from the same inputs and compare bytes — an empty diff is the proof.
Build yang dapat direproduksi: input yang identik ditambah sebuah timestamp yang dipatok dan sebuah seed /ID yang dipatok menghasilkan byte yang sama, yang dikonfirmasi oleh sebuah langkah bangun-ulang-dan-diff.

Sebuah bentuk yang kecil dan lengkap. Settings dikonstruksi sekali dan digunakan ulang, sehingga dua kali menjalankan program yang sama memancarkan berkas yang sama.

<?php
declare(strict_types=1);
use NextPDF\Core\DeterministicSettings;
use NextPDF\Exception\InvalidConfigException;
// One fixed instant for every date field — never the wall clock.
$timestamp = new DateTimeImmutable('2026-01-01T00:00:00+00:00');
// A 32-character hex seed pins the trailer /ID. Same seed, same /ID.
$fileIdSeed = '0123456789abcdef0123456789abcdef';
try {
$deterministic = new DeterministicSettings(
timestamp: $timestamp,
fileIdSeed: $fileIdSeed,
);
} catch (InvalidConfigException $e) {
// A malformed seed (not exactly 32 hex chars) is refused here,
// before any document is built — not silently coerced.
error_log($e->getMessage());
throw $e;
}
// Hand $deterministic to the document configuration. With both moving
// parts pinned, building the same content twice yields identical bytes:
//
// sha256(build_one) === sha256(build_two)

Seed adalah sebuah input build yang Anda kendalikan, bukan sebuah rahasia. Simpan ia di samping sisa konfigurasi build Anda. Intinya adalah ia tetap, sehingga file identifier yang ia hasilkan juga tetap.

Jebakan pertama adalah “Saya menghapus timestamp-nya, jadi build saya sekarang dapat direproduksi.” Biasanya tidak, karena array /ID adalah yang lebih senyap dari kedua sumber itu. Tanggal terlihat di sebuah panel metadata dan mudah diingat; trailer /ID tidak terlihat oleh sebagian besar pembaca dan dibangkitkan ulang dari jam dan sebuah sumber acak di setiap jalankan. Sebuah build yang hanya mematok tanggal tetap menghasilkan sebuah berkas yang berbeda setiap kali. Anda harus menahan keduanya tetap diam.

Jebakan kedua adalah memperlakukan determinisme sebagai sebuah fitur keamanan dengan sendirinya. Sebuah /ID yang dipatok membuat sebuah berkas dapat direproduksi; ia tidak membuatnya ditandatangani, dan ia tidak dengan sendirinya membuktikan bahwa dua build cocok. Sebuah perbandingan byte-demi-byte atau sebuah hash membuktikan build-nya cocok; mematok /ID hanya menghilangkan satu sumber perbedaan palsu. Dan tidak satu pun dari itu membuktikan bahwa sebuah pihak ketiga menjamin berkasnya. Reproduksibilitas dan penandatanganan adalah lapisan yang saling melengkapi, bukan pengganti.

Determinisme mematok bagian-bagian bergerak mesin itu sendiri. Ia tidak mematok input Anda. Jika konten Anda menyematkan sebuah timestamp langsung, menarik sebuah font yang berubah di disk, atau merender sebuah nilai yang bergantung pada tanggal saat ini, keluarannya berubah karena input-nya berubah — dan itu benar. DeterministicSettings menghilangkan non-determinisme mesin, bukan non-determinisme Anda. Sebuah build yang dapat direproduksi tetap memerlukan input yang dapat direproduksi.

Deterministic byte-identical output — edition availability
EditionAvailability
Core

Dukungan penuh. DeterministicSettings dikirim dalam core open-source: patok timestamp dan seed /ID dan konten yang sama dibangun ulang menjadi byte yang sama — tanpa gerbang edisi.

ProNot in this edition
EnterpriseNot in this edition
  • Golden-file testing — teknik CI yang bergantung pada keluaran yang identik per byte, dan mengapa sebuah mesin deterministik adalah prasyaratnya.
  • Incremental updates — bagaimana sebuah PDF tumbuh dengan menambahkan, di mana array /ID kembali penting untuk mengaitkan sebuah berkas dengan versi sebelumnya.
  • Metadata and the XMP packet — di mana tanggal tersemat hidup, dan bagaimana XMP packet mencerminkan document information dictionary.
  • The anatomy of a PDF file — trailer, cross-reference table, dan di mana array /ID berada dalam struktur berkas.
  • Byte-identical — dua berkas yang cocok persis, byte demi byte. Bentuk terkuat dari “yang sama”, dan yang dapat diverifikasi oleh sebuah hash atau sebuah diff.
  • /ID (file identifier) — array dari dua byte string yang mengidentifikasi sebuah PDF dan versi-versinya (ISO 32000-2 §14.4), disimpan di trailer dictionary (§7.5.5). Biasanya diturunkan dari jam ditambah byte acak, itulah sebabnya ia berubah di setiap build yang tidak dipatok.
  • Document information dictionary — struktur yang membawa CreationDate dan ModDate (ISO 32000-2 §14.3.3). Salah satu dari dua sumber non-determinisme yang harus dipatok oleh sebuah build deterministik.
  • Golden file — sebuah keluaran yang diketahui-baik yang direkam yang dibandingkan oleh sebuah tes; bermakna hanya ketika sebuah mesin yang tak berubah mereproduksi sebuah berkas yang tak berubah.
  • Reproducible build — sebuah build yang keluarannya adalah sebuah fungsi deterministik dari input-nya, sehingga membangun ulang dari input yang sama menghasilkan byte yang sama. Istilah ini berasal dari proyek Reproducible Builds untuk perangkat lunak terkompilasi.