HotPDF, bir PDF'i belirli sertifika sahipleri için ISO 32000 public-key security handler üzerinden şifreler: EnablePubKeyEncryption 20 baytlık rastgele bir seed alır ve her alıcı, RSA anahtarları için AddPubKeyRecipientCertificatein (RSA-OAEP key transport) ya da eliptik eğri anahtarları için AddPubKeyAgreementRecipientWithSecretin (P-256, P-384, P-521, X25519 ya da X448 üzerinde ECDH) kurduğu kendi CMS zarfını alır. Hiç kimse parola paylaşmaz; eşleşen özel anahtarı elinde tutan dosyayı açar
Kullanım vakası her zaman aynı hikâyenin bir türlüdür. Üç 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 şifrelemesi bunu ifade edemez. Sertifika şifrelemesi eder; çünkü her alıcı belgeyi zaten elinde tuttuğu bir anahtarla açar ve her alıcı kendi zarfının içinde farklı bir izin seti taşıyabilir
Sertifika temelli PDF şifrelemesi paroladan nasıl ayrılır?
Public-key ile şifrelenmiş bir PDF, dosya anahtarını rastgele bir seed artı her alıcı zarfının baytlarından türetir; bir insanın yazdığı hiçbir şeyden değil. Handler ISO 32000-1 §7.6.4'te (ISO 32000-2'de §7.6.5) tanımlanır ve zarflar RFC 5652'de tanımlanan CMS EnvelopedData yapılarıdır. HotPDF, /SubFilter /adbe.pkcs7.s5 ile /Filter /Adobe.PubSec yazar; AES-256 için bu, /V 5 ve /CF altında /CFM /AESV3lü bir /DefaultCryptFilter girdisi demektir ve /Recipients dizisi o crypt filter içinde yaşar. Her zarf 24 baytı şifreler: 20 baytlık seed ve ardından o alıcının 32 bitlik izin sözcüğü. Şifreleme sözlüğündeki /P değeri yalnızca bir yer tutucudur, çünkü gerçek izinler her zarfın içinde seyahat eder. Yükleme sırasında bir 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). 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 EnablePubKeyEncryptionı aes256 ile çağırın, sonra BeginDoctan önce DER kodlu her sertifika için bir kez AddPubKeyRecipientCertificate çağırın. Yardımcı, OAEP özeti ve MGF1 özeti için THPDFRSAOAEPHash değerleriyle (rohSHA256, rohSHA384 ya da rohSHA512) işlem içinde bir RSAES-OAEP zarfı kurar ve zarf içeriğini AES-256-CBC ile şifreler
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. Birincisi, seed uzunluğu her anahtar türü için, AES-256 dahil, 20 bayt sabittir; EnablePubKeyEncryption başka her uzunlukta istisna fırlatır. İkincisi, EnablePubKeyEncryption varsayılanı aes128dir ve her iki sertifika yardımcısı da anahtar türü aes256 olmadan çalışmayı reddeder; ikinci argümanı unutmak size "certificate envelopes require aes256" istisnasını getirir. Eski cipherlar (k40, k128, aes128) hâlâ çalışır ama yalnızca başka yerde kurduğunuz bir zarfla AddPubKeyRecipient üzerinden. Üçüncüsü, AES-256 public-key şifrelemesi bir PDF 2.0 özelliğidir, bu yüzden HotPDF belge sürümünü otomatik olarak 2.0'a çeker. Daha düşük bir sürümde StrictVersionLock set edildiğinde EnablePubKeyEncryption, hiçbir şeyi etkinleştirmeden döner ve hata sonraki satırda ancak "call EnablePubKeyEncryption first" olarak görünür. Artımlı bir güncelleme sırasında şifrelemeyi değiştirmek anında EInvalidOpException fırlatır
ECDH alıcıları eklemek: P-256, P-384, P-521, X25519 ve X448
Eliptik eğri sertifikaları için AddPubKeyAgreementRecipientWithSecret, bir CMS key-agreement alıcısı yazar (RFC 5753'ten KARI yapısı KeyAgreeRecipientInfo, RFC 8418'den X25519 ve X448 profiliyle) ve ECDH paylaşılan sırrını işlem içinde hesaplar. Eğriyi bir THPDFPubKeyAgreementScheme değeriyle seçersiniz: pkasECDHP256, pkasECDHP384, pkasECDHP521, pkasX25519 ya da pkasX448. Şema sertifikadaki anahtarla eşleşmek zorundadır, yoksa çağrı "Certificate key does not match the requested agreement scheme" fırlatır. Kaputun altında her zarf taze rastgele 32 baytlık bir UKM, stdDH KDF ile türetilmiş bir anahtar şifreleme anahtarı (P-256 ve X25519 için SHA-256, P-384 için SHA-384, P-521 ve X448 için SHA-512) ve RFC 3394'te tanımlanan bir AES-256 key wrap alır. Paylaşılan sırrın kendisi saf Pascal eğri kodundan gelir; platform crypto sağlayıcısı devrede değildir. Saf Pascal NIST eğri aritmetiği yazısı o katmanın nasıl kurulduğunu ve doğrulandığını anlatır. Montgomery eğrileri için 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. Kütüphane public-key yardımcılarını yalnızca X25519 ve X448 için getirir (HPDFX25519PublicFromScalar, HPDFX448PublicFromScalar); P-256, P-384 ve P-521 için geçici anahtar çiftini kendi araçlarınızla üretir ve tam olarak alan boyutunda (32, 48 ya da 66 bayt) big-endian bir skaler ile eşleşen sıkıştırılmamış 0x04||X||Y noktasını OriginatorPublicKey olarak geçirirsiniz. HotPDF alıcı noktasını eğri denklemine karşı doğrular ama başlatıcı public anahtarınızın gerçekten skalerinize ait olduğunu denetleyemez. Uyuşmayan yarımlar yine de hiçbir alıcının açamayacağı tümüyle biçimli bir zarf üretir; gidiş dönüşlü bir yüklemenin test takımınızda olmasının, yalnızca dosya boyutu kontrolünün değil, sebebi budur
/Recipients sırası neden önemlidir?
/Recipientsin sırası önemlidir, çünkü dosya anahtarı seed ile diziliş sırasındaki her zarfın özetidir; yazan ve okuyan aynı baytları aynı sırayla hash etmek zorundadır. HotPDF zarfları eklediğiniz sırayla tutar ve değiştirmeden yazar; alıcıları istediğiniz sıraya ekleyebilirsiniz ama o diziyi sonraki hiçbir aşama yeniden sıralayamaz, yeniden kodlayamaz ya da "temizleyemez". Bu alandaki gerçek hataların çoğu o temanın bir varyasyonuydu: iki taraf biraz farklı baytları hash ediyordu:
TListeAddile dinamik dizi koymak yalnızca ham bir işaretçi tutar, referans sayısı yerel değişkende kalır. SonrakiSetLengthtamponu serbest bırakır ve yeniden kullanabilir; her yuva son zarfla özdeşleşti ve çok alıcılı dosyalar yanlış anahtar türetti. Çözüm, sahiplenilmiş bir kopyayıList.Add(Pointer(System.Copy(Bytes)))ile saklamaktır- Zarf açma DER'i yerinde ayrıştırır ve anahtar geri kazanım geçişi başlangıçta o canlı dizileri hash ediyordu. Okuyucu artık herhangi bir açma onlara dokunmadan önce her zarfın tertemiz kopyasının anlık görüntüsünü alır ve özet anlık görüntülerin üzerinden koşar
- Unicode bir
TStringListten geçen ikili DER,$80ve üzeri baytların kod sayfası tarafından yeniden kodlanmasına yol açar; HotPDF bu yüzden zarfları içeride hex metin olarak saklar - Şifreli ve ikili dizeler hex dize olarak yazılmalıdır. Literal bir dize satır sonu normalizasyonuna tabidir; CR, LF ve CRLF tek bir LF olur (ISO 32000-1 §7.3.4.2) ve bu, ciphertexti sessizce yeniden yazar. HotPDF her
/Recipientsgirdisini hex dize olarak çıkarır ve onu dize şifrelemesinden muaf tutar; çünkü her okuyucu herhangi bir anahtara sahip olmadan önce zarflara ihtiyaç duyar - DER
BIT STRINGin ilk baytı kullanılmayan bitleri sayar ve bayt hizalı anahtarlar için sıfır olmalıdır.SetLengthsonrası onu başlatılmamış bırakmak yığındaki ne varsa yazdı ve katı bir açıcı başlatıcı anahtarı reddetti; dosya zaman zaman yazıldığı anahtarın kendisiyle açılamıyordu - Aynı anahtar hâlâ çözemiyorsa katman katman karşılaştırın: dosya anahtarı, sonra ciphertext öneki (IV), sonra nesne anahtarı, sonra düz metin. Hata, anlaşmazlığa düşen ilk katmanın hemen ardında yaşar
Sertifika ile şifreli bir PDF'i özel anahtarla nasıl açarsınız?
Sertifika ile şifreli bir PDF'i açmak için LoadFromFile çağırmadan önce özel anahtar malzemesini kaydedin, çünkü HotPDF dosya anahtarını yapısal geçiş sırasında geri kazanır. HPDFParsePFX ile ayrıştırılmış bir RSA ya da EC anahtarını PubSecKeyMateriale atayın, ek RSA anahtarlarını AddPubSecKeyMaterial ile ekleyin ve ham ECDH skalerlerini AddPubSecAgreementKeyMaterial(CurveOID, PrivateScalar, OwnPublicPoint) ile kaydedin; HPDFOIDX25519, HPDFOIDX448, HPDFOIDECP256, HPDFOIDECP384 ya da HPDFOIDECP521 sabitlerini kullanarak. NIST eğrileri alıcının kendi sıkıştırılmamış public noktasını ister; Montgomery eğrileri onu yok sayar
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFPFX, HPDFKeyAgreement;
procedure OpenAuditPack(const LegalScalar: TBytes);
var
Reader: THotPDF;
begin
Reader := THotPDF.Create(nil);
try
Reader.AutoLaunch := False;
Reader.PubSecKeyMaterial :=
HPDFParsePFX(TFile.ReadAllBytes('reviewer-a.pfx'), 'pfx-password');
Reader.AddPubSecAgreementKeyMaterial(HPDFOIDX25519, LegalScalar, nil);
// İsteğe bağlı: hepsini denemek yerine zarfı doğrudan seç
Reader.PubSecRecipientQuery :=
function(Context: Pointer; RecipientCount: Integer): Integer
begin
Result := -1; // -1 = her zarfı sırayla dene
end;
Reader.LoadFromFile('audit-pack.pdf', '');
Writeln('Pages: ', Reader.GetLoadedPageCount);
finally
Reader.Free;
end;
end;
Callback olmadan HotPDF her zarfı kayıtlı her anahtara karşı dener: önce birincil anahtar, sonra her ek RSA anahtarı, sonra EC malzemesi. PubSecRecipientQuery zarf sayısını alır ve 0 tabanlı bir indeks ya da -1 döndürür; dizi dışı bir indeks kırpılmak yerine istisna fırlatır. AddPubSecKeyMaterialin yalnızca RSA malzemesi kabul ettiğine dikkat edin (bir modulus ve özel üs ister); EC anahtarlarının yeri PubSecKeyMaterial ya da AddPubSecAgreementKeyMaterialdir. Hiçbir anahtar hiçbir zarfı açamadığında geri kazanım adımı istisna fırlatmak yerine dosya anahtarsız döner; yük çağrısının dönmesine güvenmek yerine beklediğiniz içeriğin gerçekten çözüldüğünü doğrulayın
HotPDF'in garanti etmediği şeyler
HotPDF, kendi yazıcısı ile okuyucusunun bayt bayt anlaştığını garanti eder ve yukarıda anılan CMS yapılarını izleyen zarflar kurar. Her PDF görüntüleyicinin her kombinasyonu açacağını garanti etmez. RSA-OAEP key transport ve X25519 ya da X448 alıcı desteği okuyucular ve sürümler arasında değişir ve o kombinasyonlar için uyumluluk sonuçları yayımlamadık. Bir belgenin belirli bir görüntüleyicide açılması gerekiyorsa, şemaya bağlanmadan önce aynı anahtar türünden bir test sertifikası için bir test dosyası şifreleyin ve orada açın. Zarfın taşıdığı izinler, kurallara uyan yazılımın uyduğu politika olarak kalır; parola şifrelemesi altındakiyle tam olarak aynı. Seed kalitesi de sizin sorumluluğunuzdadır: AESGenerateRandomBytes o iş için oradadır ve HotPDF, dosya anahtarı türetildikten sonra seedin kendi kopyasını siler. Bir dizenin, akışın ya da ekin farklı bir crypt filter kullanmasına da ihtiyacınız varsa, StmF, StrF ve EFF için crypt filter politika rehberi public-key handlerın hangi filtre adlarını kabul ettiğini gösterir
Sertifika şifrelemesi, RSA-OAEP ve ECDH alıcı zarfları ve özel anahtar yükleme, HotPDF Delphi PDF component ile gelir; parola şifrelemesi, dijital imzalar ve ISO 32000 araç setinin gerisi, Delphi ve C++Builder için yanında durur