Teknisk artikel

Åbn krypterede PDF-filer med rå filnøgle i Delphi

PDF Library for Delphi kan åbne en krypteret PDF fra dens rå filkrypteringsnøgle i stedet for en adgangskode. DAOpenFileWithEncryptionKey accepterer nøglen som hexadecimal tekst, kontrollerer den mod den verifier, der allerede ligger i krypteringsordbogen, og returnerer et skrivebeskyttet Direct Access-handle; DAOpenFromStreamWithEncryptionKey gør det samme for en TStream, som kaldere selv ejer. Begge kom i v3.496.0

Scenariet er snævert, men det findes i virkeligheden. En forensics-opgave giver dig en nøgle, der er gendannet fra et memory image, men ingen adgangskode. En stor arkiveringskørsel har ti tusind dokumenter, hvis filnøgler ligger i en escrow-database, fordi det oprindelige DRM-system holdt op med at udstede adgangskoder for flere år siden. En migrering væk fra et pensioneret rights-management-produkt har nøglemateriale og intet andet. I alle disse tilfælde er de legitimationsdata, du har, resultatet af key derivation og ikke inputtet, og ingen password-parameter i APIet kan tage imod dem

Hvorfor er en filkrypteringsnøgle ikke en adgangskode?

En adgangskode og en filkrypteringsnøgle ligger på hver sin side af key derivation i PDF-standardens security handler (ISO 32000-1 §7.6.3). Handleren tager en adgangskode, blander den med /O, /P, fil-IDet og en revisionsspecifik hash og producerer filnøglen. Hvis du sender en filnøgle ind i password-pladsen, bliver den hashed til noget andet nonsens, der igen hashes til noget andet nonsens, og derfor kræver dette sit eget entry point i stedet for et flag på DAOpenFile

Hvor den rå nøgle kan indsættes, er fastlagt af revisionen. Revision 2 til 4 udleder stadig en særskilt nøgle pr. objekt fra filnøglen, objektnummeret, generationsnummeret og for AESV2 AES-saltet, så en filnøgle lader dig slet ikke springe derivation på objektniveau over. Revision 5 til 7 bruger den 32 byte lange filnøgle direkte til AES-256 uden noget pr.-objekt-trin. Det ene lag, der er fælles for begge, er selve filnøglen, så det er det eneste sted, hvor PDF Library for Delphi accepterer en nøgle udefra. Hvis du stadig har adgangskoden, så brug i stedet den almindelige vej, og lad adgangskodens retry-livscyklus håndtere et forkert første forsøg, for raw-key-entry-pointet opgiver bevidst flere bekvemmeligheder, som password-vejen beholder

Hvilket input accepterer DAOpenFileWithEncryptionKey?

Kun ASCII-hex uden præfiks og uden whitespace, med et lige antal tegn, og det afkodede antal bytes skal passe nøjagtigt til krypteringsrevisionen. For revision 2 til 4 kommer den forventede længde fra /Length i krypteringsordbogen: et multiplum af 8 bit mellem 5 og 16 bytes, med 40 bit som standard, når /Length mangler. For revision 5 til 7 er den præcis 32 bytes, uden forhandling. Et 0x-præfiks, et ulige antal cifre, mere end 64 hextegn, en ukendt bit i Options eller et dokument, der slet ikke er krypteret, giver alle samme resultat: et handle på 0 og LastErrorCode sat til PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID, som er 425. Strengheden er pointen. En lempelig parser, der fjerner whitespace og udfylder en for kort inputværdi med nuller, gør med glæde en afkortet clipboard-paste til en credential og fejler derefter et langt mindre tydeligt sted

Var
  Lib: TPDFlib;
  FileHandle, PageRef: Integer;
Begin
  Lib:= TPDFlib.Create;
  Try
    // KeyHex er 32 hextegn for AES-128 R4, 64 for 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;

De øvrige fejlkoder forbliver adskilte, så et batchjob kan skelne operatørfejl fra problemer med evidensen: 411 når filen ikke findes, 401 når den ikke kan åbnes til læsning, 409 når krydsreferencestrukturen er beskadiget. Alt nøglerelateret samles med vilje i 425, fordi et raw-key-entry point, der fortæller hvilken del af nøglen der var forkert, er en oracle

Hvad beviser verificeringen faktisk?

PDF Library for Delphi beviser, at den leverede nøgle hører til dette dokument, ved hjælp af den verifier, som krypteringsordbogen allerede indeholder, og kontrollen varierer efter revision. Revision 2 genberegner RC4-krypteringen af den 32 bytes lange standard-paddingstreng og sammenligner alle 32 bytes med /U. Revision 3 og 4 hasher padding sammen med fil-IDet, kører RC4-trinnet plus de 19 XOR-afledte runder og sammenligner de første 16 bytes af /U. Revision 5 til 7 dekrypterer den 16 bytes lange /Perms-streng med en nul-IV og kontrollerer fire uafhængige ting på én gang: little-endian-rettighedsordet mod /P, de fire FF-bytes på position 5 til 8, flaget for kryptering af metadata som T eller F og markøren adb på position 10 til 12

