PDF Library for Delphi dokáže otevřít šifrované PDF z jeho surového klíče šifrování souboru namísto hesla. DAOpenFileWithEncryptionKey přijímá klíč jako hexadecimální text, ověří ho proti verifieru, který už je uložen v encryption dictionary, a vrací handle Direct Access pouze pro čtení; DAOpenFromStreamWithEncryptionKey dělá totéž pro TStream vlastněný volajícím. Obě metody přibyly ve v3.496.0
Scénář je úzký, ale skutečný. Forenzní zakázka vám předá klíč získaný z paměťového obrazu a žádné heslo. Hromadná archivace má deset tisíc dokumentů, jejichž file keys leží v escrow databázi, protože původní DRM systém přestal před lety vydávat hesla. Migrace z vyřazeného systému správy práv má klíčový materiál a nic dalšího. V každém z těchto případů je credential, který držíte, výstupem odvození klíče, nikoli jeho vstupem, a žádný parametr hesla v API ho nepřijme
Proč není file encryption key heslem
Heslo a file encryption key leží na opačných stranách odvození klíče v PDF standard security handleru (ISO 32000-1 §7.6.3). Handler vezme heslo, smíchá ho s /O, /P, file ID a hashem závislým na revizi a vytvoří file key. Když file key vložíte do slotu pro heslo, získáte nesmysl zahashovaný do jiného nesmyslu, proto potřebuje tato funkce vlastní vstupní bod místo příznaku na DAOpenFile
Místo, kam lze surový klíč vložit, určuje revize. Revize 2 až 4 stále odvozují od file key odlišný klíč pro každý objekt z čísla objektu, čísla generace a u AESV2 ještě AES salt, takže vlastnictví file key vám vůbec neumožní obejít odvození na úrovni objektu. Revize 5 až 7 používají 32bajtový file key přímo pro AES-256 bez kroku pro jednotlivé objekty. Jedinou vrstvou společnou oběma cestám je samotný file key, a právě ten PDF Library for Delphi přijímá zvenčí. Pokud heslo stále máte, zůstaňte na běžné cestě a nechte lifecycle opakování hesla zpracovat první chybný pokus, protože vstupní bod pro surový klíč se záměrně vzdává několika pohodlných vlastností, které cesta s heslem zachovává
Jaký vstup přijímá DAOpenFileWithEncryptionKey
Pouze neprefixedovaný ASCII hex bez mezer, sudé délky, jehož počet dekódovaných bajtů musí přesně odpovídat revizi šifrování. U revizí 2 až 4 se očekávaná délka bere z /Length v encryption dictionary: násobek 8 bitů mezi 5 a 16 bajty, při chybějícím /Length s výchozí hodnotou 40 bitů. U revizí 5 až 7 je délka přesně 32 bajtů a o ničem se nevyjednává. Prefix 0x, lichý počet číslic, více než 64 hex znaků, neznámý bit v Options nebo dokument, který vůbec není šifrovaný, všechny vedou ke stejnému výsledku: handle 0 a LastErrorCode nastavený na PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID, což je 425. Přísnost je záměrná. Benevolentní parser, který ořízne mezery a krátký vstup doplní nulami, ochotně promění zkrácený paste ze schránky v credential a potom selže někde mnohem hůře čitelně
Var
Lib: TPDFlib;
FileHandle, PageRef: Integer;
Begin
Lib:= TPDFlib.Create;
Try
// KeyHex má 32 hex znaků pro AES-128 R4 a 64 pro AES-256 R6
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;
Ostatní chybové kódy zůstávají odlišné, aby dávková úloha dokázala rozlišit chybu operátora od problémů s důkazním materiálem: 411, když soubor neexistuje, 401, když ho nelze otevřít ke čtení, a 409, když je poškozená cross-reference struktura. Všechno související s klíčem se záměrně slévá do 425, protože vstupní bod pro surový klíč, který prozradí, která část klíče byla špatně, je oracle
Co skutečně dokazuje ověření
PDF Library for Delphi prokáže, že dodaný klíč patří k tomuto dokumentu, pomocí verifieru, který už encryption dictionary obsahuje, a kontrola se podle revize liší. Revize 2 znovu vypočítá RC4 šifrování 32bajtového standardního paddingu a porovná všech 32 bajtů s /U. Revize 3 a 4 zahashují padding společně s file ID, provedou RC4 průchod a 19 kol odvozených přes XOR a porovnají prvních 16 bajtů /U. Revize 5 až 7 dešifrují 16bajtový řetězec /Perms s nulovým IV a současně zkontrolují čtyři nezávislé věci: little-endian permission word proti /P, čtyři bajty FF na pozicích 5 až 8, příznak šifrování metadat jako T nebo F a marker adb na pozicích 10 až 12
Když verifier existuje, ale neodpovídá, otevření je bezpodmínečně odmítnuto. Stojí za to to říct jasně, protože na této záruce stojí celá funkce. Ověření také není důkazem, že vás někdo oprávnil soubor použít: říká, že klíč dešifruje tento soubor, nikoli že máte oprávnění s ním pracovat. Permission word získané z /Perms je důkazem o klíči, nikoli udělením oprávnění, a pokud chcete vědět, co dokument skutečně tvrdí o povolených operacích, je to samostatná práce pro audit šifrování a oprávnění. Problémy s normalizací hesla, například zpracování SASLprep u ne-ASCII hesel AES-256, zde jednoduše nevznikají, protože žádný řetězec nedorazí do hashe
Kdy se uplatní PDF_RAW_KEY_ALLOW_UNVERIFIED
PDF_RAW_KEY_ALLOW_UNVERIFIED pokrývá právě jednu situaci: dokument nemá použitelný verifier, protože /Perms chybí nebo nemá 16 bajtů, případně je /U příliš krátké pro porovnání. Nemůže obejít důkaz, který selhal. Když změníte jednu hex číslici uvnitř /Perms a s nastavenou volbou předáte správný klíč, PDF Library for Delphi stále vrátí 0 a 425. Když s volbou předáte klíč z 32 nulových bajtů proti neporušenému verifieru, odpověď je stejná. Volba uvolňuje absenci důkazu, nikdy rozpor s důkazem. Protože recovery open a ověřené open jsou odlišné epistemické stavy, hlásí se také odděleně místo slévání do návratové hodnoty a DAGetEncryptionKeyValidation přijímá otevřený handle a odpovídá jednou ze tří konstant:
PDF_RAW_KEY_VALIDATION_VERIFIED(1) znamená, že verifier existoval a odpovídalPDF_RAW_KEY_VALIDATION_UNVERIFIED(2) znamená, že klíč byl přijat jen proto, že nebylo možné vyhodnotit žádný verifier a volající si tuto politiku výslovně vyžádalPDF_RAW_KEY_VALIDATION_NONE(0) hlásí běžný handle otevřený heslem
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
(Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
// v tomto souboru není verifier? opakuj pod explicitní recovery politikou
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;
Jen pro čtení z konstrukce a kdo vlastní stream
Vstup pro raw-key soubor vždy otevírá zdroj s fmOpenRead or fmShareDenyWrite a celý řetězec Direct Access označí jako pouze pro čtení, takže DAAppendFile odmítne zápis na místě a vrátí 2 místo pokusu o inkrementální update. Není to politika, o které by šlo handle přesvědčit; nastaví se v konstruktoru ještě před parsováním souboru. Pro práci s důkazy je důležitou vlastností to, že zdrojové bajty jsou po zavření handle bajtově identické, a regresní sada to přesně ověřuje na fixture revize AES-128 4 i AES-256 6. Streamová varianta se při zápisech chová stejně a přidává jedno pravidlo: DAOpenFromStreamWithEncryptionKey nikdy nepřebírá vlastnictví, takže DACloseFile ponechá váš TStream živý a uvolníte ho sami
Source:= TMemoryStream.Create;
Try
Source.LoadFromFile(ArchivePath);
Source.Position:= 0;
FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
If FileHandle<> 0 Then
Try
// 2 = handle jen pro čtení: exportuj jinam, do důkazu nikdy nepřidávej
Assert(Lib.DAAppendFile(FileHandle)= 2);
Harvest(Lib, FileHandle);
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Source.Free; // handle tento stream nikdy nevlastnil
End;
Hygiena klíče a co exportuje DLL
Zavření řetězce přepíše file key, cache hesla i odvozené klíče objektů a dekódovaný klíč se vymaže v bloku Finally samotného vstupního bodu bez ohledu na to, zda open uspěl. Za tím se skrývá drobnost, která zabrala skutečný čas při ladění: každá kopie, která musí přežít, se klonuje explicitně přes SetLength a Move, nikoli přiřazením. Když v Delphi přiřadíte jeden AnsiString druhému, obě jména sdílejí jeden buffer přes copy-on-write, takže vymazání strany volajícího vynuluje klíč, který kryptografický handler stále používá, a dokument se dešifruje na nesmysly z důvodu, který by žádný stack trace nevysvětlil. Hranici DLL překračují jen file-based vstupní body v wide i ANSI podobě společně s accessorrem validačního stavu; varianta TStream zůstává pouze v Delphi, protože závisí na životnosti objektu Delphi a referenční sémantice, které nemají poctivé vyjádření v plochém C ABI. Pokud je váš recovery nástroj klientem DLL, počítejte s přípravou do dočasného souboru a jeho odstraněním pod stejnými kontrolami, jaké používáte pro klíč
S hex klíčem zacházejte jako s credential materiálem a uplatněte pravidla, která byste dali heslu dokumentu, a validační stav ukládejte do logu řetězce custody, aby pozdější čtenář rozlišil ověřenou extrakci od nepotvrzené. Pokud v Delphi vybíráte PDF komponentu pro forenzní práci, hromadnou archivaci nebo migraci DRM, vstupní body pro raw key, záruka jen pro čtení i plocha auditu šifrování patří do téže knihovny a úplný seznam funkcí najdete na produktové stránce PDF Library for Delphi