Bir PDF imzası çoğunlukla bayt muhasebesidir ve yanlış giden yer de bayt muhasebesidir. Kriptografi, yirmi yıldır denetlenen kod üzerinde çalışır ve o kısım neredeyse hiç başarısız olmaz. Üretimde başarısız olan şey daha mütevazıdır: gerçek imza için çok küçük ayrılmış bir yer tutucu, dosyanın yanlış bir aralığı üzerinden alınmış bir özet veya imzalamadan sonra imzanın çoktan dondurduğu baytları sessizce yeniden yazan bir "kaydet". Baytları doğru döşeyin, yeşil onay işareti kendi kendine hallolur
HotPDF, Delphi ve C++Builder için imzalamayı üç düzeyde kapsar ve aralarında seçim yapmak tek bir soruyu cevaplamaktan geçer: özel anahtar nerede yaşıyor? Diskteki bir PFX dosyası tek bir fonksiyon çağrısı gerektirir. Bir HSM'de veya uzak bir imzalama servisinde kilitli bir anahtar, ayır-özetle-ekle sırasını gerektirir, çünkü hiçbir kütüphane bir tokenin içine uzanıp anahtarı çekip çıkaramaz. Avrupa mevzuatını karşılamak zorunda olan bir imza, bunun üzerine PAdES temel düzey yapılarını gerektirir. Aşağıdaki bölümler bu ilerlemeyi izler
/ByteRange, imzalanan baytları nasıl sabitler
Bir imza, imzaladığı dosyanın içinde yaşamak zorundadır ve kendini imzalayamaz. PDF, bu paradoksu bir delik bırakarak aşar. İmzalamadan önce, yazıcı sıfırlarla dolu, sabit boyutlu bir /Contents girdisi ayırır ve deliğin iki yanındaki iki aralık için bir /ByteRange dizisi kaydeder: delikten önceki her şey, sonraki her şey. İmzalayıcı bu iki aralığı özetler ve sonuçtaki CMS blob'unu deliğe onaltılık olarak yazar. Tuzak sabit kelimesindedir. Bitmiş imzanın ne kadar büyük olacağını bilmeden o deliğin boyutuna bağlanırsınız, bu yüzden ayırma, kendinden emin bir aşırı tahmin olmak zorundadır. Sekiz kilobayt, kısa bir sertifika zincirine sahip ayrık bir CMS imzasını rahatça barındırır
HotPDF, iki durumu iki çağrıya böler ve bunları karıştırmak, sık görülen bir erken hatadır. AddSignatureField, bir kişinin daha sonra bir görüntüleyicide imzalaması için boş, görünür bir alan bırakır. AddSignedSignatureField ise alanı oluşturur ve /Contents deliğini ayırır; imzayı bir insan değil kod tamamlayacaksa istediğiniz olan budur. Harici bir imzalayıcıya boş bir alan verirseniz dolduracak hiçbir şeyi olmaz
Tek çağrılık yol: bir PFX'ten imzalama
Sertifika ve özel anahtarı, sürecinizin okuyabileceği bir PFX/PKCS#12 dosyasında oturduğunda, tüm işlem hattı bir sınıf fonksiyonuna indirgenir:
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');
Bu başarısız olduğunda, nadiren sorun PDF'tir. Sorun PFX'tir. HotPDF, PBES2 ile korunan konteynerleri okur; bu, AES-256-CBC üzerinden PBKDF2 anahtar türetmesi demektir. Daha eski bir Windows sertifika sihirbazı veya 3.0 öncesi OpenSSL tarafından dışa aktarılmış bir PFX, genellikle bunun yerine eski RC2 veya 3DES ile sarılmıştır ve basitçe ayrıştırılamaz. Çözüm, konteyneri bir kez modern korumayla yeniden dışa aktarmaktır; günümüzün OpenSSL'i bunu varsayılan olarak yapar ve bu bir kod değişikliği değildir. Bu yüzden imzalama "her yerde çalışan" bir sertifikada anında ölürse, kendi kodunuzdan şüphelenmeden önce PFX'in nasıl oluşturulduğuna bakın
HSM'ler ve tokenler için ayır-özetle-ekle yolu
Tek çağrılık yol, sürecinizin anahtarı bir dosya olarak okuyabileceğini varsayar. Giderek daha çok, okuyamıyor. Anahtar bir HSM'de, bir USB tokende veya bir imzalama servisinin API'sinin arkasında oturur ve hiçbir kütüphanenin ona doğrudan ulaşmasının bir yolu yoktur. HotPDF bunu, imzalamayı bayt düzeyinde adımlara bölerek ele alır: bir yer tutucu belge yazın, kütüphaneden özet aralıklarını isteyin, özet girdisini anahtarı tutan her neyse ona geçirin, ardından döndürülen CMS'yi deliğe geri ekleyin
var
Doc: THotPDF;
Fs: TFileStream;
PdfBytes, HashInput, SigHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
// 1. Belgeyi ayrılmış bir /Contents deliğiyle yazın
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. Kaydedilen baytları yükleyin; döndürülen ofsetler 0 tabanlıdır
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. Her iki aralığı da özetleyin ve harici olarak imzalayın (HSM, token, servis)
HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
Copy(PdfBytes, R2Start + 1, R2Len);
SigHex := SignWithHsm(HashInput); // kendi entegrasyonunuz: CMS'yi onaltılık olarak döndürür
// 4. İmzayı ayrılmış deliğe ekleyin
THotPDF.InsertSignatureHex(PdfBytes, SigHex);
Fs := TFileStream.Create('signed.pdf', fmCreate);
try
Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
finally
Fs.Free;
end;
end;
Bu dizideki iki ayrıntı, aralıklı başarısızlıkların çoğuna neden olur. Birincisi, PreparePDFForSigning'in bitmiş bir dosyanın baytları üzerinde çalışmasıdır. Ofsetlerin bir anlam ifade etmesi için yer tutucunun tam olarak yazılıp kaydedilmesi gerekir; onları hâlâ derlenmekte olan bir akışa karşı hesaplarsanız sonunda özetlediğiniz baytlarla hizalanmazlar. İkincisi, yine ayırma boyutudur. İstediğiniz 8192 bayt, nihai CMS'yi barındırmak zorundadır ve ara sertifikalar taşıyan veya bir servisin imzalı özniteliklerle süslediği bir imza bunu aşabilir. InsertSignatureHex, yer açmak için deliği büyütmez. Belirti, bir sertifikayla sorunsuz imzalayıp bir sonrakiyle başarısız olan bir işlem hattıdır; çare, yer tutucuyu tahmin edilen değil, gerçek imzalayıcının ürettiği gerçek bir imzadan ölçülen bir ayırmayla yeniden üretmektir
PAdES temel düzeyleri ve bir imzayı canlı tutan zaman damgaları
Avrupa kuralları altında imzalıyorsanız, devrede olan standart, dört PAdES temel düzeyini üst üste yığan ETSI EN 319 142-1'dir. B-B düz imzadır. B-T, ne zaman yapıldığını kanıtlayan güvenilir bir zaman damgası ekler. B-LT, doğrulama malzemesini, sertifikaları ve geçersiz kılma verisini, yıllar sonra bile kontrol edilebilmesi için belgenin içine gömer. B-LTA, üzerine periyodik belge zaman damgaları katmanlar; böylece kanıt, üzerine inşa edildiği algoritmalardan daha uzun yaşar. HotPDF, her düzey için belge tarafı yapıları yayar:
// PAdES temel düzey imza alanı (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
'Contract approval', 'Boston, MA', 'legal@example.com');
// Belge zaman damgası: TSA tokeni ve zinciri için daha büyük ayırma
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);
Zaman damgasındaki 16384 baytlık ayırma bilinçlidir. Bir zaman damgası otoritesi, kendi sertifika zincirini de peşinde sürükleyen bir token döndürür, bu yüzden rutin olarak düz bir imzanın memnun olduğu 8 KB'den daha fazla yere ihtiyaç duyar. Bu belge zaman damgaları aynı zamanda B-LTA'nın arkasındaki mekanizmadır: arşivlenmiş bir imzayı, hâlâ güncel olan algoritmalarla birkaç yılda bir yeniden zaman damgalamak, 2026'da imzaladığınız bir belgeyi 2040'ta doğrulanabilir tutan şeydir
Her iki alan çağrısının da kabul ettiği neden, konum ve iletişim dizeleri hakkında bir söz: bunlar kolaylık meta verisinden başka bir şey değildir. HotPDF onları düz sözlük girdileri olarak saklar ve görünür imza görünümüne boyar, ama hiçbir doğrulayıcı onları herhangi bir şeye karşı kontrol etmez. Denetçiler bunları gerçekten okuduğu için onları iş akışı verinizden tutarlı biçimde doldurun, ama sonra onları asla kanıtla karıştırmayın. Gerçek kriptografik iddia tamamen CMS'de ve sertifika zincirinde yaşar ve bir doğrulayıcı görünür metni tamamen görmezden gelir
İmzalamadan sonra, dosya yalnızca büyüyebilir
Bir imza var olduğu anda, aralıklarının içindeki baytlar donar. Dosyayı sonradan değiştirmenin tek meşru yolu, orijinal baytlardan sonra yeni ve değişmiş nesneleri ekleyen ve onlara geri taze bir çapraz referans bölümü zincirleyen bir ISO 32000-1 §7.5.6 artımlı güncellemesidir. Bu şekilde yapıldığında, imza kendi revizyonu için geçerli kalır ve bir görüntüleyici dürüst durumu bildirir: imzalanan revizyon bozulmamıştır, belge sonradan genişletilmiştir. Bunun yerine tüm dosyayı yeniden serileştirirseniz imzalanan aralıkları yeniden yazarsınız; bu, görünürde hiçbir şey değişmemiş olsa bile imzayı yok eder. Aynı revizyon mekanizması, bir belgenin birden çok imza taşımasının da yoludur: her yeni imza kendi artımlı güncellemesine düşer ve aralıkları, ondan önceki her şeyi, daha önceki imzalar dahil, kapsar. Yalnızca-ekleme mekaniği ve ne zaman onları sıkıştırmanın güvenli olduğu, nesne akışları ve artımlı güncellemeler makalesinde ele alınır
Tasarım yaparken akılda tutmaya değer iki sınır vardır. HotPDF'in PDF/A çıktı modu, imza alanlarını doğrudan reddeder, bu yüzden arşivsel uygunluk ve gömülü bir imzanın ayrı dosyalar olarak gönderilmesi gerekir. Ve imzalama gizlilik hakkında hiçbir şey söylemez: bir belgeyi kimin ürettiğini ve o zamandan beri değişmediğini kanıtlar, ama herkes onu yine de okuyabilir. İçeriği gizlemek, AES-256 şifreleme ve izin politikası tarafından ele alınan ayrı bir iştir
Ne inşa ederseniz edin, onu dosyayı yazan koddan başka bir şeyle test edin. Çıktıyı Acrobat'ın imza panelinde açın ve üç şeyi doğrulayın: imza geçerlidir, kimlik beklediğiniz köke zincirlenir ve panel imzalamadan bu yana hiçbir değişiklik bildirmez. Ardından bir kullan-at kopyanın imzalanan aralığı içinde tek bir baytı çevirin ve panelin artık belgeyi değiştirilmiş olarak nitelendirdiğini doğrulayın. Kurcalanmış bir dosyayı reddettiğini hiç izlemediğiniz bir imzalama işlem hattı, doğrulaması gerçekten test edilmemiş bir işlem hattıdır
Üç imzalama düzeyinin tamamı, Delphi ve C++Builder için HotPDF Delphi Component ile birlikte gelir; ürün sayfası eksiksiz imza API referansına bağlantı verir