Artikel Teknis

Tanda Tangan Digital PDF dan PAdES di Delphi dengan HotPDF

Sebuah signature PDF sebagian besar adalah soal akuntansi byte, dan di situlah letak masalahnya bisa muncul. Kriptografinya berjalan di atas kode yang sudah diaudit selama dua dekade, dan bagian itu hampir tidak pernah gagal. Yang gagal di produksi justru lebih sederhana: sebuah placeholder yang dicadangkan terlalu kecil untuk signature yang sebenarnya, hash yang diambil dari rentang file yang keliru, atau sebuah "save" setelah penandatanganan yang diam-diam menulis ulang byte yang sudah dibekukan oleh signature tersebut. Susun byte-nya dengan benar dan tanda centang hijau akan mengurus dirinya sendiri

HotPDF mencakup penandatanganan untuk Delphi dan C++Builder pada tiga tingkat, dan Anda memilih di antaranya dengan menjawab satu pertanyaan: di mana private key itu berada? File PFX di disk hanya membutuhkan satu pemanggilan fungsi. Key yang terkunci di dalam HSM atau layanan penandatanganan jarak jauh membutuhkan urutan reserve-hash-insert, karena tidak ada library yang bisa menjangkau ke dalam token dan menarik key keluar. Signature yang harus memenuhi regulasi Eropa membutuhkan struktur baseline PAdES di atas semua itu. Bagian-bagian di bawah ini mengikuti urutan tersebut

Diagram keputusan yang memilih antara penandatanganan sekali-panggil PFX HotPDF, jalur cadang-hash-sisip ketika kunci berada di HSM atau layanan jauh, dan struktur baseline PAdES untuk penandatanganan Eropa yang diatur regulasi
Pilih tier penandatanganan dengan menanyakan di mana private key berdomisili; file PFX yang terbaca menciutkan penandatanganan menjadi satu panggilan, sementara kunci yang dipegang token memaksa jalan memutar level byte dan regulasi Eropa menambahkan lapisan PAdES

Bagaimana /ByteRange mengunci byte yang ditandatangani

Sebuah signature harus berada di dalam file yang ditandatanganinya sendiri, dan ia tidak bisa menandatangani dirinya sendiri. PDF mengatasi paradoks ini dengan meninggalkan sebuah lubang. Sebelum penandatanganan, penulis mencadangkan entry /Contents berukuran tetap yang penuh dengan angka nol dan mencatat sebuah array /ByteRange untuk dua rentang di kedua sisinya: semua yang sebelum lubang itu, semua yang sesudahnya. Penandatangan menghitung hash kedua rentang tersebut dan menulis blob CMS hasilnya ke dalam lubang itu sebagai heksadesimal. Jebakannya ada pada kata tetap. Anda harus menentukan ukuran lubang itu sebelum tahu seberapa besar signature yang selesai nanti, sehingga cadangan tersebut harus berupa perkiraan lebih yang meyakinkan. Delapan kilobyte biasanya cukup menampung sebuah detached CMS signature dengan rantai sertifikat pendek

HotPDF memisahkan kedua kasus ini menjadi dua pemanggilan, dan mengacaukan keduanya adalah kesalahan awal yang umum terjadi. AddSignatureField menempatkan sebuah field kosong yang terlihat untuk ditandatangani seseorang belakangan di dalam viewer. AddSignedSignatureField membuat field sekaligus mencadangkan lubang /Contents, dan inilah yang Anda inginkan setiap kali kode, bukan manusia, yang akan menyelesaikan signature tersebut. Berikan field kosong ke penandatangan eksternal dan tidak ada apa pun yang bisa diisinya

Jalur satu pemanggilan: menandatangani dari PFX

Ketika sertifikat dan private key-nya berada dalam sebuah file PFX/PKCS#12 yang bisa dibaca proses Anda, seluruh pipeline itu menyusut menjadi satu class function:

if THotPDF.SignPDFWithPFX('invoice-unsigned.pdf', 'invoice-signed.pdf',
    'company-cert.pfx', 'pfx-password') then
  Writeln('Signed: invoice-signed.pdf')
else
  raise Exception.Create('PFX signing failed');

Ketika ini gagal, jarang sekali PDF-nya yang bermasalah. PFX-nya yang bermasalah. HotPDF membaca container yang dilindungi dengan PBES2, yakni penurunan key PBKDF2 di atas AES-256-CBC. PFX yang diekspor oleh wizard sertifikat Windows versi lama, atau oleh OpenSSL sebelum versi 3.0, biasanya justru dibungkus dengan RC2 atau 3DES lawas, dan itu sama sekali tidak bisa di-parsing. Perbaikannya adalah meng-ekspor ulang container tersebut sekali saja dengan proteksi modern; OpenSSL saat ini melakukannya secara default, dan itu bukan perubahan kode. Jadi ketika penandatanganan langsung gagal pada sertifikat yang "berfungsi di mana saja," periksa dulu bagaimana PFX itu dibuat sebelum mencurigai kode Anda sendiri

