Teknisk artikel

Revision af PDF-kryptering og tilladelser i Delphi med PDF Library for Delphi

Et tilladelsesflag er ikke en sikkerhedsmekanisme. Bitten, der siger "ingen kopiering", bor i den samme /Encrypt-ordbog som kryptografien, hvilket giver den et skær af håndhævelse, den ikke har, og i det øjeblik du behandler de to som én ting, begynder din revision at producere forkerte svar. Det eneste spørgsmål, der er værd at stille en PDF, er ikke "er den krypteret". Det er mere præcist og sværere: hvilken algoritme, hvilken revision af security handleren, hvilken af de to adgangskoder der blev sat, hvilke tilladelsesbits der påstås, og hvilke dele af filen krypteringen rent faktisk rører. En fil kan være formelt krypteret og i praksis åben. Den kan nægte at lade sig læse og alligevel efterlade sine metadata i klartekst. Den kan spærre for udskrivning i et flag, som enhver fremviser frit kan ignorere. At revidere en PDF betyder at afklare alle de ting hver for sig, og PDF Library for Delphi, losLabs PDF-motor til Delphi og C++Builder, blotlægger dem hver især gennem både et fladt API med heltals-handles og et typet klasselag

Hvad /Encrypt-ordbogen faktisk registrerer

ISO 32000-1 §7.6 definerer dokumentsikkerhed gennem en håndfuld ordbogsposter, og PDF Library for Delphi afspejler dem én til én i posttypen TPDFEncryption. Filterversionen V og revisionen R vælger algoritmefamilien. Length bærer nøglestørrelsen. Tilladelsesbits ligger i P, valideringsstrengene for ejer- og brugeradgangskode i O og U (med OE og UE tilføjet for AES-256), et EncryptMetadata-flag følger med, og tre yderligere felter navngiver de crypt-filtre, der anvendes på henholdsvis strenge, streams og indlejrede filer

Værdien af denne post er, at den ikke fortolker noget for dig. Den rækker den rå ordbog tilbage og lader dig drage konklusionerne, hvilket er præcis, hvad en revision har brug for. Tilfældet med klartekst inde i noget krypteret dukker op i StringFilterIdentity og StreamFilterIdentity: når den ene af dem er sand, passerer de tilsvarende data urørt gennem Identity-filteret, uanset hvad dokumentets krypteringsstatus melder. En scanner, der stopper ved "der findes en /Encrypt-ordbog", vil kalde sådan en fil beskyttet, mens dens strenge og streams ligger i det åbne. Den samme nuance styrer metadata. Når EncryptMetadata er falsk, forbliver XMP-pakken læsbar for enhver indekserer, mens sideindholdet ikke er det, og det er værd at vide i samme sekund dine routing-regler afhænger af et titel- eller forfatterfelt

PDF Library for Delphi-diagram over PDF /Encrypt-ordbogsfelterne afbildet til TPDFEncryption-auditegenskaber, inklusive Identity-cryptfilterfælder
Hver /Encrypt-post afbildes på et TPDFEncryption-felt, og Identity-filter-flaggene afslører, hvilke strenge, streams eller metadata forbliver læsbare uanset krypteringstilstand

En kort sikkerhedssondering med det flade API

For de fleste pipelines besvarer fire flade kald hverdagens spørgsmål. LoadFromFile returnerer 1 ved succes, og når først dokumentet er åbent, rapporterer krypteringsinspektørerne ud fra dets dekrypterede tilstand:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', UserPassword) <> 1 then
      raise Exception.Create('Open failed: wrong password or damaged file');
    Writeln('status    : ', PDF.EncryptionStatus);     // dekrypteret / krypteret / ukendt
    Writeln('algorithm : ', PDF.EncryptionAlgorithm);  // RC4 kontra AES-familien
    Writeln('strength  : ', PDF.EncryptionStrength);   // nøglelængdeklasse
    Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
  finally
    PDF.Free;
  end;
end;

CheckPassword betyder mere, end dens enkeltlinjede signatur antyder. PDF definerer to adgangskoder med ulige magt. Brugeradgangskoden kræves for overhovedet at åbne filen. Ejeradgangskoden giver fulde rettigheder og tilsidesætter hver eneste tilladelsesbit. Bytes på disken er de samme i begge tilfælde, men en session åbnet med ejeradgangskoden kan gøre ting, en brugeradgangskode-session ikke kan, så en revision, der ikke noterer, hvilken legitimation der blev fremvist, noterer den halve sandhed. Klasselaget gør skellet mulig at forespørge på. TPDFDocument.HasUserPassword og HasOwnerPassword melder, hvad filen kræver, mens IsUserPassword og IsOwnerPassword melder, hvilken adgangskode der faktisk åbnede den aktuelle session. Log den kendsgerning. Log aldrig selve adgangskodeværdierne

