HotPDF Delphi PDF-komponenten implementerar krypteringsfiltermodellen i ISO 32000-1 §7.6.5 som tre oberoende policyer i stället för en enda växel: ConfigureCryptFilterDefaults tilldelar strängfiltret /StrF, strömfiltret /StmF och filtret för inbäddade filer /EFF separat, SetStreamCryptFilter åsidosätter en enskild ström och GetLoadedCryptFilterInfo rapporterar vad en inkommande fil deklarerar. De flesta interoperabilitetsfel för krypterade PDF-filer bor i mellanrummen mellan de tre
Här är felet som skickar människor in i det här lagret. Ett team levererar ett dokument där sidinnehållet måste förbli läsbart för ett nedströmsverktyg men den bifogade nyttolasten inte får vara det, så de ställer in /EFF /StdCF och lämnar /StmF /Identity. Acrobat öppnar det utan problem. En konform tredjepartsläsare lämnar tillbaka bilagan som chiffertextskräp, eftersom /EFF är en producentpolicy om vilket filter som gäller för inbäddade filer och en allmän läsare fortfarande löser en omärkt ström genom /StmF. Lösningen är inte ett annat /EFF-värde. Lösningen är en uttrycklig /Crypt-filterpost på själva strömmen för den inbäddade filen
Vad styr krypteringsfilterlagret egentligen?
Krypteringsfilter ligger mellan krypteringsalgoritmen och objektgrafen, och de avgör vilka objekt algoritmen berör, inte hur den fungerar. Ordboken /CF i krypteringsordboken mappar namn till filterdefinitioner, där varje definition bär på en metod /CFM, en valfri /Length och en /AuthEvent. De tre posterna på toppnivå, /StrF, /StmF och /EFF, väljer sedan vilket av dessa namngivna filter som gäller för strängar, strömmar utan ett explicit filter och inbäddade filer. HotPDF begränsar medvetet vad de inbyggda hanterarna får skriva. ConfigureCryptFilterDefaults accepterar endast de reserverade namnen för den aktiva hanteraren: standardhanteraren för säkerhet avger /StdCF eller /Identity, hanteraren med publik nyckel avger /DefaultCryptFilter eller /Identity och allt annat ger EArgumentException på anropsstället. Filter som externa producenter har skrivit under andra namn bevaras fortfarande genom sökvägarna för inläsning, inspektion och kompatibilitetsomskrivning, så HotPDF är konservativ som skrivare och tillåtande som läsare. Två ytterligare skydd gäller: anropet ger EInvalidOpException när dokumentserialiseringen väl har börjat och igen om dokumentet befinner sig i en inkrementell uppdatering, eftersom krypteringspolicyn inte kan ändras mellan revisioner av samma 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;
// strängar krypterade, sidströmmar i klartext, bilagor krypterade
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 är värd att ange först, eftersom den kontrolleras sent och överraskar människor. Namngivna krypteringsfilter i HotPDF kräver dokumentkryptering med aes128, aes256 eller aesgcm. Konfigurera en filterpolicy ovanpå RC4 k40 eller k128 och valideringspasset som körs när krypteringen aktiveras ger ett fel i stället för att tyst höja nyckeltypen. Det är samma hållning som i resten av AES-256-krypteringsvägen för PDF i Delphi: avvisa den tvetydiga konfigurationen i stället för att gissa vad anroparen menade
Varför betyder posten /Length två olika saker?
Eftersom specifikationen definierar den i två olika enheter beroende på säkerhetshanteraren, och HotPDF måste respektera båda. I en krypteringsfilterordbok vars /CFM är /V2 uttrycks posten /Length i byte under standardhanteraren och i bit under hanteraren med publik nyckel. Krypteringsordbokens /Length som ligger bredvid /V (ISO 32000-1 §7.6.2) är alltid i bitar. Läs en filterordbok med /Length 16 och du har en 128-bitarsnyckel i en fil med standardhanterare och en avvisad fil i en med publik nyckel. HotPDF normaliserar detta när den fångar den inlästa konfigurationen. Den multiplicerar /Length i ett /V2-filter med åtta endast när filen inte är krypterad med publik nyckel, faller tillbaka till dokumentnivåns /Length när filtret saknar en egen och lagrar resultatet i THPDFCryptFilterInfo.KeyLengthBits. AESV2 låses till 128 bitar och AESV3 och AESV4 till 256, eftersom dessa metoder saknar förhandlingsbar nyckelstorlek. Den strikta delen kommer sedan: endast 40-bitars och 128-bitars /V2 accepteras. Ett filter som löser till någon annan längd rapporteras som otillgängligt och operationen misslyckas, i stället för att rundas till 128 på teorin att de flesta producenter ändå menade 128. Att tyst normalisera en nyckellängd är så man levererar en fil som dekrypteras på den egna maskinen och ingen annanstans
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 och /StmF har standardvärdet Identity; /EFF har standardvärdet /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;
Vad garanterar /CFM /None, och hur skiljer sig /Identity?
De når samma resultat via olika vägar, och att blanda ihop dem bryter uppslagningen. Ett namngivet filter vars /CFM är /None och ett namngivet filter som helt saknar /CFM betyder båda att filtret inte utför någon kryptering eller dekryptering — HotPDF mappar den saknade posten till None före upplösningen, så båda hamnar på hcfmNone med en registrerad nyckellängd på noll. /Identity är annorlunda till sin natur: det är det reserverade namnet som kringgår uppslagningen i /CF helt, så ett dokument kan referera till /Identity utan att definiera det någonstans i /CF. PDF-namn är skiftlägeskänsliga, vilket gör en ytterligare implementationdetalj icke-förhandlingsbar: ingen uppslagning av krypteringsfilter får vara skiftlägesokänslig. HotPDF löser namn i underordboken /CF, filtrets /Length-post och kontrollen av strömmens /Type genom skiftlägeskänsliga uppslagningar. En fil som definierar /stdcf medan /StmF pekar på /StdCF är felaktig, och att behandla de två som samma nyckel skulle förvandla ett upptäckbart författarfel till en felaktig nyckel som tyst tillämpas på varje ström i dokumentet
Få /EFF att fastna på strömmar för inbäddade filer
När /EFF skiljer sig från /StmF behöver strömmen för den inbäddade filen en uttrycklig inledande /Crypt-post i dess /Filter och en matchande /DecodeParms-ordbok med /Name på samma arrayposition. HotPDF räknar ut detta per ström vid sparandet: den upptäcker /Type /EmbeddedFile, ärver det konfigurerade filtret för inbäddade filer och skriver den uttryckliga /Crypt-markören endast när det ärvda namnet skiljer sig från strömmens effektiva standardfilter. När /EFF och /StmF överensstämmer skrivs ingen markör, eftersom en läsare ändå skulle lösa samma filter. Arraypositionen betyder då lika mycket som namnet. När HotPDF läser tillbaka en ström skannar den /Filter efter /Crypt-posten, registrerar dess index och slår sedan upp samma index i /DecodeParms-arrayen för att hitta /Name. En /Crypt på index 0 tillsammans med parametrar på index 1 löser till /Identity, inte till ditt filter. Det är också varför skrivaren fyller parameterarrayen med null när strömmen tidigare hade ett /Filter men inget /DecodeParms: positionerna måste fortsätta vara synkroniserade
Det finns en skarpare fälla under detta. Om den befintliga /Filter- eller /DecodeParms-posten är ett indirekt objekt — vanligt i filer från generatorer som delar en filterarray mellan många strömmar — skulle en insättning av /Crypt på plats mutera en delad filtergraf och förstöra varje annan ström som pekar på den. HotPDF löser det indirekta objektet och klonar det först till ett strömprivat direkt objekt, rensar objekt- och generationsnumren så att den ursprungliga indirekta roten aldrig bäddas in i den nya arrayen. För en ström som redan använde ASCIIHexDecode blir det serialiserade resultatet /Filter [ /Crypt /ASCIIHexDecode ] med /DecodeParms [ << /Type /CryptFilterDecodeParms /Name /StdCF >> ... ]. Samma positionsdisciplin styr varje annan filterkedja, inklusive dem du går igenom när du extraherar bilder från en inläst PDF genom deras avkodningsfilter
// Editorn har redan ett inläst dokument, och ContentStream är en
// THPDFStreamObject vars /Filter är ett indirekt /ASCIIHexDecode-namn
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');
// Ett tomt namn tar bort åsidosättningen och strippar den gamla /Crypt-
// inlägget tillsammans med dess avkodningsparametrar vid nästa sparning
Editor.SetStreamCryptFilter(ContentStream, '');
Editor.SaveLoadedDocument('cleared.pdf');
Ärver objektströmmar dokumentets /Encrypt-policy?
Nej, och att anta det är ett säkert sätt att skapa skräp. En objektström måste följa den faktiska /StmF-policyn eller sin egen uttryckliga /Crypt-markör: själva förekomsten av en /Encrypt-ordbok gör inte varje /ObjStm-behållare till chiffertext. Ett dokument med /StmF /Identity har klartext i objektströmmarna även om dess strängar är helt krypterade, och en avkodare som dekrypterar dem ändå matar inflate-steget med indata som aldrig var deflate-utdata
Konsekvensen för medlemsobjekten är delen som är värd att läsa två gånger. Enligt ISO 32000-1 §7.5.7 är strängar inne i en krypterad objektström redan klartext när själva behållaren har dekrypterats, så att dekryptera dem igen skulle vara en dubbeldekryptering. HotPDF skyddar mot det genom att fråga om varje objekt av typ 2 om dess behållare var krypterad och hoppa över objektet när den var det, med överhoppen räknade i XRefProbeDecryptObjStmSkips som direkt bevis på att skyddet aktiverades. När behållaren var klartext var medlemssträngarna aldrig täckta av något, så HotPDF materialiserar dessa medlemmar och tillämpar /StrF på var och en — med nyckel enligt den faktiska implementationen genom medlemsobjektets nummer och generation, inte genom den innehållande /ObjStm-objektnumret. Vänd på detta i en fil med blandad policy och varje sträng i varje komprimerat objekt avkodas till brus. Reglerna på behållarnivå täcks mer utförligt i anteckningarna om PDF-objektströmmar och inkrementella uppdateringar
Var HotPDF vägrar gissa
Krypteringsfiltersemantik finns inte under /V 4, så HotPDF avvisar varje åsidosättning per ström i en sådan fil med ett uttryckligt fel i stället för att skriva en /Crypt-markör som ingen konform läsare skulle respektera. Samma sak gäller vid läsning: en krypteringsordbok med /V under 4 rensar alla tre inlästa filternamn, eftersom det inte finns något där att rapportera. Tre ytterligare gränser upprätthålls avsiktligt:
- Ett filter per ström som inte är
Identityi ett dokument krypterat med publik nyckel avvisas, eftersom en strömspecifik policy under hanteraren med publik nyckel kräver ett strömspecifikt mottagarkuvert som HotPDF ännu inte avger - Inbäddade filer krypterade med publik nyckel där
/EFFskiljer sig från det effektiva/StmFavvisas av samma anledning, i stället för att skrivas i en form som ingen kan dekryptera - Den direkta filvägen för AES-256 gäller endast när strängar, strömmar och inbäddade filer alla löser till samma krypteringsfiltermetod och inget objekt i filen bär en uttrycklig
/Crypt; en blandad policy eller klartextmetadata tvingar fram en reservväg genom hela objektgrafen
Inget av detta är prestandabeslut. De markerar platser där en felaktig gissning ger en PDF som öppnas i en visare, misslyckas i en annan och inte ger utvecklaren någon signal alls förrän en kund rapporterar det. Ett avslag vid ConfigureCryptFilterDefaults eller vid sparandet kostar ett undantag; en tyst felnycklad inbäddad fil kostar en supportcykel. Om du bygger Delphi- eller C++Builder-programvara som producerar eller konsumerar krypterad PDF — selektivt klartextinnehåll på sidor med krypterade bilagor, krypterade nyttolastkuvert för PDF 2.0 eller interoperabilitet med filer vars krypteringsfilterpolicyer du inte valde — levereras API:et för krypteringsfilter som beskrivs här i den aktuella HotPDF Delphi PDF-komponenten tillsammans med vägarna för kryptering, objektströmmar och inkrementella uppdateringar som den bygger på