Sertifika ile şifreli bir PDF, dosyanın kime yazıldığından bağımsız olarak şifreleme sözlüğünde tek bir /P değeri taşır; bu yazıda sonra kurulan dosyalarda o değer -64'tür. Üç farklı izin setine sahip üç alıcı aynı tek sayıyı paylaşır. "Bu kullanıcı yazdırabilir mi?" sorusunu /P'den yanıtlayan kod bir yer tutucuyu okuyor demektir; çünkü public-key security handler altında gerçek izin sözcüğü her alıcının şifreli zarfının içinde seyahat eder ve yalnızca o alıcının özel anahtarı zarfı açtığında görünür
O tasarım, sertifika şifrelemesinin zahmete değmesinin nedenidir. Üç aylık denetim paketi üç dış incelemeciye gider; hukuk her birinin onu okumasını ister, yalnızca biri yazdırabilsin ister ve hiç kimse ekin yanındaki e-posta zincirinde bir parola beklemesin ister. Parola bunu ifade edemez. Sertifikalar eder: her alıcı dosyayı hâlihazırda elinde tuttuğu bir anahtarla açar ve her zarf kendi izin sözcüğünü taşır
Sertifika temelli PDF şifrelemesi paroladan nasıl ayrılır?
Public-key ile şifreli bir PDF, dosya anahtarını rastgele bir seed artı her alıcı zarfının tam baytlarından türetir; bir insanın yazdığı hiçbir şey hesaba girmez. Handler ISO 32000-1 §7.6.4'te (ISO 32000-2'de §7.6.5) tanımlanır ve zarflar RFC 5652'den CMS EnvelopedData yapılarıdır. HotPDF, /SubFilter /adbe.pkcs7.s5 ile /Filter /Adobe.PubSec yazar. AES-256 için bu, /V 5, /CF altında /CFM /AESV3'lü bir /DefaultCryptFilter girdisi ve üst düzey sözlük yerine o crypt filter'ın içine yerleştirilen /Recipients dizisi demektir
Her zarf 24 baytı şifreler: 20 baytlık seed ve ardından o alıcının 32 bitlik izin sözcüğü. Yükleme sırasında okuyucu bir zarfı açar, seedi geri kazanır ve dosya anahtarını yeniden kurmak için seedi /Recipients sırasındaki her zarfla birlikte hash eder (AES-256 için SHA-256, eski cipherlar için SHA-1). İncelemeci B, incelemeci A'nın ne yapabildiğini asla öğrenmez; ama B'nin okuyucusu yine de A'nın zarf baytlarını hash'ler, yani tüm dizi baytı baytına hayatta kalmalıdır. Bu model ile sıradan parolalar arasında hâlâ karar veriyorsanız, AES-256 parola şifrelemesi ve izin bayrakları rehberi o takasın öteki yüzünü kapsar
EnablePubKeyEncryption ile RSA alıcıları yazmak
RSA sertifikaları için sıra kısadır: aes256 ile EnablePubKeyEncryption, sonra BeginDoctan önce DER kodlu her sertifika için bir AddPubKeyRecipientCertificate çağrısı. Yardımcı RSAES-OAEP zarfını işlem içinde kurar. Son iki parametresi OAEP özeti ile MGF1 özetini THPDFRSAOAEPHash'den seçer (rohSHA256, rohSHA384 ya da rohSHA512); ikisi de varsayılan olarak rohSHA256'dır ve zarf içeriği AES-256-CBC ile şifrelenir
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFCrypt, HPDFRSA;
procedure WriteAuditPack(const OutFile: string);
var
Pdf: THotPDF;
Seed: AnsiString;
begin
SetLength(Seed, 20); // tam 20 bayt, AES-256 için bile
AESGenerateRandomBytes(@Seed[1], Length(Seed));
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := OutFile;
Pdf.EnablePubKeyEncryption(Seed, aes256, True); // varsayılan anahtar türü aes128
// İncelemeci A yazdırabilir; incelemeci B yalnızca okur ve çıkarır
Pdf.AddPubKeyRecipientCertificate(TFile.ReadAllBytes('reviewer-a.cer'),
[prPrint, prPrint12bit, prExtractContent], rohSHA256, rohSHA256);
Pdf.AddPubKeyRecipientCertificate(TFile.ReadAllBytes('reviewer-b.cer'),
[prExtractContent]);
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 720, 0, 'Q3 audit pack');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
O listelemede üç ayrıntı yük taşır. Seed her anahtar türü için, AES-256 dâhil, 20 bayttır ve EnablePubKeyEncryption başka her uzunlukta fırlatır. Anahtar türünün varsayılanı aes128 iken her iki sertifika yardımcısı aes256 üzerinde ısrar eder; ikinci argümanı düşürmek, ilk AddPubKeyRecipientCertificate çağrısını "certificate envelopes require aes256" mesajıyla fırlatır. Eski cipherlar (k40, k128, aes128), başka araç takımlarının kurduğu eksiksiz bir DER zarfı alan AddPubKeyRecipient üzerinden hâlâ erişilebilirdir
Üçüncü ayrıntı sürümdür. Public-key handler altında AES-256 bir PDF 2.0 özelliğidir ve HotPDF belge sürümünü kendi başına 2.0'a çeker. Daha düşük bir sürümde StrictVersionLock set edildiğinde o yükseltmeye izin verilmez: EnablePubKeyEncryption, hiçbir şeyi etkinleştirmeden döner ve hata bir satır sonra, alıcı yardımcısı "call EnablePubKeyEncryption first" raporladığında görünür. O mesaj insanları bir çağrı sırası hatası aramaya yollar; sürüm kilidini denetleyin. Artımlı bir güncelleme sırasında şifrelemeyi değiştirmek ise tümüyle reddedilir, EInvalidOpException ile
ECDH alıcıları eklemek: P-256, P-384, P-521, X25519 ve X448
Eliptik eğri sertifikaları AddPubKeyAgreementRecipientWithSecretten geçer; o çağrı bir CMS key-agreement alıcısı yazar (RFC 5753'ten KeyAgreeRecipientInfo, RFC 8418'in X25519 ve X448 profiliyle) ve ECDH paylaşılan sırrını işlem içinde hesaplar. Eğriyi bir THPDFPubKeyAgreementScheme değeri adlandırır: pkasECDHP256, pkasECDHP384, pkasECDHP521, pkasX25519 ya da pkasX448. Şema, sertifikanın anahtar algoritmasıyla, eğrisiyle ve anahtar uzunluğuyla eşleşmek zorundadır; aksi hâlde çağrı "Certificate key does not match the requested agreement scheme" fırlatır
Her zarf taze rastgele 32 baytlık bir UKM alır, stdDH KDF'ten bir anahtar şifreleme anahtarı (P-256 ile X25519 için SHA-256, P-384 için SHA-384, P-521 ile X448 için SHA-512) ve RFC 3394'te tanımlanan bir AES-256 key wrap alır. Eğri aritmetiği altında platform kripto sağlayıcısı olmayan düz Pascal'dır; saf Pascal NIST eğri aritmetiği yazısı o katmanın nasıl kurulduğunu gezdirir. Montgomery eğrilerinde tüm geçici anahtar çifti yerelde üretilebilir:
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFCrypt, HPDFPubSec,
HPDFKeyAgreement;
procedure AddLegalRecipient(Pdf: THotPDF);
var
Scalar, OriginatorPublic: TBytes;
begin
// Zarf başına taze geçici skaler; clamping merdivenin içinde olur
SetLength(Scalar, 32);
AESGenerateRandomBytes(@Scalar[0], Length(Scalar));
try
OriginatorPublic := HPDFX25519PublicFromScalar(Scalar);
Pdf.AddPubKeyAgreementRecipientWithSecret(
TFile.ReadAllBytes('legal-x25519.cer'),
[prPrint, prExtractContent], pkasX25519,
OriginatorPublic, Scalar,
[]); // OwnPublicPoint: yalnızca NIST eğrileri için anlamlı
finally
HPDFSecureClearBytes(Scalar);
end;
end;
NIST eğrileri çağırandan daha fazlasını ister. HotPDF başlatıcı public anahtarlarını yalnızca X25519 ve X448 için türetir (HPDFX25519PublicFromScalar, HPDFX448PublicFromScalar); P-256, P-384 ve P-521 için geçici çift kendi araçlarınızdan gelir. Tam olarak alan boyutunda (32, 48 ya da 66 bayt) big-endian bir skaler ve eşleşen sıkıştırılmamış 0x04||X||Y noktasını (65, 97 ya da 133 bayt) OriginatorPublicKey olarak geçirin ve aynı noktayı NIST şemaları için ayrılmış OwnPublicPoint parametresine verin. Yanlış uzunluklar anında fırlatır. Sertifikadan gelen alıcı noktası, herhangi bir çarpma koşmadan önce eğri denklemine karşı denetlenir
Hiçbir kütüphanenin denetleyemeyeceği şey, başlatıcı public anahtarınızın skalerinize ait olup olmadığıdır. Uyuşmayan yarımlar yine de iyi biçimli bir zarf üretir. Yazıcı, anahtarını skaler × alıcı noktasından türetir; alıcı, anahtarını kendi özel anahtarı × yanlış başlatıcı noktasından türetir ve alıcı tarafındaki RFC 3394 bütünlük denetimi fark eden ilk şey olur, o zamana kadar dosya yollanmıştır. Aynı eğri üzerindeki bir test anahtarıyla gidiş-dönüşlü bir yükleme derlemenize girsin; başarılı bir EndDoc ve makul bir dosya boyutu burada hiçbir şey kanıtlamaz
O eşleştirme adımı, eğri seçiminin pratikte neden önemli olduğunun ta kendisidir. Sertifika ihraçlarını siz yönetiyorsanız X25519, Delphi'den doğru kurması daha kolay eğridir: tek bir 32 baytlık rastgele skaler, tek bir yardımcı çağrısı, elle senkronda tutulacak bir nokta yok. NIST eğrileri, alıcı sertifikalarının yalnızca P-256 ya da P-384 ihraç eden bir PKI'dan geldiği zaman yerini hak eder; o durumda gidiş-dönüşlü yükleme isteğe bağlı olmaktan çıkar
/Recipients sırası neden önemlidir?
/Recipients'in sırası önemlidir, çünkü dosya anahtarı, seedin ardından diziliş sırasındaki her zarfın geldiği bir özettir; yazan ile okuyan özdeş baytları özdeş sırada hash etmek zorundadır. HotPDF zarfları eklediğiniz sırada tutar ve değiştirmeden yazar; yani sırayı seçmekte serbestsiniz. Riskli olan, sonrasında dizinin başına gelen şeydir. İşleme sonrası araçları, imzalama hatları ve ev yapımı zarf kurucuları kırılmanın yaşadığı yerlerdir ve başarısızlıklar tek biçimlidir — iki taraf biraz farklı baytları hash ediyor:
- Delphi'nin tiplenmemiş
TList'i ham bir işaretçi saklar. ZarflarıList.Add(Pointer(Bytes))ile toplamak, referans sayısını yerel değişkende bırakır; o değişkendeki sonrakiSetLengthtamponu serbest bırakır ya da yeniden kullanır ve her yuva son zarfla özdeşleşebilir.TList<TBytes>ya da düz birTArray<TBytes>referansları sayılı tutar - Yerinde çalışan DER ayrıştırıcıları, ayrıştırdıkları tamponu değiştirir. Her zarfı, hiçbir açma denemesi dokunmadan önce alınmış tertemiz bir kopyasından hash edin
- Bir Unicode
TStringListikili DER için hiçbir yer değildir:$80ve üzeri baytlar, girişte kod sayfası tarafından yeniden kodlanır. ZarflarıTBytes'te tutun ya da önce hex kodlayın - Ciphertext, hex dizgelere aittir. Bir literal dizge içinde CR, LF ve CRLF tek bir LF olarak geri okunur (ISO 32000-1 §7.3.4.2); tesadüfen
$0D $0Ataşıyan bir zarf bir bayt kaybeder. HotPDF her/Recipientsgirdisini hex dizge olarak yazar ve onu dizge şifrelemesinin dışında tutar; çünkü okuyucu herhangi bir anahtar elde etmeden önce zarflara ihtiyaç duyar - Bir DER
BIT STRING'in ilk içerik baytı kullanılmayan bitleri sayar ve bayt hizalı anahtarlar için$00olmak zorundadır. O baytı düşürüp0x04||X||Ynoktasını doğrudan içeriğe koyan elle yazılmış DER, tüm anahtarı bir bayt kaydırır ve katı bir ayrıştırıcı başlatıcı anahtarını tümüyle reddeder - Çalışması gereken bir anahtar hâlâ çözemyorsa katman katman karşılaştırın, bu sırada: dosya anahtarı, ciphertext'in önündeki IV, nesne başına anahtar, düz metin. Hata, anlaşmazlığa düşen ilk katmanın hemen ardındadır
Sertifika ile şifreli bir PDF'i özel anahtarlarla yüklemek
Özel anahtarlar LoadFromFile koşmadan önce kaydedilmiş olmalıdır; çünkü HotPDF dosya anahtarını yapısal geçiş sırasında geri kazanır. HPDFParsePFX ile ayrıştırılan RSA kimlikleri PubSecKeyMaterial'e gider, ek RSA anahtarları AddPubSecKeyMaterial'e; o yardımcı, modulus ve özel üs taşımayan malzemeyi reddeder. İki anahtar yuvası da RSA modulusu ve özel üssü üzerinden eşlenir; yani EC anahtarları, PFX tabanlılar dâhil, üçüncü bir yolu izler: AddPubSecAgreementKeyMaterial(CurveOID, PrivateScalar, OwnPublicPoint); HPDFOIDX25519, HPDFOIDX448, HPDFOIDECP256, HPDFOIDECP384 ya da HPDFOIDECP521 sabitlerinden biriyle. NIST eğrileri orada alıcının kendi sıkıştırılmamış public noktasını ister; Montgomery eğrileri onu yok sayar
PFX dosyası olarak gelen bir EC kimliği aynı çağrıya oturur. HPDFParsePFX onu KeyAlgorithm = pfxkaECDSA ile rapor eder (X25519 ve X448 kimlikleri dâhil) ve CurveOID'yi, PrivateKey'teki özel skaleri ve PublicKey'teki sertifika noktasını doldurur; üçü de doğrudan üç parametreye eşlenir
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFPFX, HPDFKeyAgreement;
procedure OpenAuditPack(const LegalScalar: TBytes);
var
Reader: THotPDF;
EcKey: THPDFPFXKeyMaterial;
begin
Reader := THotPDF.Create(nil);
try
Reader.AutoLaunch := False;
// RSA kimliği birincil anahtar olur
Reader.PubSecKeyMaterial :=
HPDFParsePFX(TFile.ReadAllBytes('reviewer-a.pfx'), 'pfx-password');
// Bir PFX'ten EC kimliği: eğri, skaler ve nokta anlaşma listesine gider
EcKey := HPDFParsePFX(TFile.ReadAllBytes('reviewer-c-p256.pfx'), 'pfx-password');
if EcKey.KeyAlgorithm = pfxkaECDSA then
Reader.AddPubSecAgreementKeyMaterial(EcKey.CurveOID,
EcKey.PrivateKey, EcKey.PublicKey);
// Hukuk ekibinin tuttuğu ham X25519 skaleri; nokta gerekmez
Reader.AddPubSecAgreementKeyMaterial(HPDFOIDX25519, LegalScalar, nil);
Reader.LoadFromFile('audit-pack.pdf', '');
Writeln('Pages: ', Reader.GetLoadedPageCount);
finally
Reader.Free;
end;
end;
Kendi haline bırakılırsa HotPDF her zarfı her kayıtlı anahtara karşı dener: birincil RSA anahtarı, sonra her ek RSA anahtarı, sonra EC malzemesi. PubSecRecipientQuery bunu tek bir zarfa daraltır. Callback zarf sayısını alır ve dizi içinde sıfır tabanlı bir indeks döndürmek zorundadır; öteki her değer, -1 dâhil, yükleme sırasında fırlatır. Her zarfı denemek istiyorsanız özelliği atamasız bırakın. Delphi altında tür, bir fonksiyona referanstır ve anonim bir metot iş görür; FPC altında ise düz bir fonksiyon işaretçisidir ve Context argümanı PubSecRecipientQueryContext'ten gelir
Bir davranış kendi testini hak eder. Hiçbir kayıtlı anahtar hiçbir zarfı açamadığında, anahtar geri kazanımı bir dosya anahtarı olmadan biter ve o yüzden fırlamaz. Belgenin taşıdığını bildiğiniz içeriğe, bir metin dizgesine ya da bir metadata değerine iddia kurun; normal dönen bir LoadFromFile çağrısı yalnızca dosya yapısının ayrıştığını gösterir
Hedef görüntüleyicide neyi denetlemelisiniz?
HotPDF'in yazıcısı ile okuyucusu bayt bayt anlaşır ve zarflar yukarıda anılan CMS yapılarını izler. Üçüncü taraf görüntüleyiciler ayrı bir sorudur: RSA-OAEP anahtar taşıması için, üstelik X25519 ya da X448 alıcılar için, destek okuyucudan okuyucuya ve aynı okuyucunun sürümleri arasında değişir. Belge belirli bir görüntüleyicide açılmak zorundaysa, aynı anahtar türünden bir test sertifikası için bir test dosyası şifreleyin ve bir şemaya bağlanmadan önce orada açın. Görüntüleyici sizin kontrolünüzün dışındaysa destek listesi şemayı seçer ve o, daha önce söylenen X25519 kolaylık argümanını geçersiz kılar
Zarf izinleri, kurallara uyan yazılımın uyduğu politika olarak kalır; parola şifrelemesi altında oldukları gibi. Seed kalitesi size aittir: AESGenerateRandomBytes o iş için oradadır ve HotPDF, dosya anahtarı türetildikten sonra seedin kendi kopyasını siler. Bir dizgenin, akışın ya da ekin farklı bir crypt filter'a ihtiyacı varsa StmF, StrF ve EFF için crypt filter politika rehberi nasıl atandığını gösterir; public-key handler DefaultCryptFilter'ı ya da Identity'yi kabul eder
RSA ve eliptik eğri sertifikaları için alıcı başına zarflar, geri yolundaki anahtar yüklemesiyle birlikte, parola şifrelemesi ve dijital imzaların yanında, Delphi ve C++Builder için HotPDF PDF component'in parçasıdır