Jalur reserve-hash-insert untuk HSM dan token

Jalur satu pemanggilan mengasumsikan proses Anda bisa membaca key sebagai sebuah file. Semakin sering hal itu tidak mungkin. Key berada di dalam HSM, di token USB, atau di balik API layanan penandatanganan, dan tidak ada cara bagi library untuk menjangkaunya secara langsung. HotPDF menangani hal ini dengan memecah penandatanganan menjadi langkah-langkah pada level byte: menulis dokumen placeholder, meminta library untuk rentang hash-nya, meneruskan input hash tersebut ke mana pun key itu berada, lalu menyisipkan kembali CMS yang dikembalikan ke dalam lubang tersebut

HotPDF: Pipeline cadang-hash-sisip empat langkah atas placeholder.pdf yang menunjukkan lubang /Contents yang dicadangkan di antara dua span ByteRange dan HSM yang menukar digest dengan heksa CMS
HotPDF mereservasi lubangnya dan melaporkan kedua span ByteRange, pemegang kunci Anda menandatanganinya di luar, dan CMS yang kembali disambung ulang byte demi byte tanpa menyentuh satu byte pun yang beku
var
  Doc: THotPDF;
  Fs: TFileStream;
  PdfBytes, HashInput, SigHex: AnsiString;
  R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
  // 1. Menulis dokumen dengan lubang /Contents yang dicadangkan
  Doc := THotPDF.Create(nil);
  try
    Doc.FileName := 'placeholder.pdf';
    Doc.BeginDoc;
    Doc.CurrentPage.AddSignedSignatureField('Sig1',
      Rect(50, 100, 350, 150), 8192, 'adbe.pkcs7.detached',
      'Contract approval', 'Boston, MA', 'legal@example.com');
    Doc.EndDoc;
  finally
    Doc.Free;
  end;

  // 2. Memuat byte yang tersimpan; offset yang dikembalikan berbasis 0
  Fs := TFileStream.Create('placeholder.pdf', fmOpenRead);
  try
    SetLength(PdfBytes, Fs.Size);
    Fs.ReadBuffer(PdfBytes[1], Fs.Size);
  finally
    Fs.Free;
  end;
  THotPDF.PreparePDFForSigning(PdfBytes, R1Start, R1Len, R2Start, R2Len,
    CStart, CLen);

  // 3. Hash kedua rentang dan tandatangani secara eksternal (HSM, token, service)
  HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
               Copy(PdfBytes, R2Start + 1, R2Len);
  SigHex := SignWithHsm(HashInput);  // integrasi Anda: mengembalikan CMS sebagai hex

  // 4. Menyisipkan signature ke dalam lubang yang dicadangkan
  THotPDF.InsertSignatureHex(PdfBytes, SigHex);
  Fs := TFileStream.Create('signed.pdf', fmCreate);
  try
    Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
  finally
    Fs.Free;
  end;
end;

Ada dua detail dalam urutan ini yang menyebabkan sebagian besar kegagalan yang tidak konsisten. Pertama, PreparePDFForSigning bekerja pada byte dari sebuah file yang sudah selesai. Placeholder harus ditulis dan disimpan secara lengkap sebelum offset-nya berarti apa pun; hitung offset itu terhadap sebuah stream yang masih dalam proses penyusunan dan hasilnya tidak akan cocok dengan byte yang akhirnya Anda hash. Kedua, ukuran cadangan, sekali lagi. 8192 byte yang Anda minta harus bisa menampung CMS akhir, dan sebuah signature yang membawa sertifikat perantara, atau yang dihiasi layanan dengan signed attribute, bisa saja melampaui ukuran itu. InsertSignatureHex tidak akan memperbesar lubang tersebut untuk memberi ruang lebih. Tandanya adalah sebuah pipeline yang berhasil menandatangani dengan satu sertifikat namun gagal dengan sertifikat berikutnya; obatnya adalah membuat ulang placeholder dengan cadangan yang diukur dari signature nyata yang dihasilkan oleh penandatangan sebenarnya, bukan hasil tebakan

Baseline PAdES, dan timestamp yang menjaga signature tetap hidup

Jika Anda menandatangani di bawah aturan Eropa, standar yang berlaku adalah ETSI EN 319 142-1, yang menyusun empat tingkat baseline PAdES. B-B adalah signature biasa. B-T menambahkan timestamp tepercaya yang membuktikan kapan signature itu dibuat. B-LT menanamkan materi validasi, sertifikat dan data revokasi, di dalam dokumen sehingga tetap bisa diperiksa bertahun-tahun kemudian. B-LTA menumpuk timestamp dokumen berkala di atasnya, sehingga bukti itu bertahan lebih lama daripada algoritma yang menjadi dasarnya. HotPDF menghasilkan struktur sisi dokumen untuk setiap tingkat:

