PDFium Component menandatangani PDF dengan tanda tangan digital PAdES B-B melalui metode SignPades-nya: ia memuat dokumen, melakukan hash pada rentang byte yang ditandatangani, membangun struktur CAdES CMS, dan menambahkan tanda tangan sebagai pembaruan bertahap (incremental update). Backend kriptografi ini khusus Windows, jadi lindungi setiap panggilan dengan PadesCryptoAvailable sebelum Anda menandatangani
Skenario ini sudah sangat dikenal. Sebuah file PDF kontrak mendarat di meja Anda, bagian hukum menginginkannya ditandatangani secara digital sebelum dikirim keluar, dan Anda meraih build PDFium yang sama yang biasa Anda gunakan untuk merender dan memeriksa dokumen, hanya untuk menemukan bahwa PDFium tidak dapat menulis tanda tangan sama sekali. API tanda tangannya benar-benar hanya-baca (read-only). PDFium Component menutup celah tersebut dengan memiliki seluruh pipa penandatanganan di Pascal, mulai dari fungsi hash hingga penyuntikan tingkat byte, dan artikel ini membahas pipa tersebut dari ujung ke ujung
Mengapa PDFium tidak dapat menulis tanda tangan digital?
PDFium mengekspos tanda tangan sebagai objek hanya-baca dan tidak menawarkan apa pun untuk membuatnya. Keluarga FPDFSignatureObj_* memungkinkan Anda menghitung tanda tangan yang ada, membaca /Contents-nya, dan memeriksa /ByteRange-nya, tetapi tidak ada fungsi setara yang membangun kamus tanda tangan, memesan slot /Contents, atau menulis rentang byte; penyimpanan bertahap (incremental saving) memang ada (FPDF_SaveAsCopy dengan FPDF_INCREMENTAL) tetapi tidak membawa kait penandatanganan. Oleh karena itu, komponen apa pun yang menandatangani PDF di atas PDFium harus menghasilkan setiap byte tanda tangan itu sendiri, itulah sebabnya PDFium Component membangun mekanisme ini dari tiga unit Pascal murni. FPC 3.2.2 mengirimkan md5 dan sha1 tetapi tidak memiliki SHA-2 sama sekali, dan API SHA-256 System.Hash milik Delphi tidak kompatibel secara sumber dengan FPC, sehingga FPdfSha256 adalah implementasi FIPS 180-4 mandiri yang menjaga setiap jalur kode CMS pada satu tipe TSHA256Digest tanpa percabangan kompiler. FPdfAsn1 menyediakan DER encoder dan pembaca yang dibutuhkan struktur CMS, dan FPdfCms menyusun CAdES SignedData di atas keduanya
Bagaimana cara menandatangani PDF secara digital di Delphi?
Muat dokumen, lalu panggil SignPades dengan sidik jari sertifikat (certificate thumbprint). PDFium Component menyelesaikan sidik jari tersebut terhadap penyimpanan sertifikat "MY" Pengguna Saat Ini (Current User), menarik sertifikat yang cocok beserta kunci privatnya, dan menulis salinan bertanda tangan ke jalur yang Anda tentukan
uses
PDFium, FPdfCrypto;
procedure SignContract(const AThumbprint: string);
var
Pdf: TPdf;
begin
if not PadesCryptoAvailable then
raise Exception.Create('PAdES signing requires the Windows CNG backend');
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf'; // the document to sign
Pdf.Active := True;
// Second argument: SHA-1 thumbprint of a certificate in the Current
// User "MY" store. First argument: destination for the signed copy.
if not Pdf.SignPades('contract-signed.pdf', AThumbprint) then
raise Exception.Create('Signing failed');
finally
Pdf.Free;
end;
end;
Backend Windows CNG dan batasannya
PadesCryptoAvailable adalah pemeriksaan yang Anda lakukan pertama kali, setiap saat. Di Windows, ia mengembalikan True dan backend crypt32/ncrypt aktif; di platform lain ia mengembalikan False dan panggilan penandatanganan akan memunculkan EPadesCrypto. Memperlakukan pemeriksaan ini sebagai hal wajib mencegah kegagalan build Linux atau macOS saat dijalankan pada jalur yang tidak dapat berfungsi di sana. Sidik jari sertifikat itu sendiri adalah hash SHA-1 dari sertifikat, nilai yang sama yang ditampilkan pengelola sertifikat Windows pada tab Detailnya, dan ia menamai penandatangan tertentu tanpa pernah menempatkan materi kunci di dalam kode sumber Anda
Apa yang masuk ke dalam CMS: atribut bertanda tangan dan RFC 5652
Tanda tangan tingkat dasar PAdES bukanlah tanda tangan RSA mentah di atas file; ia merupakan struktur CAdES CMS SignedData yang membawa satu set atribut bertanda tangan wajib, dan FPdfCms.BuildSignedData memancarkan set tersebut secara tepat: content-type, message-digest, dan signing-certificate-v2, atribut ESS yang mengikat tanda tangan ke sertifikat penandatangan berdasarkan hash. Satu detail di sini mengalahkan hampir setiap implementasi CMS buatan sendiri. RFC 5652 §5.4 mengharuskan digest dari signed-attributes dihitung atas pengkodean DER SET OF, tag 0x31, sedangkan atribut yang sama ditransfer di dalam SignerInfo di bawah tag IMPLICIT [0], 0xA0. PDFium Component mengkodekan set atribut sekali, mencerna bentuk 0x31, lalu menulis ulang hanya byte tag utama menjadi 0xA0 untuk pancaran, sehingga satu buffer melayani kedua peran tanpa lintasan kedua atas pohon data
var
Pdf: TPdf;
Opts: TPadesSignOptions;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := 'a1b2c3d4e5f6...'; // signer in the MY store
Opts.Reason := 'I approve this agreement';
Opts.Location := 'Berlin, DE';
Opts.ContentsSize := 16384; // hex width of /Contents
if not Pdf.SignPades('contract-signed.pdf', Opts) then
raise Exception.Create('Signing failed');
finally
Pdf.Free;
end;
end;
Kelebihan beban opsi menambahkan metadata kamus tanda tangan yang ditentukan ISO 32000-1 §12.8.1: Reason, Location, ContactInfo, dan Name, semuanya bersifat opsional dan semuanya ditulis ke dalam kamus nilai tanda tangan. Satu batasan yang mudah membuat orang keliru. Jika Anda menyetel CommitmentTypeOid to add a CAdES commitment-type-indication signed attribute, do not also set Reason; ETSI EN 319 142-1 §6.3 melarang membawa keduanya, karena keduanya mengekspos maksud yang sama dengan cara yang berbeda
Bagaimana ByteRange dan slot /Contents cocok satu sama lain?
Tanda tangan harus mencakup seluruh file kecuali byte yang menampung tanda tangan itu sendiri, dan PAdES menyelesaikan kemelingkaran tersebut dengan placeholder dengan lebar tetap yang dikelola SignPadesBytes secara tepat. Ia memesan string heksadesimal /Contents sebesar ContentsSize byte (bawaan 16384, jauh lebih besar dari CMS SignedData pada umumnya), menserialisasikan pembaruan bertahap untuk menemukan offset slot yang tepat, lalu menghitung /ByteRange sebagai dua rentang yang mengapit slot, yaitu semua data sebelum pembatas pembuka string heksadesimal dan semua data setelah pembatas penutupnya. SHA-256 berjalan di atas kedua rentang itu saja. CMS yang selesai dikodekan heksadesimal ke dalam slot yang dipesan, diberi bantalan nol (zero-padded) hingga lebar tetap, dan pembaruan referensi silang ditambahkan. Karena lebarnya ditentukan di awal, pengisian slot tidak menggeser byte hilir apa pun, yang merupakan alasan utama mengapa rentang byte tetap valid; byte dokumen asli dipertahankan apa adanya, sehingga tanda tangan sebelumnya pada file yang sama bertahan utuh persis seperti yang disyaratkan oleh penandatanganan bertahap ISO 32000-1 §12.8.1
Backend Windows CNG dan batasannya
PDFium Component hanya menandatangani di Windows, dan batasan itu disengaja. FPdfCryptoWin mengikat crypt32.dll dan ncrypt.dll secara dinamis, tanpa menambahkan dependensi DLL pada waktu kompilasi, dan rantai penandatanganan menggunakan CNG standar: buka penyimpanan "MY", cari sertifikat berdasarkan hash, dapatkan handel kunci privatnya melalui CryptAcquireCertificatePrivateKey, dan panggil NCryptSignHash. RSA dengan PKCS#1 v1.5, RSA-PSS, and ECDSA are all supported. ECDSA memerlukan satu perbaikan yang tidak diperlukan oleh yang lain, karena NCryptSignHash mengembalikan pasangan r-and-s IEEE P1363 mentah sedangkan CMS mengharapkan SEQUENCE DER ECDSA-Sig-Value, sehingga backend mengodekan ulangnya sesuai RFC 5480
var
Pdf: TPdf;
Opts: TPadesSignOptions;
Output: TFileStream;
begin
if not PadesCryptoAvailable then
Exit; // no signing backend on this platform
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := ReadThumbprintFromConfig;
Pdf := TPdf.Create(nil);
Output := TFileStream.Create('contract-signed.pdf', fmCreate);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Pdf.SignPadesToStream(Output, Opts);
finally
Output.Free;
Pdf.Free;
end;
end;
Konsekuensi praktisnya adalah kunci privat harus berada di penyimpanan sertifikat Windows. Sertifikat yang disimpan dalam file PFX hanya berfungsi setelah Anda mengimpornya ke penyimpanan Pengguna Saat Ini (Current User), di mana sidik jarinya adalah nilai yang Anda berikan ke SignPades. Rilis ini tidak memiliki jalur PKCS#11 atau HSM dan tidak memiliki backend file kunci perangkat lunak (software-key file backend), jadi ketika PadesCryptoAvailable mengembalikan False, penandatanganan tidak dapat dilakukan pada mesin tersebut
Di mana batas PAdES B-B
PAdES B-B adalah tingkat dasar, lantai dari keempat tingkat PAdES: ia membuktikan siapa yang menandatanganinya dan bahwa byte data tidak berubah sejak saat itu, dan tidak lebih dari itu. Tanda tangan B-B tidak membawa stempel waktu tepercaya, sehingga tidak dapat membuktikan kapan penandatanganan terjadi, dan ia tidak menyematkan data pencabutan, sehingga verifikator bertahun-tahun kemudian harus mengambil sendiri rantai sertifikat dan statusnya. Celah itulah yang ditutup oleh tingkat yang lebih tinggi. Ketika Anda memerlukan waktu penandatanganan yang dapat diterima auditor, menambahkan stempel waktu RFC 3161 dan DSS untuk validasi jangka panjang memindahkan tanda tangan to B-T and above; ketika you want to read a finished signature back and confirm which level it reached, inspecting a PDF signature and its PAdES level is the companion tool; and before signing anything at all, auditing a PDF for security risks tells you what you are about to put your name on
Metode SignPades yang ditunjukkan di sini didistribusikan bersama PDFium Component untuk Delphi dan C++Builder, di samping pemeriksaan tanda tangan hanya-baca yang disediakan oleh PDFium secara langsung