Når der findes en verifier, men den ikke matcher, afvises åbningen ubetinget. Det er værd at sige klart, fordi det er den garanti, hele funktionen bygger på. Bemærk også, hvad verificering ikke er: Den siger, at nøglen dekrypterer denne fil, ikke at nogen har givet dig tilladelse til at bruge den. Rettighedsordet, der gendannes fra /Perms, er evidens om nøglen og ikke en bevilling, og hvis du vil vide, hvad dokumentet faktisk påstår at tillade, er det en separat opgave for et audit-trin for kryptering og rettigheder. Problemer med normalisering på password-siden, såsom SASLprep-håndtering af non-ASCII AES-256-adgangskoder, opstår ganske enkelt ikke her, eftersom ingen streng nogensinde når en hash

Hvornår gælder PDF_RAW_KEY_ALLOW_UNVERIFIED?

PDF_RAW_KEY_ALLOW_UNVERIFIED dækker præcis én situation: Dokumentet indeholder ingen brugbar verifier, fordi /Perms mangler eller ikke er 16 bytes, eller fordi /U er for kort til at kunne sammenlignes. Det kan ikke tilsidesætte modstridende evidens. Hvis du ødelægger ét hextegn inde i /Perms og sender den korrekte nøgle med indstillingen slået til, returnerer PDF Library for Delphi stadig 0 og 425. Hvis du sender en nøgle på 32 nulbytes mod en intakt verifier med indstillingen slået til, er svaret det samme. Indstillingen lemper på fraværet af bevis, aldrig på en modsigelse af det. Fordi en recovery-åbning og en verificeret åbning er forskellige epistemiske tilstande, rapporteres de også separat i stedet for at blive foldet ind i returværdien, og DAGetEncryptionKeyValidation tager det åbne handle og svarer med én af tre konstanter:

  • PDF_RAW_KEY_VALIDATION_VERIFIED (1) betyder, at en verifier var til stede og matchede
  • PDF_RAW_KEY_VALIDATION_UNVERIFIED (2) betyder, at nøglen kun blev accepteret, fordi ingen verifier kunne evalueres, og kaldere eksplicit bad om denne politik
  • PDF_RAW_KEY_VALIDATION_NONE (0) er det, et handle, der blev åbnet med en almindelig adgangskode, rapporterer
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
   (Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
  // Ingen verifier i denne fil? Prøv igen under en eksplicit recovery-politik
  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;

Skrivebeskyttet fra konstruktionen, og hvem ejer streamen

Raw-key-fil-entry pointet åbner altid kilden med fmOpenRead or fmShareDenyWrite og markerer hele Direct Access-kæden som skrivebeskyttet, så DAAppendFile afviser skrivning på stedet og returnerer 2 i stedet for at forsøge en inkrementel opdatering. Det er ikke en politik, du kan overtale handle til at fravige; den sættes i konstruktøren, før filen overhovedet parses. Ved arbejde med evidens er den egenskab, du ønsker, at kildebytes er byte-identiske efter lukning af handle, og regressionspakken hævder præcis det på både en AES-128-revision 4-fixture og en AES-256-revision 6-fixture. Stream-entry pointet opfører sig på samme måde ved writes og tilføjer én regel: DAOpenFromStreamWithEncryptionKey overtager aldrig ejerskabet, så DACloseFile lader din TStream leve videre, og du frigiver den selv

Source:= TMemoryStream.Create;
Try
  Source.LoadFromFile(ArchivePath);
  Source.Position:= 0;
  FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
  If FileHandle<> 0 Then
  Try
    // 2 = skrivebeskyttet handle: eksporter et andet sted, tilføj aldrig til evidensen
    Assert(Lib.DAAppendFile(FileHandle)= 2);
    Harvest(Lib, FileHandle);
  Finally
    Lib.DACloseFile(FileHandle);
  End;
Finally
  Source.Free;   // handle overtog aldrig ejerskabet af denne stream
End;

Nøglehygiejne, og hvad DLL'en eksporterer

Når kæden lukkes, overskrives filnøglen, password-cachen og de afledte objektnøgler, og den afkodede nøgle slettes i Finally-blokken i selve entry pointet, uanset om åbningen lykkedes eller ej. Der ligger en detalje bag dette, som kostede reel fejlsøgningstid: Enhver kopi, der skal overleve, klones eksplicit med SetLength plus Move i stedet for at blive tildelt. Hvis du tildeler én AnsiString til en anden i Delphi, deler begge navne én buffer under copy-on-write, så sletning på kaldersiden ville nulstille den nøgle, crypt-handleren stadig bruger, og dokumentet ville dekryptere til skrald af grunde, som ingen stack trace ville forklare. Kun de filbaserede entry points krydser DLL-grænsen i wide- og ANSI-former sammen med accessoren til valideringsstatus; TStream-varianten forbliver Delphi-only, fordi den afhænger af Delphi-objektets levetid og referencesemantik, som ikke har en ærlig repræsentation i en flad C-ABI. Hvis dit recovery-værktøj er en DLL-klient, så planlæg at stage til en midlertidig fil og slette den under de samme kontroller, som du anvender på nøglen

Behandl hexnøglen som credential-materiale med de håndteringsregler, du ville give en dokumentadgangskode, og gem valideringsstatussen i den log, som din chain of custody producerer, så en senere læser kan skelne en verificeret ekstraktion fra en uden attest. Hvis du evaluerer en Delphi PDF-komponent til forensics, massearkivering eller en DRM-migrering, er raw-key-entry points, garantien om skrivebeskyttelse og krypteringsauditfladen alle dele af det samme bibliotek, og du kan læse hele funktionslisten på produktsiden for PDF Library for Delphi