HotPDF Delphi PDF-komponenten implementerer ISO 32000-1 §7.6.5-modellen for crypt filters som tre uafhængige politikker i stedet for én switch: ConfigureCryptFilterDefaults tildeler string-filteret /StrF, stream-filteret /StmF og embedded-file-filteret /EFF separat, SetStreamCryptFilter tilsidesætter én stream, og GetLoadedCryptFilterInfo rapporterer, hvad en indgående fil erklærer. De fleste interoperabilitetsfejl i krypterede PDF-filer bor i mellemrummene mellem de tre
Her er fejlen, der sender folk ned i dette lag. Et team leverer et dokument, hvor sideindholdet skal være læsbart for et efterfølgende værktøj, men den vedhæftede payload ikke må være det, så de sætter /EFF /StdCF og lader /StmF /Identity stå. Acrobat åbner det fint. En konform tredjepartslæser returnerer vedhæftningen som krypteret skrald, fordi /EFF er en politik på producentsiden om, hvilket filter der gælder for embedded files, og en generel læser stadig opløser en umarkeret stream gennem /StmF. Rettelsen er ikke en anden værdi for /EFF. Rettelsen er et eksplicit /Crypt-filter på selve embedded-file-streamen
Hvad styrer crypt-filterlaget faktisk?
Crypt filters ligger mellem krypteringsalgoritmen og objektgrafen, og de afgør hvilke objekter algoritmen rører, ikke hvordan den virker. /CF-ordbogen inde i krypteringsordbogen mapper navne til filterdefinitioner, der hver har en /CFM-metode, en valgfri /Length og en /AuthEvent. De tre top-level-poster /StrF, /StmF og /EFF vælger derefter, hvilket af disse navngivne filtre der gælder for strings, streams uden et eksplicit filter og embedded files. HotPDF begrænser bevidst, hvad de indbyggede handlere vil skrive. ConfigureCryptFilterDefaults accepterer kun de reserverede navne for den aktive handler: Standard security handler udsender /StdCF eller /Identity, public-key-handleren udsender /DefaultCryptFilter eller /Identity, og alt andet rejser EArgumentException på kaldestedet. Filtre, som eksterne producenter skriver under andre navne, bevares stadig på load-, inspektions- og kompatibilitetsrewrite-stierne, så HotPDF er konservativ som writer og rummelig som reader. To yderligere værn gælder: Kaldet rejser EInvalidOpException, når dokumentserialiseringen er begyndt, og igen hvis dokumentet er i en inkrementel opdatering, fordi krypteringspolitikken ikke kan ændres mellem revisioner af den samme fil
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'wrapper.pdf';
Pdf.OwnerPassword := 'owner-secret';
Pdf.UserPassword := 'open-secret';
Pdf.CryptKeyLength := aes128;
// strings krypteret, sidestreams plaintext, vedhæftninger krypteret
Pdf.ConfigureCryptFilterDefaults('StdCF', 'Identity', 'StdCF');
Pdf.ActivateProtection := True;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(50, 50, 0, 'Visible stream operators');
Pdf.AddDocumentAttachment('payload.bin', 'Encrypted payload');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
En begrænsning er værd at sige på forhånd, fordi den kontrolleres sent og overrasker folk. Navngivne crypt filters i HotPDF kræver dokumentkryptering med aes128, aes256 eller aesgcm. Konfigurér en filterpolitik oven på RC4 k40 eller k128, og valideringspasset, der kører, når kryptering aktiveres, rejser en fejl i stedet for lydløst at opgradere nøgletype. Det er den samme designholdning som resten af AES-256 PDF-krypteringsvejen i Delphi: afvis den tvetydige konfiguration i stedet for at gætte, hvad kaldere mente
Hvorfor betyder /Length-posten to forskellige ting?
Fordi specifikationen definerer den i to forskellige enheder afhængigt af security handleren, og HotPDF skal respektere begge. I en crypt-filter-ordbog, hvor /CFM er /V2, udtrykkes /Length-posten i bytes under Standard security handler og i bits under public-key-handleren. /Length i krypteringsordbogen, som står ved siden af /V (ISO 32000-1 §7.6.2), er altid i bits. Læs en filterordbog med /Length 16, og du har en 128-bit nøgle i en Standard-handler-fil og en afvist fil i en public-key-fil. HotPDF normaliserer dette, når den indlæser den konfiguration, filen deklarerer. Den multiplicerer en /V2-filters /Length med otte, kun når filen ikke er public-key-krypteret, falder tilbage til dokumentniveauets /Length, når filteret ikke har sin egen, og gemmer resultatet i THPDFCryptFilterInfo.KeyLengthBits. AESV2 er låst til 128 bit og AESV3 og AESV4 til 256, eftersom disse metoder ikke har en forhandlbar nøglestørrelse. Den strenge del kommer bagefter: Kun 40-bit og 128-bit /V2 accepteres. Et filter, der opløses til en anden længde, rapporteres som utilgængeligt, og operationen fejler i stedet for at blive rundet til 128 med teorien om, at de fleste producenter nok mente 128 alligevel. Lydløs normalisering af en nøglelængde er sådan, man leverer en fil, der dekrypterer på ens egen maskine og ingen andre steder
var
Reader: THotPDF;
Info: THPDFCryptFilterInfo;
I: Integer;
begin
Reader := THotPDF.Create(nil);
try
Reader.AutoLaunch := False;
if Reader.LoadFromFile('incoming.pdf', 'open-secret') <> 1 then
Exit;
// /StrF og /StmF er som standard Identity; /EFF er som standard /StmF
WriteLn(Reader.LoadedStringCryptFilterName); // StdCF
WriteLn(Reader.LoadedStreamCryptFilterName); // Identity
WriteLn(Reader.LoadedEmbeddedFileCryptFilterName); // StdCF
for I := 0 to Reader.GetLoadedCryptFilterCount - 1 do
if Reader.GetLoadedCryptFilterInfo(I, Info) then
if (Info.Method = hcfmV2) and
not (Info.KeyLengthBits in [40, 128]) then
raise Exception.CreateFmt(
'crypt filter /%s: unsupported V2 key length %d',
[String(Info.Name), Info.KeyLengthBits]);
finally
Reader.Free;
end;
end;
Hvad garanterer /CFM /None, og hvordan adskiller /Identity sig?
De når samme resultat ad forskellige veje, og at blande dem sammen ødelægger opslag. Et navngivet filter, hvis /CFM er /None, og et navngivet filter, der helt udelader /CFM, betyder begge, at dette filter ikke udfører kryptering eller dekryptering — HotPDF mapper den manglende post til None før opløsningen, så begge ender på hcfmNone med en registreret nøglelængde på nul. /Identity er anderledes af sin natur: Det er det reserverede navn, der omgår /CF-opslaget helt, så et dokument kan referere til /Identity uden at definere det noget sted i /CF. PDF-navne skelner mellem store og små bogstaver, hvilket gør endnu en implementeringsdetalje ufravigelig: Intet crypt-filter-opslag må være case-insensitivt. HotPDF opløser navne i /CF-underdictionary, filterets /Length-post og streamens /Type-kontrol gennem case-sensitive dictionary-opslag. En fil, der definerer /stdcf, mens /StmF peger på /StdCF, er malformed, og hvis de to behandles som samme nøgle, forvandles en detekterbar authoring-fejl til en forkert nøgle, der lydløst anvendes på hver stream i dokumentet
Sådan får du /EFF til at hænge fast på embedded-file-streams
Når /EFF adskiller sig fra /StmF, skal embedded-file-streamen have en eksplicit indledende /Crypt-post i dens /Filter og en matchende /DecodeParms-ordbog med /Name på samme array-position. HotPDF regner dette ud pr. stream ved save-tidspunktet: Den detekterer /Type /EmbeddedFile, arver det konfigurerede embedded-file-filter og udsender kun den eksplicitte /Crypt-markør, når det nedarvede navn adskiller sig fra den effektive stream-default. Når /EFF og /StmF stemmer overens, skrives ingen markør, fordi en reader alligevel ville opløse det samme filter. Array-positionen betyder derefter lige så meget som navnet. Når HotPDF læser en stream tilbage, scanner den /Filter efter /Crypt-posten, registrerer dens indeks og slår derefter op på det samme indeks i /DecodeParms-arrayet for at finde /Name. En /Crypt på indeks 0 med parametre på indeks 1 opløses til /Identity, ikke til dit filter. Det er også derfor, at writeren udfylder parameterarrayet med null, når streamen tidligere havde en /Filter men ingen /DecodeParms: Positionerne skal forblive på linje
Der ligger en skarpere fælde under dette. Hvis den eksisterende /Filter eller /DecodeParms er et indirekte objekt — almindeligt i filer fra generatorer, der deler ét filterarray på tværs af mange streams — ville en indsættelse af /Crypt på stedet mutere en delt filtergraf og beskadige hver anden stream, der peger på den. HotPDF opløser det indirekte objekt og kloner det først til et direkte objekt, der kun tilhører streamen, og rydder objekt- og generationsnumrene, så den oprindelige indirekte rod aldrig indlejres i det nye array. For en stream, der allerede brugte ASCIIHexDecode, er det serialiserede resultat /Filter [ /Crypt /ASCIIHexDecode ] med /DecodeParms [ << /Type /CryptFilterDecodeParms /Name /StdCF >> ... ]. Den samme positionsdisciplin styrer enhver anden filterkæde, også dem du går igennem ved ekstraktion af billeder fra en indlæst PDF gennem deres decode-filtre
// Editor har allerede et indlæst dokument, og ContentStream er en
// THPDFStreamObject, hvis /Filter er et indirekte /ASCIIHexDecode-navn
Editor.OwnerPassword := 'owner-secret';
Editor.UserPassword := 'open-secret';
Editor.CryptKeyLength := aes128;
Editor.ConfigureCryptFilterDefaults('StdCF', 'Identity');
Editor.SetStreamCryptFilter(ContentStream, 'StdCF');
Editor.ActivateProtection := True;
Editor.SaveLoadedDocument('out.pdf');
// Et tomt navn rydder override og fjerner den forældede /Crypt-
// post sammen med dens decode-parametre ved næste save
Editor.SetStreamCryptFilter(ContentStream, '');
Editor.SaveLoadedDocument('cleared.pdf');
Arver object streams dokumentets /Encrypt-politik?
Nej, og at antage det er en pålidelig måde at producere skrald på. En object stream skal følge den faktiske /StmF-politik eller sin egen eksplicitte /Crypt-markør: Den blotte tilstedeværelse af en /Encrypt-ordbog gør ikke enhver /ObjStm-container til ciphertext. Et dokument med /StmF /Identity har plaintext-object streams, selv om dets strings er fuldt krypterede, og en decoder, der dekrypterer dem alligevel, sender input til inflate-trinnet, som aldrig var deflate-output
Konsekvensen for medlemsobjekterne er den del, der er værd at læse to gange. Ifølge ISO 32000-1 §7.5.7 er strings inde i en krypteret object stream allerede plaintext, når selve containeren er dekrypteret, så at dekryptere dem igen ville være en dobbelt-dekryptering. HotPDF beskytter dette ved at spørge, om hver type-2-objekts container var krypteret, og springe objektet over, når den var, mens springene tælles i XRefProbeDecryptObjStmSkips som direkte evidens for, at værnet udløstes. Når containeren var plaintext, var medlemsstrengene aldrig dækket af noget, så HotPDF materialiserer disse medlemmer og anvender /StrF på hver enkelt — keyed, som implementeringen faktisk gør det, med medlemsobjektnummer og generation, ikke med nummeret på den indeholdende /ObjStm. Vend dette om i en fil med blandet politik, og hver streng i hvert komprimeret objekt afkodes til støj. Reglerne på containerniveau er dækket yderligere i noterne om PDF object streams og inkrementelle opdateringer
Hvor HotPDF nægter at gætte
Crypt-filter-semantik findes ikke under /V 4, så HotPDF afviser enhver per-stream override i sådan en fil med en eksplicit fejl i stedet for at skrive en /Crypt-markør, som ingen konform reader ville respektere. Det samme gælder på læsesiden: En krypteringsordbog med /V under 4 rydder alle tre indlæste filternavne, fordi der ikke er noget at rapportere. Tre yderligere grænser håndhæves bevidst:
- Et per-stream-filter, der ikke er
Identity, på et public-key-krypteret dokument afvises, fordi en streamspecific politik under public-key-handleren kræver en streamspecific recipient envelope, som HotPDF endnu ikke udsender - Public-key-krypterede embedded files, hvis
/EFFadskiller sig fra den effektive/StmF, afvises af samme grund i stedet for at blive skrevet i en form, der ikke dekrypterer for nogen - Den direkte AES-256-fast path for filer gælder kun, når strings, streams og embedded files alle opløses til den samme crypt-filter-metode, og intet objekt i filen bærer en eksplicit
/Crypt; en blandet politik eller plaintext-metadata tvinger en fallback til den fulde objektgraf-vej
Ingen af disse er performancebeslutninger. De markerer de steder, hvor et forkert gæt giver en PDF, der åbner i én viewer, fejler i en anden og slet ikke giver udvikleren et signal, før en kunde rapporterer det. En afvisning ved ConfigureCryptFilterDefaults eller ved save-tidspunktet koster én exception; en embedded file med lydløst forkert nøgle koster en supportcyklus. Hvis du bygger Delphi- eller C++Builder-software, der producerer eller indlæser krypterede PDF-filer — selektivt plaintext-sideindhold med krypterede vedhæftninger, krypterede payload-wrappers i PDF 2.0 eller interoperabilitet med filer, hvis crypt-filter-politikker du ikke selv valgte — leveres den crypt-filter-API, der er beskrevet her, i den aktuelle HotPDF Delphi PDF-komponent sammen med de krypterings-, object-stream- og inkrementelle opdateringsstier, den bygger på