Strength-stigen, hvor "AES-256" betyder to ting

De flade funktioner Encrypt og EncryptFile tager et heltal Strength med fem meningsfulde værdier: 0 for 40-bit RC4, 1 for 128-bit RC4, 2 for 128-bit AES læsbar fra Acrobat 7, 3 for 256-bit AES som indført med Acrobat 9, og 4 for 256-bit AES som krævet af Acrobat X og senere

Det interessante er, at 3 og 4 begge er mærket AES-256 og ikke er samme ordning. Strength 3 svarer til security handler revision 5, et foreløbigt design, som Acrobat 9 leverede, og som ISO aldrig overtog. Strength 4 svarer til revision 6, hvis nøgleafledningsfunktion blev hærdet og standardiseret i ISO 32000-2. For et dokument, du opretter i dag, er der ingen grund til at vælge 3 frem for 4. For en revision er forskellen afgørende: en politik, der lyder "AES-256 efter ISO 32000-2", opfyldes af R6 alene, og en R5-fil, der kalder sig AES-256, dumper den politik, mens den består et naivt styrketjek. Klasselaget holder de to adskilt ved navn, esAES256Bit for R5 over for esAES256BitAcroX for R6, og egenskaben EncryptionAcroX besvarer revisionsspørgsmålet med en enkelt boolean

PDF-krypteringsstyrkestige fra 40-bit RC4 til AES-256 revision 5 versus revision 6 til Delphi-audits
Styrker 3 og 4 kaldes begge AES-256, men kun revision 6 opfylder en ISO 32000-2-politik, så skal audits registrere handler-revisionen frem for kun etiketten

Tilladelsesbits og det med småt om nøglelængde

EncodePermissions pakker otte flag ind i det heltal, som Encrypt og EncryptFile forventer. Udskrivning, kopiering, ændring og tilføjelse af noter udgør basissættet; udfyldning af felter, kopiering til tilgængelighed, samling og udskrivning i fuld kvalitet udgør det udvidede sæt. Det med småt, som bibliotekets egen krypteringsdemo siger lige ud, er, at de fire udvidede kun træder i kraft ved 128-bit styrke og opefter. Flaget for udskrivning i fuld kvalitet falder under samme regel: ryd det for at gennemtvinge udskrivning i lav opløsning, og et 40-bit-dokument vil ignorere dig, for den nedgradering kræver også 128-bit eller stærkere kryptering. Kod en politik om "kun udskrivning i lav opløsning" ind i en 40-bit-fil, og enhver fremviser udskriver alligevel i fuld kvalitet

Det dybere spørgsmål er, hvem der overhovedet håndhæver de bits, og svaret er ingen, du kan stole på. Tilladelser er instruktioner til overholdende læsere, ikke kryptografiske begrænsninger. Dekrypteringsnøglen er den samme, uanset om kopiering er tilladt eller nægtet, så et låst tilladelsessæt holder kun ærlige fremvisere ærlige. En læser, der vælger at ignorere de bits, møder slet ingen kryptografisk forhindring. Er forpligtelsen at forhindre udtræk snarere end at fraråde det, har filen brug for en brugeradgangskode, og arbejdsgangen har brug for kontroller på procesniveau omkring den, og en revisionsrapport bør navngive, hvilket af de to regimer hver fil faktisk hører under, i stedet for at behandle et tilladelsesflag som en lås

At sætte politik og bevise, at den holdt

At anvende kryptering på eksisterende filer kræver ikke, at de indlæses i objekttræet. EncryptFile behandler input til output i ét enkelt kald, og revisionsløkken genåbner resultatet for at bekræfte, hvad der landede på disken. Den medfølgende krypteringsdemo følger samme skriv-og-læs-tilbage-mønster:

var
  PDF: TPDFlib;
  R: Integer;
begin
  PDF := TPDFlib.Create;
  try
    R := PDF.EncryptFile('in.pdf', 'out.pdf', 'owner-secret', 'user-secret', 4,
      PDF.EncodePermissions(1, 0, 0, 0,    // udskrivning tilladt; kopiering/ændring/noter nægtet
                            0, 0, 0, 1));  // udvidet sæt: kun udskrivning i fuld kvalitet
    if (R = 1) and (PDF.LoadFromFile('out.pdf', 'user-secret') = 1) then
    begin
      Writeln('algorithm = ', PDF.EncryptionAlgorithm);
      Writeln('strength  = ', PDF.EncryptionStrength);
      Writeln('owner pw accepted: ', PDF.CheckPassword('owner-secret'));
    end;
  finally
    PDF.Free;
  end;
end;

