A PDF Library for Delphi titkosított PDF-et a jelszó helyett közvetlenül a nyers fájltitkosítási kulccsal is meg tud nyitni. A DAOpenFileWithEncryptionKey a kulcsot hexadecimális szövegként fogadja, ellenőrzi az encryption dictionaryban már tárolt verifikátorral, és csak olvasható Direct Access handle-t ad vissza; a DAOpenFromStreamWithEncryptionKey ugyanezt teszi egy hívó által birtokolt TStream esetén. Mindkettő a v3.496.0-ban érkezett
A helyzet szűk, de valós. Egy forenzikus vizsgálat memóriaképből visszanyert kulcsot ad, jelszót nem. Egy tömeges archiválási feladat tízezer olyan dokumentummal dolgozik, amelyek fájlkulcsai egy escrow-adatbázisban vannak, mert a régi DRM-rendszer évekkel ezelőtt leállt a jelszavak kiadásával. Egy kivonulás egy megszüntetett jogkezelő termékből kulcsanyagot hagy maga után, mást nem. Ezekben az esetekben a nálad lévő credential a kulcsszármaztatás kimenete, nem a bemenete, és az API egyetlen jelszóparamétere sem tudja fogadni
Miért nem jelszó a fájltitkosítási kulcs?
A jelszó és a fájltitkosítási kulcs a PDF szabványos security handlerében végzett kulcsszármaztatás (ISO 32000-1 §7.6.3) két ellentétes oldalán áll. A handler a jelszót az /O, /P, a fájlazonosító és a revíziófüggő hash keverékével feldolgozva állítja elő a fájlkulcsot. Ha a fájlkulcsot a jelszó helyére adod, második, értelmetlen hash készül belőle, ezért van szükség külön belépési pontra a DAOpenFile kapcsolója helyett
A nyers kulcs injektálási helyét a revízió rögzíti. A 2–4. revízió még külön objektumkulcsot származtat a fájlkulcsból, az objektumszámból, a generációs számból és AESV2 esetén az AES sójából, ezért a fájlkulcs birtoklása egyáltalán nem hagyja ki az objektumszintű származtatást. Az 5–7. revízió a 32 bájtos fájlkulcsot közvetlenül használja AES-256-hoz, objektumonkénti lépés nélkül. A közös réteg maga a fájlkulcs, ezért a PDF Library for Delphi csak itt fogad el kívülről megadott kulcsot. Ha a jelszó még megvan, maradj a normál útvonalon, és hagyd, hogy a jelszó-újrapróbálási életciklus kezelje az első hibás kísérletet, mert a nyerskulcsos belépés szándékosan kevesebb kényelmi funkciót biztosít
Milyen bemenetet fogad el a DAOpenFileWithEncryptionKey?
Csak előtag nélküli, whitespace-mentes, páros hosszúságú ASCII hexadecimális szöveget, és a dekódolt bájtszámnak pontosan egyeznie kell a titkosítási revízióval. A 2–4. revízió esetében a várt hosszt az encryption dictionary /Length értéke adja: 8 bites többszörös, 5 és 16 bájt között, hiányzó /Length esetén 40 bitre visszaesve. Az 5–7. revíziónál ez pontosan 32 bájt, nincs egyeztetés. A 0x előtag, páratlan számjegyszám, 64 hex karakternél hosszabb bemenet, ismeretlen bit az Options-ban, illetve a nem titkosított dokumentum ugyanazt eredményezi: 0 handle-t és LastErrorCode értékként PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID-et, azaz 425-öt. A szigorúság itt a lényeg. Egy engedékeny parser, amely levágja a whitespace-t és nullákkal tölti fel a rövid bemenetet, egy csonkolt vágólapbeillesztést is credentialként fogadna el, majd jóval kevésbé érthető helyen hibázna
Var
Lib: TPDFlib;
FileHandle, PageRef: Integer;
Begin
Lib:= TPDFlib.Create;
Try
// A KeyHex 32 hex karakter AES-128 R4, 64 AES-256 R6 esetén
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;
A többi hibakód külön marad, hogy egy batch-feladat meg tudja különböztetni az operátori hibát a bizonyíték problémájától: 411, ha a fájl nem létezik, 401, ha nem nyitható meg olvasásra, 409, ha a kereszt-hivatkozási szerkezet sérült. Minden kulccsal kapcsolatos hiba szándékosan 425-re olvad össze, mert egy nyerskulcsos belépési pont, amely megmondja, melyik kulcsrész volt hibás, oracle-lé válna
Mit bizonyít valójában az ellenőrzés?
A PDF Library for Delphi azt bizonyítja, hogy a megadott kulcs ehhez a dokumentumhoz tartozik, az encryption dictionary által már hordozott verifikátorral, és az ellenőrzés revíziónként eltér. A 2. revízió újraszámolja a 32 bájtos szabványos padding string RC4-titkosítását, majd mind a 32 bájtot összeveti az /U értékkel. A 3. és 4. revízió a paddinget a fájlazonosítóval együtt hash-eli, lefuttatja az RC4 menetet és a 19 XOR-származtatott kört, majd az /U első 16 bájtját hasonlítja össze. Az 5–7. revízió a 16 bájtos /Perms stringet nulla IV-vel dekódolja, és egyszerre négy független dolgot ellenőriz: a little-endian jogosultságszót az /P ellen, az 5–8. pozícióban lévő négy FF bájtot, a metaadat-titkosítás jelzőjét T vagy F alakban, valamint a 10–12. pozícióban lévő adb jelölőt
Ha van verifikátor, de nem egyezik, a megnyitást feltétel nélkül elutasítja. Ezt érdemes egyenesen kimondani, mert az egész funkció erre a garanciára épül. Azt is tartsd szem előtt, hogy az ellenőrzés mit nem bizonyít: azt mondja, hogy a kulcs ezt a fájlt dekódolja, nem azt, hogy bárki engedélyezte a használatát. A /Perms-ből visszanyert jogosultságszó a kulcs bizonyítéka, nem jogosultság, és ha tudni akarod, mit állít a dokumentum ténylegesen engedélyezettként, az külön feladat, titkosítási és jogosultság-audit. A jelszóoldali normalizálási problémák, például a nem ASCII AES-256 jelszavak SASLprep-kezelése, itt fel sem merülnek, mert egyetlen string sem kerül hash-be
Mikor érvényes a PDF_RAW_KEY_ALLOW_UNVERIFIED?
A PDF_RAW_KEY_ALLOW_UNVERIFIED pontosan egy helyzetet fed le: a dokumentum nem hordoz használható verifikátort, mert a /Perms hiányzik vagy nem 16 bájtos, illetve az /U túl rövid az összehasonlításhoz. Hibás bizonyítékot nem tud felülírni. Ha a /Perms belsejében egy hexadecimális számjegyet elrontasz, majd a helyes kulccsal és az opcióval próbálkozol, a PDF Library for Delphi továbbra is 0-t és 425-öt ad. Ha sértetlen verifikátor ellen 32 nulla bájtot adsz meg az opcióval, ugyanaz a válasz. Az opció a bizonyíték hiányát enyhíti, ellentmondását soha. Mivel a recovery open és a verified open két külön ismeretelméleti állapot, ezeket a visszatérési érték helyett külön is jelenti, a DAGetEncryptionKeyValidation pedig a megnyitott handle-ről a három konstans egyikével válaszol:
PDF_RAW_KEY_VALIDATION_VERIFIED(1) azt jelenti, hogy a verifikátor jelen volt és egyezettPDF_RAW_KEY_VALIDATION_UNVERIFIED(2) azt jelenti, hogy a kulcsot csak azért fogadta el, mert verifikátor nem volt kiértékelhető, és a hívó ezt a szabályzatot kifejezetten kértePDF_RAW_KEY_VALIDATION_NONE(0) az érték, amelyet egy hagyományos, jelszóval megnyitott handle ad vissza
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
(Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
// nincs verifikátor ebben a fájlban? próbáld újra explicit recovery szabállyal
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;
Felépítéséből adódóan csak olvasható, és ki birtokolja a streamet?
A nyerskulcsos fájlbelépés mindig fmOpenRead or fmShareDenyWrite módban nyitja a forrást, és a teljes Direct Access láncot csak olvashatóként jelöli, ezért a DAAppendFile elutasítja a helyben írást, és 2-t ad vissza ahelyett, hogy inkrementális frissítést próbálna. Ez nem olyan szabály, amelyről a handle-t meg lehet győzni: a konstruktorban áll be, még a fájl parsolása előtt. Bizonyítékkezelésnél az a kívánt tulajdonság, hogy a forrás bájtjai a handle bezárása után bájtról bájtra azonosak maradjanak, és a regressziós suite ezt pontosan ellenőrzi egy AES-128-as 4. revíziós és egy AES-256-os 6. revíziós fixture-ön. A streames belépés az írásra ugyanígy viselkedik, és még egy szabályt hozzáad: a DAOpenFromStreamWithEncryptionKey soha nem veszi át a tulajdonjogot, ezért a DACloseFile életben hagyja a saját TStream-edet, amelyet neked kell felszabadítanod
Source:= TMemoryStream.Create;
Try
Source.LoadFromFile(ArchivePath);
Source.Position:= 0;
FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
If FileHandle<> 0 Then
Try
// 2 = csak olvasható handle: máshová exportálj, a bizonyítékhoz ne fűzz
Assert(Lib.DAAppendFile(FileHandle)= 2);
Harvest(Lib, FileHandle);
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Source.Free; // a handle soha nem birtokolta ezt a streamet
End;
Kulcshigiénia és mit exportál a DLL
A lánc bezárása felülírja a fájlkulcsot, a jelszócache-t és a származtatott objektumkulcsokat, a dekódolt kulcsot pedig maga a belépési pont törli a Finally blokkban, akár sikeres volt a megnyitás, akár nem. Ennek van egy finom részlete, amely valódi hibakeresési időbe került: minden megőrzendő másolatot kifejezetten SetLength és Move segítségével kell klónozni, nem hozzárendeléssel. Ha Delphiben egyik AnsiString-et a másikhoz rendeled, copy-on-write miatt ugyanazt a puffert használják, ezért a hívó oldali törlés lenullázná a crypt handler által még használt kulcsot, és a dokumentum megmagyarázhatatlanul szemétté dekódolódna. A DLL-határon csak a fájlos belépési pontok mennek át wide és ANSI alakban, valamint a validációs állapot hozzáférője; a TStream változat Delphi-only marad, mert Delphi objektumélettartamra és referencia-szemantikára épül, amelyeket egy lapos C ABI nem tud tisztességesen ábrázolni. Ha recovery eszközöd DLL-kliens, tervezz ideiglenes fájlba staginget és annak törlését ugyanazon kontrollok alatt, amelyeket a kulcsra alkalmazol
A hexadecimális kulcsot kezeld credentialként, ugyanazokkal a szabályokkal, mint egy dokumentumjelszót, és a validációs állapotot tartsd meg a custody-lánc naplójában, hogy egy későbbi olvasó meg tudja különböztetni az ellenőrzött kinyerést a bizonyítatlanétól. Ha Delphi PDF-komponenst értékelsz forenzikához, tömeges archiváláshoz vagy DRM-migrációhoz, a nyerskulcsos belépési pontok, a csak olvasható garancia és a titkosítási auditfelület ugyanannak a librarynek a részei, a teljes funkciólista pedig a PDF Library for Delphi termékoldalán olvasható