PDF Library for Delphi, şifreli bir PDF'i parola yerine ham dosya şifreleme anahtarıyla açabilir. DAOpenFileWithEncryptionKey anahtarı hexadecimal metin olarak kabul eder, encryption dictionary içinde zaten saklanan doğrulayıcıya karşı kontrol eder ve salt okunur bir Direct Access tutamacı döndürür; DAOpenFromStreamWithEncryptionKey de çağıranın sahibi olduğu bir TStream için aynısını yapar. İkisi de v3.496.0 ile geldi
Senaryo dar ama gerçektir. Bir adli inceleme çalışması size bir bellek imgesinden kurtarılmış bir anahtar ve hiç parola olmadan gelir. Toplu arşivleme işinde, kaynak DRM sistemi yıllar önce parola vermeyi bıraktığı için dosya anahtarlarını escrow veritabanında tutan on bin belge vardır. Kullanımdan kaldırılmış bir hak yönetimi ürününden geçişte geriye anahtar malzemesi ve başka hiçbir şey kalmaz. Bu durumların hepsinde elinizdeki kimlik bilgisi key derivation işleminin çıktısıdır, girdisi değil; API'nin hiçbir yerindeki parola parametresi bunu kabul etmez
Dosya şifreleme anahtarı neden parola değildir?
Parola ile dosya şifreleme anahtarı, PDF standardı güvenlik işleyicisinde (ISO 32000-1 §7.6.3) key derivation işleminin zıt taraflarında durur. İşleyici bir parolayı alır, /O, /P, dosya ID'si ve revizyona özgü bir hash ile karıştırır ve dosya anahtarını üretir. Dosya anahtarını parola yuvasına verirseniz başka bir anlamsızlığa hash'lenmiş anlamsız bir değer elde edersiniz; bu yüzden DAOpenFile üzerinde bir bayrak yerine kendi giriş noktasına ihtiyaç vardır
Ham anahtarın enjekte edilebileceği yer revizyona göre sabittir. 2 ile 4 arasındaki revizyonlar dosya anahtarından, nesne numarasından, generation numarasından ve AESV2 için AES salt değerinden ayrı bir nesne başına anahtar türetmeye devam eder; dolayısıyla dosya anahtarını bilmek nesne düzeyindeki türetmeyi atlamanızı hiç sağlamaz. 5 ile 7 arasındaki revizyonlar 32 baytlık dosya anahtarını doğrudan AES-256 için kullanır ve nesne başına adım yoktur. İkisinde ortak olan tek katman dosya anahtarının kendisidir; PDF Library for Delphi dışarıdan verilen anahtarı yalnızca burada kabul eder. Parola hâlâ elinizdeyse bunun yerine normal yolu kullanın ve yanlış ilk denemeyi parola yeniden deneme yaşam döngüsünün ele almasına izin verin; çünkü ham anahtar girişi, parola yolunun koruduğu bazı kolaylıklardan bilerek vazgeçer
DAOpenFileWithEncryptionKey hangi girdiyi kabul eder?
Yalnızca öneksiz, boşluksuz, çift uzunlukta ASCII hex kabul edilir ve çözülen bayt sayısı encryption revision ile tam olarak eşleşmelidir. 2 ile 4 arasındaki revizyonlarda beklenen uzunluk encryption dictionary içindeki /Length değerinden gelir: 8 bitin katı olan, 5 ile 16 bayt arasına denk gelen ve /Length yoksa varsayılan olarak 40 bite düşen bir değer. 5 ile 7 arasındaki revizyonlarda uzunluk pazarlık olmadan tam 32 bayttır. 0x öneki, tek sayıda basamak, 64 hex karakterden fazlası, Options içinde bilinmeyen bir bit veya hiç şifrelenmemiş bir belge aynı sonucu üretir: 0 tutamacı ve LastErrorCode değerinde 425 olan PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID. Katılık kasıtlıdır. Boşlukları kırpan ve kısa girdiyi sıfırlarla tamamlayan gevşek bir ayrıştırıcı, kesilmiş bir clipboard yapıştırmasını memnuniyetle bir kimlik bilgisine çevirir ve sonra çok daha az okunabilir bir yerde başarısız olur
Var
Lib: TPDFlib;
FileHandle, PageRef: Integer;
Begin
Lib:= TPDFlib.Create;
Try
// KeyHex, AES-128 R4 için 32, AES-256 R6 için 64 hex karakteridir
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If FileHandle= 0 Then
Raise Exception.CreateFmt('raw key refused (error %d)', [Lib.LastErrorCode]);
Try
PageRef:= Lib.DAFindPage(FileHandle, 1);
WriteLn(Lib.DAExtractPageText(FileHandle, PageRef, 0));
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Lib.Free;
End;
End;
Diğer hata kodları da bir toplu işin operatör hatasını kanıt sorunlarından ayırabilmesi için farklı kalır: dosya yoksa 411, okuma için açılamıyorsa 401, çapraz başvuru yapısı bozuksa 409. Anahtarla ilgili her şey kasıtlı olarak 425'te birleşir; çünkü anahtarın hangi kısmının yanlış olduğunu söyleyen bir ham anahtar giriş noktası bir oracle'dır
Doğrulama gerçekte neyi kanıtlar?
PDF Library for Delphi, verilen anahtarın bu belgeye ait olduğunu encryption dictionary'nin zaten taşıdığı doğrulayıcıyı kullanarak kanıtlar ve kontrol revizyona göre değişir. Revision 2, 32 baytlık standart padding dizesinin RC4 şifrelemesini yeniden hesaplar ve 32 baytın tamamını /U ile karşılaştırır. Revisions 3 ve 4 padding'i dosya ID'siyle birlikte hash'ler, RC4 geçişini ve XOR türetilmiş 19 turu çalıştırır ve /U değerinin ilk 16 baytını karşılaştırır. Revisions 5 ile 7, 16 baytlık /Perms dizesini sıfır IV ile çözer ve aynı anda dört bağımsız şeyi kontrol eder: little-endian permission word değerinin /P ile eşleşmesi, 5 ile 8 arasındaki konumlardaki dört FF baytı, encrypt-metadata bayrağının T veya F olması ve 10 ile 12 arasındaki konumlardaki adb işareti
Bir doğrulayıcı varsa ama eşleşmiyorsa açılış koşulsuz olarak reddedilir. Bu özelliğin dayandığı garantiyi açıkça belirtmek gerekir. Ayrıca doğrulamanın ne olmadığını da bilin: anahtarın bu dosyanın şifresini çözdüğünü söyler, kimsenin onu kullanmanıza izin verdiğini değil. /Perms içinden çıkarılan permission word anahtar hakkında kanıttır, izin değildir; belgenin gerçekte neye izin verdiğini bilmek istiyorsanız bu, ayrı bir şifreleme ve izin denetimi geçişinin işidir. ASCII dışı AES-256 parolalarının SASLprep işlemi gibi parola tarafı normalleştirme sorunları burada ortaya çıkmaz, çünkü hiçbir string hash'e ulaşmaz
PDF_RAW_KEY_ALLOW_UNVERIFIED ne zaman uygulanır?
PDF_RAW_KEY_ALLOW_UNVERIFIED tam olarak tek bir durumu kapsar: belge kullanılabilir bir doğrulayıcı taşımadığı için /Perms eksik veya 16 bayt değil ya da /U karşılaştırılamayacak kadar kısadır. Başarısız kanıtı geçersiz kılmaz. /Perms içinde tek bir hex basamağını bozup doğru anahtarı seçenek açıkken verirseniz PDF Library for Delphi yine 0 ve 425 döndürür. Seçenek açıkken bozulmamış bir doğrulayıcıya karşı 32 sıfır baytlık bir anahtar verirseniz cevap aynıdır. Seçenek kanıt yokluğunu gevşetir, onunla çelişen kanıtı asla değil. Kurtarma açılışı ile doğrulanmış açılış farklı epistemik durumlar olduğundan, dönüş değerine katılmak yerine ayrı bildirilir ve DAGetEncryptionKeyValidation açılmış tutamacı alıp üç sabitten biriyle cevap verir:
PDF_RAW_KEY_VALIDATION_VERIFIED(1), bir doğrulayıcının bulunduğu ve eşleştiği anlamına gelirPDF_RAW_KEY_VALIDATION_UNVERIFIED(2), anahtarın yalnızca değerlendirilebilecek bir doğrulayıcı olmadığı ve çağıranın bu politikayı açıkça istediği için kabul edildiği anlamına gelirPDF_RAW_KEY_VALIDATION_NONE(0), sıradan bir parolayla açılmış tutamacın bildirdiği değerdir
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
(Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
// bu dosyada doğrulayıcı yok mu? açık bir kurtarma politikasıyla yeniden dene
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex,
PDF_RAW_KEY_ALLOW_UNVERIFIED);
If FileHandle<> 0 Then
Begin
Case Lib.DAGetEncryptionKeyValidation(FileHandle) Of
PDF_RAW_KEY_VALIDATION_VERIFIED:
Chain.Note('key verified against the encryption dictionary');
PDF_RAW_KEY_VALIDATION_UNVERIFIED:
Chain.Note('no verifier available: extraction is unattested');
End;
End;
Yapısı gereği salt okunur ve akışın sahibi kim?
Ham anahtar dosya girişi kaynağı her zaman fmOpenRead or fmShareDenyWrite ile açar ve tüm Direct Access zincirini salt okunur işaretler; bu nedenle DAAppendFile yerinde yazmayı reddeder, artımlı güncelleme denemek yerine 2 döndürür. Bu, tutamacı konuşarak değiştirebileceğiniz bir politika değildir; dosya ayrıştırılmadan önce constructor içinde ayarlanır. Kanıt çalışması için istediğiniz özellik, kaynak baytlarının tutamaç kapandıktan sonra da byte özdeş kalmasıdır ve regresyon paketi bunu hem AES-128 revision 4 hem de AES-256 revision 6 fixture'ında doğrular. Akış girişi yazmalarda aynı davranır ve bir kural daha ekler: DAOpenFromStreamWithEncryptionKey hiçbir zaman sahipliği almaz; bu yüzden DACloseFile sizin TStream'inizi canlı bırakır ve onu kendiniz serbest bırakırsınız
Source:= TMemoryStream.Create;
Try
Source.LoadFromFile(ArchivePath);
Source.Position:= 0;
FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
If FileHandle<> 0 Then
Try
// 2 = salt okunur tutamaç: başka yere aktar, kanıta hiçbir zaman ekleme yapma
Assert(Lib.DAAppendFile(FileHandle)= 2);
Harvest(Lib, FileHandle);
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Source.Free; // tutamaç bu akışın sahibi olmadı
End;
Anahtar hijyeni ve DLL'in dışa aktardıkları
Zinciri kapatmak dosya anahtarını, parola önbelleğini ve türetilmiş nesne anahtarlarını üzerine yazar; çözülmüş anahtar da açılış başarılı olsun veya olmasın giriş noktasının kendi Finally bloğunda silinir. Bunun arkasında gerçek hata ayıklama zamanı harcatan ince bir nokta var: hayatta kalması gereken her kopya atama yerine açıkça SetLength ve Move ile klonlanır. Delphi'de bir AnsiString'i diğerine atarsanız copy-on-write altında iki ad da aynı buffer'ı paylaşır; çağıran tarafı silmek, crypt handler'ın hâlâ kullandığı anahtarı sıfırlar ve belge, hiçbir stack trace'in açıklayamayacağı nedenlerle anlamsız veriye çözülür. DLL sınırını yalnızca dosya tabanlı giriş noktaları, wide ve ANSI biçimleriyle, doğrulama durumu erişicisiyle birlikte geçer; TStream varyantı yalnızca Delphi'de kalır, çünkü düz bir C ABI içinde dürüstçe temsil edilemeyecek Delphi nesne yaşam süresine ve başvuru semantiklerine bağlıdır. Kurtarma araçlarınız bir DLL istemcisiyse geçici bir dosyaya aktarmayı ve onu anahtara uyguladığınız kontrollerle aynı kontroller altında silmeyi planlayın
Hex anahtarını belge parolasına uygulayacağınız kullanım kurallarıyla credential malzemesi olarak ele alın ve doğrulama durumunu custody zincirinizin ürettiği günlüğe koyun; böylece daha sonraki bir okuyucu doğrulanmış çıkarma ile doğrulanmamış olanı ayırabilir. Adli inceleme, toplu arşivleme veya DRM geçişi için bir Delphi PDF bileşenini değerlendiriyorsanız ham anahtar giriş noktaları, salt okunur garantisi ve şifreleme denetimi yüzeyi aynı kütüphanenin parçalarıdır; tam özellik listesini PDF Library for Delphi ürün sayfasında okuyabilirsiniz