Teams, der arbejder på dokumentlaget, får den samme operation med typede mængder i stedet for bit-pakning, og det overlever et code review med langt mindre skelen:

if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
  [ppCanPrint], [ppCanPrintFull]) then
  raise Exception.Create('Encryption failed');

Uanset hvad er tilbagelæsningstrinnet ikke valgfri ceremoni. Det fanger de udrulningsfejl, der ellers dukker op måneder senere på en kundes maskine: en gammel biblioteksbuild, der stiltiende nedgraderer den ønskede styrke, en outputsti, der aldrig blev skrevet, fordi mappen var skrivebeskyttet, et tilladelsesheltal, hvis argumenter kom ind i forkert rækkefølge. Alle tre består en lokal smoke test og fejler ude i marken, og en genåbning af outputtet gør hver af dem til en undtagelse, du ser under selve den kørsel, der skabte filen. GetEncryptionFingerprint returnerer en kompakt værdi, du kan gemme sammen med jobposten, så en senere sammenligning kan afgøre, om to output deler samme krypteringskonfiguration, uden at nogen af dem skal genåbnes

Falske positiver i revisionen, som det er værd at kode efter

Nogle få mønstre skubber pålideligt sikkerhedsscannere frem til den forkerte konklusion, og hver af dem stammer fra at klappe et flerdelt spørgsmål sammen til et ja-eller-nej-svar. Identity-crypt-filteret er det reneste eksempel. Der findes en /Encrypt-ordbog, filen melder sig som krypteret, og alligevel løber strengene og streams uændret gennem Identity-filteret, så det egentlige indhold er klartekst. Løsningen er at læse StringFilterIdentity og StreamFilterIdentity, før noget som helst erklæres beskyttet

Opdelingen omkring metadata er finere. EncryptMetadata kan være uenig med resten af dokumentet i begge retninger og efterlade en krypteret fil med en læsbar XMP-pakke eller, sjældnere, det omvendte. "Filen er krypteret" siger intet om, hvorvidt dens metadata er det, og det betyder noget i det øjeblik en indekserer eller en routing-regel griber ud efter titlen. Indlejrede filer tilføjer en tredje akse: PDF tillader et dedikeret crypt-filter blot til vedhæftninger, så vedhæftningerne kan være den eneste krypterede del af et ellers åbent dokument eller den eneste klartekstdel af et krypteret. Opsaml de tre filtertildelinger som separate felter for strenge, streams og indlejrede filer, og så kan ingen af disse fælder fange dig. Gem en enkelt boolean, og den forkerte konklusion er kun et spørgsmål om tid

PDF Library for Delphi: Auditflow, der tjekker StringFilterIdentity, StreamFilterIdentity, EncryptMetadata og embedded-file-cryptfilteret, før en PDF kaldes beskyttet
Fire uafhængige akser afgør, om en fil, der ser krypteret ud, virkelig er forseglet, og at skæve dem sammen til én boolesk værdi ender med at fejlklassificere en fil

At fjerne kryptering og at vælge den til nye filer

En revision ender ofte i en beslutning om at fjerne beskyttelsen, og mekanikken er ikke forhindringen dér. DecryptFile(InputFileName, OutputFileName, Password) skriver en dekrypteret kopi uden en fuld indlæsning, og Decrypt på det indlæste dokument gør det samme i hukommelsen, når en fil allerede er åben. Begge kræver en gyldig adgangskode; ingen af dem omgår kryptografien. Den egentlige port er politik snarere end kode, så lad dine indtagsregler sige klart, hvornår fjernelse er tilladt, og notér den adgangskodeklasse, der gav lov, for det tekniske trin efterlader i sig selv intet spor

Valget for nyt output er snævrere, end de fem Strength-værdier antyder. Brug Strength 4, AES-256 revision 6, medmindre du er nødt til at åbne filer i fremvisere ældre end Acrobat X. Strength 2, AES-128, er det pragmatiske gulv for en aldrende flåde af fremvisere, der ikke kan opgraderes. RC4-mulighederne 0 og 1 findes, så du kan læse og revidere historiske arkiver, ikke så du kan producere noget nyt med dem; at gribe efter dem i et design fra 2026 er et tegn på, at et krav længere oppe i kæden er forældet

Krypteringstilstanden går direkte ind i beslutninger om signering, eftersom en arbejdsbænk, der validerer og signerer dokumenter, har brug for den samme tilbagelæsningsdisciplin, som denne revision hviler på. Det område dækkes i artiklen om arbejdsbænken til compliance og signering. Når et batch anvender EncryptFile på tværs af tusindvis af store dokumenter, viser direct access-guiden til store PDF'er, hvordan man holder hukommelsesforbruget fladt undervejs. Den komplette API-reference for kryptering findes på PDF Library for Delphi-produktsiden