HotPDF, imzalı ve AES-256 şifreli PDF'leri Delphi'de eskisinden çok daha hızlı yüklüyor; çünkü yükleme yolundan üç ayrı maliyet çıkarıldı: kaynak akışın tamponsuz belirteç ayrıştırması, çözme sırasında ISO 32000-2 Algorithm 2.A parola kontrolünün ikinci kez çalıştırılması ve R6 sertleştirilmiş özetinin içindeki yavaş bir iç AES-128-CBC döngüsü. Delphi hedeflerinde tek R6 düzeltmesi, bir belge yüklemesini kabaca 66 ms'den 16 ms'ye indirdi
Bunların hiçbiri, çoğu test setinin kullandığı küçük test dosyalarında görünmez. Bir iki kilobaytlık imzasız, şifresiz bir örnek, ayrıştırıcı ne yaparsa yapsın saniyenin birkaç yüzde birinde açılır. Sorun müşterilerin gerçekten gönderdiği belgelerde başlar: gömülü bir CMS imzası taşıyan, kabaca 35 KB'lik, revision 6 parolasıyla korunan bir sözleşme. Bu kombinasyon pahalı her kod yolunu aynı anda açar ve her birinin gerekenden yavaş olmasının kendi nedeni vardı. Bu yazı onları bulundukları sırayla ve imza doğrulama sonuçları önbelleklenmeye başladığında ortaya çıkan cache-key hatalarıyla birlikte ele alıyor
34 KB'lık imzalı bir PDF neden 170 ms'de yüklenir?
34 KB'lık imzalı bir PDF 170 ms'de yükleniyordu, çünkü ayrıştırıcı çıplak bir THandleStream'i doğrudan okuyordu ve her belirteç baytı kendi ReadFile sistem çağrısına dönüşüyordu. Bir PDF belirteçleyici, ayraç ararken bir seferde bir bayt ilerler; tamponsuz bir handle akışı bu yüzden ucuza mal olan bellek içi bir taramayı binlerce çekirdek geçişine çevirir
Ayrıntılar hatanın neden hayatta kaldığını açıklar. THotPDF.LoadFromFile, dosya kaynağını uzun süredir dahili bir okuma tamponuna sarıyor; AutoDecrypt sondası da aynı nedenle, 56 MB'lik bir dosyanın boyutunu yeniden sorgulayarak 1.4 s harcadıktan sonra düzeltilmişti. İmzalı ve şifreli belgeler ise başka bir yol izler. Tembel nesne okumaları ile imza incelemesinin, çağıran kendi akışını serbest bıraktıktan sonra bile daima aynı değişmez revizyonu görmesi için THotPDF.LoadFromStream kaynağın bir anlık görüntüsünü alır ve onu ikinci kez ayrıştırır. İkinci ayrıştırma, kimlik doğrulamalı revizyonlar için kullanılan yeniden yüklemeyle birlikte, anlık görüntü tanıtıcısını önünde tampon olmadan okuyordu. İkisi de artık dosya yolunun kullandığı aynı dahili tamponlu okuyucudan geçiyor
O tampon dahilidir; onu siz asla oluşturmazsınız. Kontrol ettiğiniz şey LoadFromStream'e verdiğiniz akıştır ve HotPDF ilk yapısal geçişte o akışı olduğu gibi okur. Veri hâlihazırda bellekteyse bir TMemoryStream verin. Diskteyse LoadFromFile çağırın; Delphi 10.1 Berlin ve üzerinde de yükleyiciye ham bir TFileStream yerine bir TBufferedFileStream verin
uses
System.SysUtils, System.Classes, HPDFDoc;
function LoadEncryptedFromBytes(const FileName: string;
const Password: AnsiString): THotPDF;
var
Mem: TMemoryStream;
begin
Result := THotPDF.Create(nil);
Mem := TMemoryStream.Create;
try
Mem.LoadFromFile(FileName); // belirteç başına okuma yerine tek toplu okuma
Mem.Position := 0;
// LoadFromStream sayfa sayısını döndürür; belge yüklenmediyse 0
if Result.LoadFromStream(Mem, Password) < 1 then
raise Exception.Create('Could not load ' + FileName);
except
Result.Free;
Mem.Free;
raise;
end;
Mem.Free;
end;
Yüklemeden hemen sonra bellek akışını serbest bırakmak imzalı ve şifreli girdi için güvenlidir; bileşen baytların kendi anlık görüntüsünü çoktan tutar. Yine de sonrasında imzaları doğrulamayı planlıyorsanız özgün dosyayı ya da tamponu saklayın; doğrulama, çözülmüş nesne modelini değil dosyanın ham /ByteRange bölümlerini özetler
Yüklemeyi ve doğrulamayı ayrı evreler olarak ölçmek
Yükleme süresi ile imza doğrulama süresi ayrı ölçülmelidir; çünkü farklı biçimlerde başarısız olurlar ve farklı yerlerde düzeltilirler. Yükleme evresi ayrıştırmayı, parola kontrolünü ve çözmeyi kapsar. Doğrulama evresi imzalı bayt aralıklarını yeniden özetler ve CMS imzasını kontrol eder. İkisini tek bir sayıda birleştirirseniz birindeki gerileme ötekine gizlenir
Bu iş için System.Diagnostics'teki RTL TStopwatch yeter. Aşağıdaki düzenek, THotPDF.LoadFromFile'in parola aşırı yüklemesiyle yükler ve ilk imzayı VerifyLoadedSignature'in TStream aşırı yüklemesiyle doğrular. Akış aşırı yüklemesi şifreli bir dosya için doğru seçimdir; çünkü parolalı yükleme yolu, parametresiz aşırı yüklemelerin yeniden açması için kaynak dosya adını saklamaz
uses
System.SysUtils, System.Classes, System.Diagnostics,
HPDFDoc, HPDFSignVerify;
procedure TimeSignedEncryptedLoad(const FileName: string;
const Password: AnsiString);
var
Pdf: THotPDF;
Src: TFileStream;
Info: THPDFSignatureInfo;
Status: THPDFSignatureVerifyStatus;
Watch: TStopwatch;
Pages: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Watch := TStopwatch.StartNew;
Pages := Pdf.LoadFromFile(FileName, Password);
Writeln(Format('Load + decrypt: %d ms, %d pages, encrypted=%s',
[Watch.ElapsedMilliseconds, Pages, BoolToStr(Pdf.IsEncrypted, True)]));
if (Pages < 1) or (Pdf.GetLoadedSignatureFieldCount = 0) then
Exit;
Src := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Watch := TStopwatch.StartNew;
Status := Pdf.VerifyLoadedSignature(0, Src, Info);
Writeln(Format('Verify signature 0: %d ms, status %d, signer %s',
[Watch.ElapsedMilliseconds, Ord(Status), string(Info.SignerName)]));
finally
Src.Free;
end;
finally
Pdf.Free;
end;
end;
Birkaç kez çalıştırın ve ilk turu yok sayın. İlk çağrı, AES tablolarını kurmak ve kendi kendini testini çalıştırmak dahil tek seferlik kurulum maliyetlerini öder; disk önbelleği de ilk okumayı çarpıtır. Aradığınız şey, gerçek bir müşteri dosyası için kararlı durum (steady-state) sayısıdır; aynı belgenin imzasız, şifresiz bir kopyasına karşı ölçülür
AES-256 parolası neden iki kez türetildi?
HotPDF, AES-256 dosya anahtarını iki kez türetiyordu; çünkü aynı yüklemenin iki evresi de tam parola kontrolünü kendi başına çalıştırıyordu. Yapısal ayrıştırmadan önce RecoverReloadKeysFromStreamRanges, filtreler çözülürken şifreli nesne akışı kapsayıcılarının da çözülebilmesi için ham baytları önceden tarar. O ön tarama zaten ISO 32000-2 §7.6.4.3.3 Algorithm 2.A kontrollerini yapar: parolayı kullanıcı ve sahip tuzlarına (salt) karşı doğrular ve dosya şifreleme anahtarını kurtarır. DecryptLoadedDocument ise o sonucu yok sayıyor ve türetimin tamamını yeniden yapıyordu
Revision 4 ve daha eskilerde kopyanın maliyeti bir MD5 döngüsünden biraz fazlaydı. Revision 6'da Algorithm 2.A, Algorithm 2.B sertleştirilmiş özetini çağırır; kopya böylece dosyayı açmanın en pahalı kısmını ikiye katlıyordu. Kurtarılan anahtarı yeniden kullanmak basittir. Onu güvenle yeniden kullanmak daha zordur, çünkü kendi parola kontrolünden uzun yaşayan önbellekli bir anahtar bir kimlik doğrulama atlatmasına dönüşür
Düzeltme yeniden kullanımı bir kapsam bayrağıyla sınırlar; bayrak yalnızca LoadFromFile ile LoadFromStream'in parola aşırı yüklemeleri içindeki ön tarama ile çözme çağrısı arasında true'dur ve bir finally bloğunda temizlenir. Bayrak yalnızca ön tarama parolayı gerçekten doğruladığında ayarlanır. Düz bir yüklemenin ardından DecryptLoadedDocument'e doğrudan bir çağrı daima türetimin tamamını çalıştırır; çünkü o parola hiç ön taranmamıştır. Bu kısıt olmasaydı, önceki başarılı bir açmadan arta kalan bayat bir yeniden yükleme anahtarı, yanlış parola veren sonraki bir çağırıcı için belgeyi sessizce çözebilirdi; ve bu, yavaş bir yüklemeden çok daha kötü bir hatadır
R6 sertleştirilmiş özeti zamanını nereye harcar?
R6 sertleştirilmiş özeti zamanının neredeyse tamamını AES-128-CBC şifrelemesine harcar. ISO 32000-2 §7.6.4.3.4 Algorithm 2.B bir turu en az 64 kez yineler. Her tur, parolanın, geçerli özetin ve sahip tarafında /U dizgesinin 64 kopyasından oluşan bir tampon kurar; o tamponu, özetin ilk 16 baytını anahtar, sonraki 16 baytını IV yaparak AES-128-CBC ile şifreler ve bir sonraki özet için, şifreli metnin ilk 16 baytını 3'e göre mod bir sayı olarak okuyup SHA-256, SHA-384 ya da SHA-512 seçer. Özetler kısadır. Şifrelenen tamponlar tur başına birkaç kilobayttır
HotPDF'in o döngü uygulaması olan PDF20HashDanceR6, tamponu bir zamanlar bayt odaklı bir referans AES üzerinden blok blok şifreliyordu. Artık önce yerel bir dolgusuz CBC rutini dener; Delphi hedeflerinde bu, bağlı olan C AES arka ucuna ulaşır. O değişiklikle bütün bir belge yüklemesi kabaca 66 ms'den 16 ms'ye indi. Yerel rutin yoksa ya da uyuşmayan bir uzunluk döndürürse kod tarihsel blok döngüsüne döner; o döngü de T-tabloları mevcut S-box'tan üretilen tablo güdümlü bir AES-128 bloğu kullanabilir. Şifre her durumda aynıdır; türetilen anahtar, hangi yolun hesapladığına bakılmaksızın bayt bayt özdeştir
Bu yazı hız hakkında. Algorithm 2.B sonlanma kuralını doğru kurmak ayrı bir doğruluk sorunudur ve oradaki bir birim kayması doğru parolaları yalnızca zaman zaman reddeder. R5 ile R6 karşılaştırmasının yapılandırma tarafı HotPDF ile AES-256 PDF şifreleme kurulum rehberinde ele alınır
Hızlı bir AES bloğunun sizi kandırmasının dört yolu
Tablo güdümlü bir AES bloğu yanlış şifreli metin üretip kendi kendini testinden geçebilir. Aşağıdaki dört tuzaktan her biri hızlı yol kurulurken karşımıza çıktı ve ikisi, ilk tasarımlandığı hâliyle kendini testini geçti
- Kendini testine yeniden giriş. Hazır olma rutini, kendini testini çalıştırmadan önce başlatıldı bayrağını ayarlar; test sırasında yapılan yeniden girişli bir çağrı böylece referans yolu alır. Testin ilk sürümü bu yüzden referans uygulamayı kendisiyle karşılaştırıyordu. Düzeltme, kendini testinin doğrudan çağırdığı korumasız bir ham hızlı bloktur; böylece gerçekten test edilen kod tablo yolu olur
- Yer değiştirmiş tablo düzenleri. Dört tur tablosundan ikisinin (Te1 ve Te3) bayt sırasını takas etmek, yeniden girişli kendini testinin fark edemeyeceği yanlış şifreli metin üretir. Onu yakalayan, FIPS-197 Ek C.1 şifreli metnini
69c4e0d86a7b0430d8cdb78070b4c55adeğerini,000102…0fanahtarı ve00112233…ffaçık metni için basan bağımsız bir sonda programıydı. Kendini testi artık hızlı yola güvenmeden önce hem o vektörü hem referans blokla bayt eşitliğini ister - FPC aralık ve taşma kontrolleri. Free Pascal altında
-Crile-Cokullanırken Delphi tarzı açıkByte()dönüşümleri ve bayt genişliğinde kaydırma aritmetiği aralık ya da taşma istisnaları fırlatır. Tablo indeksleri ile GF(2^8) ikilemesi artık yerel tamsayı genişliğinde hesaplanır veand $FFile maskelenir - FPC inliner yanlış derlemesi.
PByteparametreli küçük big-endian yükleme ve saklama yardımcıları, FPC altındainlineişaretlendiklerinde erişim ihlallerine derleniyordu.inline'ı kaldırmak onları düzeltti; çağrı maliyeti tablo aramalarının yanında ihmal edilebilir
Son bağlantının arkasında da aynı deneyim var. Kütüphanedeki her AES-128 bloğunu hızlı tablolardan geçirmek, bir yayın adayı yüklemesi sırasında deterministik bir FPC erişim ihlaline yol açtı; o değişiklik geri alındı. Sevkedilen tasarım tutucudur: önce yerel CBC, R6 döngüsünün içinde açık bir hızlı tablo dalı ve başka her yerde referans blok. Genel ders şu: birkaç derleyici altında derlenmesi gereken yeni kriptografik kod, her hedef derleyicide bağımsız bilinen yanıtlı bir sonda ister. DLL düzeyindeki bir test başarısızlığı elinizde yanlış bir tablo mu var yoksa bir çökme mi söylemez. Free Pascal tarafı için ayrıntı HotPDF Deflate ve AES arka uçlarının Free Pascal altındaki notlarında
İmza doğrulamasını yanlış yanıtı önbelleklemeden nasıl önbelleklersiniz?
Bir imza doğrulama önbelleği, kararı değiştirebilecek her girdiyi — imzalı bayt aralıklarının dışında yaşayanlar dahil — anahtarı kapsıyorsa güvenlidir. HotPDF'in headless belge katmanı, CMS doğrulama sonuçlarını şunları özetleyerek hatırlar: CMS baytları, /Contents boşluğundaki imza sözlüğünün baytları, kapsanan aralıkların içeriği, bir politika parmak izi, doğrulama bütçeleri, toplam kaynak boyutu ve bir dakikalık zaman kovası. O girdilerden üçü, bir hatanın eksikliklerini göstermesinden sonra eklendi
Güvenilen sertifika dosyaları yolla değil içerikle anahtarlanır. İşlem katmanı, aynı PEM malzemesini her komut için yeni bir geçici dosya adına yazar; yol tabanlı bir anahtar asla eşleşmedi ve önbellek hiç tutmadı. Toplam kaynak boyutunun anahtarın parçası olması gerekiyordu, çünkü bildirilen UnsignedTrailingBytes ona bağlıdır: bir taban revizyon ile sonraki bir artımlı güncelleme, farklı sondaki uzunluklara sahipken tam olarak aynı kapsanan içeriği paylaşabilir ve yalnızca aralıklara dayalı bir önbellek, taban revizyonun kararını güncellenmiş dosyaya verirdi. O sondaki baytların ne anlama geldiğinin arka planı HotPDF'in Delphi'de PDF dijital imzalarını doğrulama rehberindedir
Üçüncü hata bir tasarım hatası değil Pascal dil tuzağıydı. Özet rutinleri türsüz bir const tampon alır; dinamik bir dizi değişkenini türsüz bir parametreye vermek, dizi verisini değil dizi referansının adresini geçirir
function Checksum(const Data; Count: Integer): Cardinal;
var
P: PByte;
I: Integer;
begin
Result := 0;
P := @Data;
for I := 0 to Count - 1 do
begin
Result := Result + P^;
Inc(P);
end;
end;
procedure DigestCMS(const CMS: TBytes);
begin
// Yanlış: dizi referansını ve yığında ardından gelen her şeyi okur.
// Oradan yapılan 16 KB'lik bir okuma yığın sayfasını aşar ve bir erişim
// ihlali fırlatır; Delphi ile FPC farklı noktalarda başarısız olur.
Writeln(Checksum(CMS, Length(CMS)));
// Doğru: ilk öğeyi geçin, boş dizi için korumalı
if Length(CMS) > 0 then
Writeln(Checksum(CMS[0], Length(CMS)));
end;
Önbellek kilidi, Windows InitializeCriticalSection API'si değil SyncObjs.TCriticalSection'dır; çünkü aynı ünite Linux'ta FPC altında da derlenir ve o API orada yoktur
Neyin bilerek yavaş kaldığı
R6 anahtar türetiminin kendisi yüklemeler arasında asla önbelleklenmez ve bu bilinçlidir. Dış baytlar ile açıkça verilmiş bir parola daima gerçek kimlik doğrulama yolundan geçer; bir R6 belgesinin parolalı her yüklemesi en az bir Algorithm 2.B özeti öder, sahip parolası denendiğinde kullanıcı tarafı ile sahip tarafı özetlerinin ikisini de. Yukarıda anlatılan kapsamlı yeniden kullanım, tek bir yüklemenin içindeki kopyayı kaldırır ve daha fazlasını değil
- 66 ms'den 16 ms'ye rakamı, yerel AES arka ucu bağlı Delphi hedefleri için geçerlidir. FPC derlemeleri tablo güdümlü bloğu alır ve Windows'ta FPC kod üretimi, aynı assertion'lar altında Delphi'den 2-4 kat daha yavaş çalıştı
- Doğrulama önbelleği bir dakikalık zaman kovasına bağlıdır; aynı baytların kısa pencere içindeki yinelenen kontrolleri ucuzdur ama önbellekli bir karar, hesaplandığı dakikadan hiçbir zaman uzun yaşamaz
- Birleşik revizyon yetki önbelleği kuruldu, hataları ayıklandı ve sonra kaldırıldı. FPC farkını kapatmadı ve Delphi güvenlik callback'lerini ölçülebilir biçimde yavaşlattı; önbellek tutmadan önce ölçmenin iyi bir hatırlatıcısı
- İmza doğrulaması özgün şifreli dosya baytlarını ister; kaynağı erişilebilir tutun. Daha hızlı çözme o gereksinimi kaldırmaya hiçbir şey yapmaz
SSS
Ön taranan anahtarı yeniden kullanmak parola kontrolünü zayıflatır mı?
Hayır. HotPDF kurtarılan dosya anahtarını yalnızca parolayı tam Algorithm 2.A kontrolleriyle doğrulayan aynı parola-aşırı yükleme çağrısının içinde yeniden kullanır ve kapsam bayrağı bir finally bloğunda temizlenir. Bağımsız bir DecryptLoadedDocument çağrısı daima türetimin tamamını yapar; önceki bir açmadan arta kalan anahtar yanlış parolayla ödünç alınamaz
R6 özet sonucu yüklemeler arasında neden önbelleklenmiyor?
Özeti yüklemeler arasında önbelleklemek, açılan dosyaya karşı kontrol etmeden bir parolayı kabul etmek demek olurdu. HotPDF her dış bayt kaynağını ve her açık parolayı kendi başına kimlik doğrulaması gereken güvenilmeyen girdi sayar; ölçülebilir hızlanma, Algorithm 2.B'yi atlamaktan değil onu hızlandırmaktan geldi
LoadFromStream'e bir TFileStream mi vermeliyim?
Diskteki dosyalar için LoadFromFile tercih edin; dahilen zaten tamponlar. Akış aşırı yüklemesine ihtiyaç duyduğunuzda bir TMemoryStream ya da TBufferedFileStream verin; HotPDF vereceğiniz akışı ilk yapısal geçişte doğrudan okur ve ham bir handle akışı her küçük okuma için bir sistem çağrısına mal olur
Parola korumalı yükleme, AES-256 R6 şifrelemesi, CMS imza doğrulaması ve yüklü belge API'sinin geri kalanı, Delphi ve C++Builder için tek bir yerel VCL kütüphanesinde birlikte gelir. Özellik ayrıntıları ve desteklenen IDE sürümleri HotPDF Component ürün sayfasında