PDF Library for Delphi può aprire un PDF cifrato dalla sua raw file encryption key invece che da una password. DAOpenFileWithEncryptionKey accetta la chiave come testo esadecimale, la verifica contro il verifier già memorizzato nell'encryption dictionary e restituisce un handle Direct Access read-only; DAOpenFromStreamWithEncryptionKey fa lo stesso per un TStream posseduto dal caller. Entrambi sono arrivati nella v3.496.0
Lo scenario è circoscritto, ma reale. Un incarico forense consegna una chiave recuperata da un memory image e nessuna password. Un job di archiviazione di massa ha diecimila documenti le cui file key sono in un database di escrow perché il sistema DRM d'origine ha smesso di emettere password anni fa. Una migrazione da un prodotto di rights management dismesso ha il materiale delle chiavi e nient'altro. In tutti questi casi la credenziale disponibile è l'output della derivazione della chiave, non l'input, e nessun parametro password dell'API può accettarla
Perché una file encryption key non è una password?
Una password e una file encryption key si trovano ai lati opposti della derivazione della chiave nel security handler standard PDF (ISO 32000-1 §7.6.3). L'handler prende una password, la combina con /O, /P, il file ID e un hash specifico della revisione, e produce la file key. Inserire una file key nello slot della password produce nonsense hashato in altro nonsense, ed è per questo che serve un entry point dedicato invece di un flag su DAOpenFile
Il punto in cui si può iniettare la raw key è definito dalla revisione. Le revisioni da 2 a 4 derivano ancora una chiave distinta per oggetto dalla file key, dal numero dell'oggetto, dal numero di generazione e, per AESV2, dal sale AES, quindi possedere la file key non consente affatto di saltare la derivazione a livello di oggetto. Le revisioni da 5 a 7 usano direttamente la file key di 32 byte per AES-256, senza un passaggio per oggetto. L'unico layer comune a entrambe è la file key, ed è l'unico punto in cui PDF Library for Delphi accetta una chiave fornita dall'esterno. Se avete ancora la password, restate invece sul percorso normale e lasciate che il ciclo di retry della password gestisca un primo tentativo errato, perché l'entry point raw-key rinuncia deliberatamente a diverse comodità mantenute dal percorso con password
Quale input accetta DAOpenFileWithEncryptionKey?
Solo hex ASCII senza prefisso e senza spazi, di lunghezza pari, e il numero di byte decodificati deve corrispondere esattamente alla revisione di cifratura. Per le revisioni da 2 a 4 la lunghezza attesa proviene da /Length nell'encryption dictionary: un multiplo di 8 bit compreso tra 5 e 16 byte, con default a 40 bit quando /Length è assente. Per le revisioni da 5 a 7 è esattamente 32 byte, senza negoziazione. Un prefisso 0x, un numero dispari di cifre, più di 64 caratteri hex, un bit sconosciuto in Options o un documento non cifrato producono tutti lo stesso esito: handle 0 e LastErrorCode impostato a PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID, cioè 425. La rigidità è voluta. Un parser permissivo che elimina gli spazi e riempie con zeri l'input corto trasformerebbe volentieri un incolla troncato dagli appunti in una credenziale, per poi fallire in un punto molto meno leggibile
Var
Lib: TPDFlib;
FileHandle, PageRef: Integer;
Begin
Lib:= TPDFlib.Create;
Try
// KeyHex contiene 32 caratteri hex per AES-128 R4, 64 per 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;
Gli altri codici di errore restano distinti, così un job batch può distinguere l'errore dell'operatore dai problemi dell'evidenza: 411 quando il file non esiste, 401 quando non può essere aperto in lettura, 409 quando la struttura cross-reference è danneggiata. Tutto ciò che riguarda la chiave converge in 425, volutamente, perché un entry point raw-key che segnala quale parte della chiave era errata sarebbe un oracle
Cosa dimostra davvero la verifica?
PDF Library for Delphi dimostra che la chiave fornita appartiene a questo documento, usando il verifier già presente nell'encryption dictionary, e il controllo cambia in base alla revisione. La revisione 2 ricalcola la cifratura RC4 della stringa standard di padding da 32 byte e confronta tutti i 32 byte con /U. Le revisioni 3 e 4 sottopongono il padding insieme al file ID all'hash, eseguono il passaggio RC4 più i 19 round derivati da XOR e confrontano i primi 16 byte di /U. Le revisioni da 5 a 7 decifrano la stringa /Perms da 16 byte con IV zero e controllano contemporaneamente quattro aspetti indipendenti: la permission word little-endian contro /P, i quattro byte FF nelle posizioni da 5 a 8, il flag encrypt-metadata come T o F e il marker adb nelle posizioni da 10 a 12
Quando esiste un verifier ma non corrisponde, l'apertura viene rifiutata senza eccezioni. Vale la pena dirlo chiaramente perché è la garanzia su cui poggia tutta la funzionalità. Notate anche cosa non è la verifica: dice che la chiave decifra questo file, non che qualcuno vi abbia autorizzato a usarla. La permission word recuperata da /Perms è un'evidenza sulla chiave, non un'autorizzazione, e per sapere cosa il documento dichiara davvero di consentire serve un lavoro distinto: un passaggio di audit di cifratura e permessi. I problemi di normalizzazione lato password, come la gestione SASLprep delle password AES-256 non ASCII, qui non si presentano semplicemente perché nessuna stringa arriva a un hash
Quando si applica PDF_RAW_KEY_ALLOW_UNVERIFIED?
PDF_RAW_KEY_ALLOW_UNVERIFIED copre esattamente una situazione: il documento non contiene un verifier utilizzabile, perché /Perms manca o non è lungo 16 byte, oppure /U è troppo corto per il confronto. Non può ignorare un'evidenza che fallisce. Corrompete una cifra hex dentro /Perms e passate la chiave corretta con l'opzione impostata: PDF Library for Delphi restituisce comunque 0 e 425. Passate una chiave composta da 32 byte zero contro un verifier integro con l'opzione impostata: la risposta è la stessa. L'opzione allenta l'assenza di prova, mai una contraddizione della prova. Poiché un'apertura di recupero e un'apertura verificata sono stati epistemici diversi, vengono segnalati separatamente invece di essere fusi nel valore di ritorno, e DAGetEncryptionKeyValidation riceve l'handle aperto e risponde con una delle tre costanti:
PDF_RAW_KEY_VALIDATION_VERIFIED(1) significa che un verifier era presente e corrispondevaPDF_RAW_KEY_VALIDATION_UNVERIFIED(2) significa che la chiave è stata accettata solo perché non era possibile valutare alcun verifier e il caller aveva richiesto esplicitamente quella policyPDF_RAW_KEY_VALIDATION_NONE(0) è ciò che segnala un handle aperto con una normale password
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
(Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
// nessun verifier in questo file? riprova con una policy di recupero esplicita
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;
Read-only per costruzione e chi possiede lo stream
L'entry point raw-key per file apre sempre la sorgente con fmOpenRead or fmShareDenyWrite e marca read-only l'intera catena Direct Access, così DAAppendFile rifiuta la scrittura in-place e restituisce 2 invece di tentare un aggiornamento incrementale. Non è una policy da cui si possa convincere l'handle a uscire; viene impostata nel costruttore, prima ancora che il file venga analizzato. Per il lavoro probatorio la proprietà desiderata è che i byte della sorgente siano identici byte per byte dopo la chiusura dell'handle, e la suite di regressione lo verifica esattamente sia su un fixture AES-128 revisione 4 sia su uno AES-256 revisione 6. L'entry point per stream si comporta nello stesso modo sulle scritture e aggiunge una regola: DAOpenFromStreamWithEncryptionKey non prende mai la proprietà dello stream, quindi DACloseFile lascia vivo il vostro TStream e sarete voi a liberarlo
Source:= TMemoryStream.Create;
Try
Source.LoadFromFile(ArchivePath);
Source.Position:= 0;
FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
If FileHandle<> 0 Then
Try
// 2 = handle read-only: esporta altrove, non aggiungere mai all'evidenza
Assert(Lib.DAAppendFile(FileHandle)= 2);
Harvest(Lib, FileHandle);
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Source.Free; // l'handle non ha mai posseduto questo stream
End;
Igiene della chiave e cosa esporta la DLL
La chiusura della catena sovrascrive la file key, la cache delle password e le object key derivate, e la chiave decodificata viene azzerata nel blocco Finally dello stesso entry point, indipendentemente dal successo dell'apertura. Dietro c'è una sottigliezza che ha richiesto tempo reale di debugging: ogni copia che deve sopravvivere viene clonata esplicitamente con SetLength più Move, invece di essere assegnata. Assegnate una AnsiString a un'altra in Delphi e i due nomi condividono un buffer in copy-on-write, quindi azzerare il lato del caller azzererebbe la chiave ancora usata dal gestore crittografico e il documento verrebbe decifrato in spazzatura per ragioni che nessuno stack trace saprebbe spiegare. Solo gli entry point basati su file attraversano il confine DLL, nelle forme wide e ANSI, insieme all'accessor dello stato di validazione; la variante TStream resta solo Delphi, perché dipende dal lifetime degli oggetti Delphi e da semantiche di riferimento che non hanno una rappresentazione onesta in una C ABI piatta. Se il vostro strumento di recupero è un client DLL, pianificate lo staging in un file temporaneo e la sua cancellazione sotto gli stessi controlli applicati alla chiave
Trattate la chiave hex come materiale credenziale, con le regole di gestione che usereste per la password di un documento, e conservate lo stato di validazione nel log prodotto dalla vostra catena di custodia, così un lettore successivo potrà distinguere un'estrazione verificata da una non attestata. Se state valutando un componente PDF Delphi per attività forensi, archiviazione di massa o migrazione DRM, gli entry point raw-key, la garanzia read-only e la superficie di audit della cifratura fanno parte della stessa libreria, e l'elenco completo delle funzionalità è nella pagina prodotto PDF Library for Delphi