HotPDF: Level baseline PAdES bertumpuk dari B-B melalui B-T dan B-LT ke B-LTA, dengan timeline pembaruan yang menunjukkan document timestamp berkala menjaga tanda tangan tetap dapat diverifikasi puluhan tahun kemudian
Setiap level menumpuk perlindungan baru di atas yang terakhir; B-LTA terus menerapkan ulang document timestamp agar buktinya hidup lebih lama daripada algoritma yang pertama membangunnya
// Field signature baseline PAdES (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
  'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
  'Contract approval', 'Boston, MA', 'legal@example.com');

// Timestamp dokumen: cadangan lebih besar untuk token TSA dan rantainya
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);

Cadangan 16384 byte pada timestamp itu memang disengaja. Sebuah timestamp authority mengembalikan token yang membawa rantai sertifikatnya sendiri, sehingga secara rutin membutuhkan ruang lebih besar daripada 8 KB yang cukup bagi signature biasa. Timestamp dokumen tersebut juga menjadi mesin di balik B-LTA: menandai ulang waktu (re-timestamping) sebuah signature yang diarsipkan setiap beberapa tahun, dengan algoritma yang masih mutakhir, itulah yang menjaga dokumen yang Anda tandatangani pada 2026 tetap dapat diverifikasi pada 2040

Sepatah kata tentang string reason, location, dan contact yang diterima kedua pemanggilan field itu: keduanya hanyalah metadata untuk kenyamanan, tidak lebih. HotPDF menyimpannya sebagai entry dictionary biasa dan melukiskannya ke dalam appearance signature yang terlihat, namun tidak ada validator yang memeriksanya terhadap apa pun. Isi string tersebut secara konsisten dari data workflow Anda, karena auditor memang membacanya, namun jangan pernah menganggapnya sebagai bukti. Klaim kriptografis sesungguhnya sepenuhnya berada di dalam CMS dan rantai sertifikatnya, dan seorang verifier sama sekali mengabaikan teks yang terlihat itu

Setelah ditandatangani, file hanya boleh bertambah

Begitu sebuah signature ada, byte di dalam rentangnya menjadi beku. Satu-satunya cara sah untuk mengubah file sesudahnya adalah incremental update menurut ISO 32000-1 §7.5.6, yang menambahkan objek baru dan yang berubah setelah byte asli lalu merangkai sebuah bagian cross-reference baru kembali ke sana. Dilakukan dengan cara itu, signature tetap valid untuk revisinya dan sebuah viewer melaporkan keadaan yang jujur: revisi yang ditandatangani tetap utuh, dokumen diperluas sesudahnya. Serialisasi ulang seluruh file justru menulis ulang rentang yang ditandatangani, yang menghancurkan signature bahkan ketika tidak ada yang terlihat berubah. Mekanisme revisi yang sama juga menjadi cara satu dokumen membawa beberapa signature: setiap signature baru mendarat di incremental update-nya sendiri, dan rentangnya mencakup semua yang ada sebelumnya, termasuk signature-signature sebelumnya. Mekanisme append-only ini, dan kapan aman untuk memadatkannya, dibahas di artikel tentang object stream dan incremental update

Ada dua batasan yang layak diingat saat Anda merancang. Mode output PDF/A milik HotPDF langsung menolak field signature, sehingga kesesuaian arsip dan signature yang tertanam harus dikirim sebagai file terpisah. Dan penandatanganan sama sekali tidak berbicara tentang kerahasiaan: ia membuktikan siapa yang membuat sebuah dokumen dan bahwa dokumen itu belum berubah sejak saat itu, namun siapa pun tetap bisa membacanya. Menyembunyikan isinya adalah pekerjaan terpisah, ditangani oleh enkripsi AES-256 dan kebijakan izin

Apa pun yang Anda bangun, uji dengan sesuatu selain kode yang menulis file itu sendiri. Buka hasilnya di panel signature Acrobat dan pastikan tiga hal: signature-nya valid, identitasnya berantai ke root yang diharapkan, dan panel melaporkan tidak ada perubahan sejak penandatanganan. Lalu balik satu byte di dalam rentang yang ditandatangani pada sebuah salinan sekali pakai dan pastikan panel sekarang menyatakan dokumen itu telah diubah. Sebuah pipeline penandatanganan yang belum pernah Anda saksikan menolak file yang dimanipulasi adalah pipeline yang verifikasinya belum benar-benar diuji

Ketiga tingkat penandatanganan ini tersedia dalam HotPDF Delphi Component untuk Delphi dan C++Builder; halaman produk menautkan referensi API signature secara menyeluruh