Technical Article

PDF šifravimo ir teisių auditas „Delphi“ programose su PDFlibPas

Teisių vėliavėlė (permission flag) nėra saugumo mechanizmas. Bitas, kuris nurodo „kopijavimas draudžiamas“, yra tame pačiame /Encrypt žodyne kaip ir šifravimo nustatymai, o tai sukuria apgaulingą apsaugos įspūdį, kurio iš tikrųjų nėra. Jei abu šiuos dalykus laikysite vienu, jūsų auditas pradės duoti neteisingus atsakymus. Vienintelis klausimas, kurį verta užduoti tikrinant PDF, yra ne „ar jis šifruotas“, bet konkretesni ir sudėtingesni klausimai: koks algoritmas naudotas, kokia saugumo modulio versija, kuris iš dviejų slaptažodžių buvo nustatytas, kokie teisių bitai deklaruojami ir kurias failo dalis šifravimas iš tikrųjų apima. Failas gali būti formaliai šifruotas, bet praktiškai atviras. Jis gali neleisti perskaityti turinio, bet palikti metaduomenis atviru tekstu. Jis gali blokuoti spausdinimą vėliavėle, kurią bet kuri peržiūros programa gali tiesiog ignoruoti. PDF auditas reiškia visų šių aspektų tyrimą atskirai, o „PDFlibPas“ – „losLab“ PDF variklis, skirtas „Delphi“ ir „C++Builder“, atveria juos per plokščią sveikųjų skaičių rankenų API ir tipizuotą klasių sluoksnį.

Ką iš tikrųjų fiksuoja /Encrypt žodynas

ISO 32000-1 §7.6 standartas apibrėžia dokumento saugumą per kelis žodyno įrašus, o „PDFlibPas“ atspindi juos vienas prie vieno TPDFEncryption struktūroje. Filtro versija V ir revizija R parenka algoritmų šeimą. Savybė Length nurodo rakto dydį. Teisių bitai saugomi P, savininko (owner) ir naudotojo (user) slaptažodžių tikrinimo eilutės – O ir U (bei OE ir UE, pridedamos AES-256 atveju), šalia yra EncryptMetadata vėliavėlė ir dar trys laukai nurodo kriptografinius filtrus, taikomus atitinkamai eilutėms, srautams ir įterptiesiems failams.

Šios struktūros vertė yra ta, kad ji nieko neinterpretuoja už jus. Ji tiesiog grąžina neapdorotą žodyną ir leidžia jums patiems daryti išvadas, o būtent to ir reikia auditui. Atviro teksto šifruotame dokumente atvejis pasireiškia per StringFilterIdentity and StreamFilterIdentity: kai bet kuri iš šių savybių yra True, atitinkami duomenys praeina per Identity filtrą visiškai nepakeisti, nepriklausomai nuo to, ką praneša dokumento šifravimo būsena. Skenavimo įrankis, kuris sustoja ties faktu „/Encrypt žodynas yra“, pavadins tokį failą saugomu, nors jo eilutės ir srautai bus matomi atvirai. Tas pats niuansas galioja ir metaduomenims. Kai EncryptMetadata yra False, XMP paketas lieka prieinamas bet kokiam indeksuotojui, nors puslapio turinys yra neperskaitomas – tai svarbu žinoti, jei jūsų nukreipimo (routing) taisyklės remiasi pavadinimo ar autoriaus laukais.

Trumpas saugumo patikrinimas naudojant plokščią API

Daugumai procesų pakanka keturių pagrindinių iškvietimų kasdieniams klausimams atsakyti. Metodas LoadFromFile grąžina 1 sėkmės atveju, o atidarius dokumentą, šifravimo tikrinimo funkcijos praneša jo dešifruotą būseną:

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);     // decrypted / encrypted / unknown
    Writeln('algorithm : ', PDF.EncryptionAlgorithm);  // RC4 vs AES family
    Writeln('strength  : ', PDF.EncryptionStrength);   // key length class
    Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
  finally
    PDF.Free;
  end;
end;

Metodas CheckPassword yra svarbesnis, nei rodo jo viena eilutė kode. PDF formatas apibrėžia du nevienodos galios slaptažodžius. Naudotojo (user) slaptažodis reikalingas tam, kad failą būtų galima atidaryti. Savininko (owner) slaptažodis suteikia visas teises ir perrašo visus teisių bitus. Baitai diske abiem atvejais yra identiški, tačiau sesija, atidaryta su savininko slaptažodžiu, gali atlikti veiksmus, kurių naudotojo sesija negali, todėl auditas, kuris neužfiksuoja, kuris kredencialas (credential) buvo pateiktas, atspindi tik pusę tiesos. Klasių sluoksnis leidžia tai patikrinti. TPDFDocument.HasUserPassword ir HasOwnerPassword praneša, ko reikalauja failas, o IsUserPassword ir IsOwnerPassword nurodo, kuris slaptažodis atidarė dabartinę sesiją. Registruokite šį faktą. Pačių slaptažodžio reikšmių niekada neregistruokite.

Saugumo lygiai, kai „AES-256“ turi dvi reikšmes

Plokščiosios funkcijos Encrypt ir EncryptFile priima sveikojo skaičiaus parametrą Strength, turintį penkias reikšmingas vertes: 0 reiškia 40 bitų RC4, 1 – 128 bitų RC4, 2 – 128 bitų AES (skaitomą nuo „Acrobat 7“), 3 – 256 bitų AES (pristatytą su „Acrobat 9“), ir 4 – 256 bitų AES (kurio reikalauja „Acrobat X“ ir vėlesnės versijos).

Įdomiausia yra tai, kad tiek 3, tiek 4 lygiai yra vadinami AES-256, tačiau tai nėra ta pati schema. 3 lygis atitinka saugumo modulio 5 reviziją – laikiną variantą, kurį naudojo „Acrobat 9“ ir kurio ISO standartas niekada nepatvirtino. 4 lygis atitinka 6 reviziją, kurios rakto generavimo funkcija (KDF) buvo sustiprinta ir standartizuota ISO 32000-2. Dokumentui, kurį kuriate šiandien, nėra jokios priežasties rinktis 3 lygį vietoj 4. Audito tikslais šis skirtumas yra lemiamas: reikalavimą „AES-256 pagal ISO 32000-2“ atitinka tik R6 versija, o R5 failas, vadinamas AES-256, pažeidžia šią politiką, nors praeina paprastą stiprumo patikrą. Klasių sluoksnis juos skiria pagal pavadinimus: esAES256Bit – R5 versijai, esAES256BitAcroX – R6 versijai, o savybė EncryptionAcroX property atsako į revizijos klausimą viena logine reikšme.

Teisių bitai ir jų susiejimas su rakto ilgiu

Metodas EncodePermissions supakuoja aštuonias vėliavėles į sveikąjį skaičių, kurio tikisi Encrypt ir EncryptFile. Spausdinimas, kopijavimas, keitimas ir pastabų pridėjimas sudaro pagrindinį rinkinį; formų užpildymas, kopijavimas prieinamumui (accessibility), dokumento surinkimas (assemble) ir aukštos kokybės spausdinimas sudaro išplėstinį rinkinį. Svarbi detalė, kurią nurodo pati bibliotekos šifravimo demonstracinė programa, yra ta, kad keturios išplėstinės vėliavėlės veikia tik esant 128 bitų ar didesniam šifravimo stiprumui. Aukštos kokybės spausdinimo draudimo vėliavėlė paklūsta tai pačiai taisyklei: išvalykite ją 40 bitų dokumente, ir jis vis tiek leis spausdinti aukšta kokybe, nes šis apribojimas reikalauja bent 128 bitų šifravimo. Įrašykite taisyklę „tik žemos raiškos spausdinimas“ į 40 bitų failą, ir visos peržiūros programos jį spausdins pilna kokybe.

Gilesnis klausimas – kas užtikrina šių taisyklių laikymąsi, ir atsakymas yra: niekas, kuo galėtumėte pasitikėti. Teisės yra tiesiog instrukcijos standartų besilaikančioms peržiūros programoms, o ne kriptografiniai apribojimai. Dešifravimo raktas yra identiškas nepriklausomai nuo to, ar kopijavimas leidžiamas, ar draudžiamas, todėl apribotas teisių rinkinys veikia tik kaip rekomendacija sąžiningoms programoms. Peržiūros programa, nusprendusi ignoruoti šiuos bitus, nesusidurs su jokiu kriptografiniu barjeru. Jei užduotis yra visiškai užkirsti kelią duomenų išgavimui, o ne tiesiog jį apsunkinti, failui reikalingas naudotojo slaptažodis, o darbo procese būtinos kontrolės priemonės. Audito ataskaitoje turėtų būti aiškiai įvardyta, kuri saugumo taisyklė taikoma failui, užuot teisių vėliavėlę laikius tikru užraktu.

Politikos nustatymas ir patvirtinimas, kad ji pritaikyta

Šifravimo taikymas esamiems failams nereikalauja jų įkėlimo į objektų medį. Metodas EncryptFile apdoroja įvesties failą į išvesties failą vienu iškvietimu, o audito ciklas iš naujo atidaro rezultatą, kad patvirtintų, kas buvo įrašyta į diską. Bibliotekos šifravimo demonstracinė programa naudoja tą pačią formą – įrašymą, o po to perskaitymą atgal:

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,    // print allowed; copy/change/notes denied
                            0, 0, 0, 1));  // extended set: full-quality print only
    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;

Komandos, dirbančios dokumentų lygiu, gali naudoti tą pačią operaciją su tipizuotais rinkiniais (sets) vietoj bitų pakavimo – toks kodas peržiūros metu yra kur kas lengviau suprantamas:

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

Bet kuriuo atveju, perskaitymo atgal žingsnis yra privalomas. Jis padeda aptikti diegimo klaidas, kurios priešingu atveju pasireikštų tik kliento kompiuteryje: seną bibliotekos versiją, kuri tyliai sumažina prašomą šifravimo stiprumą, neįrašytą išvesties kelią dėl katalogo teisių trūkumo arba teisių sveikąjį skaičių, kurio argumentai buvo nurodyti neteisinga tvarka. Visi šie trys atvejai praeina vietinius testus, bet sugenda realiame darbe, o pakartotinis išvesties atidarymas paverčia juos pastebimomis išimtimis (exceptions). Metodas GetEncryptionFingerprint grąžina glaustą reikšmę, kurią galite išsaugoti su užduoties įrašu, kad vėliau galėtumėte palyginti, ar dvi išvestys naudoja tą pačią šifravimo konfigūraciją iš naujo neatidarydami failų.

Klaidingai teigiami audito rezultatai, kuriuos verta numatyti kode

Keli šablonai dažnai klaidina saugumo skenerius, o priežastis visada ta pati – sudėtingas klausimas supaprastinamas iki atsakymo „taip arba ne“. Geriausias to pavyzdys yra „Identity“ šifravimo filtras. /Encrypt žodynas yra, failas rodomas kaip šifruotas, tačiau eilutės ir srautai praeina per Identity filtrą nepakeisti, todėl tikrasis turinys yra atviras tekstas. Šios problemos sprendimas yra StringFilterIdentity ir StreamFilterIdentity reikšmių tikrinimas prieš paskelbiant dokumentą saugiu.

Metaduomenų atskyrimas yra dar subtilesnis. EncryptMetadata gali skirtis nuo likusios dokumento dalies abiem kryptimis – šifruotas failas gali turėti nuskaitomą XMP paketą arba, rečiau, atvirkščiai. Faktas „failas yra šifruotas“ nieko nepasako apie tai, ar jo metaduomenys taip pat šifruoti, o tai labai svarbu, jei nukreipimo taisyklė bando nuskaityti pavadinimą. Įterptieji failai prideda trečią ašį: PDF standartas leidžia naudoti atskirą šifravimo filtrą tik priedams, zodėl priedai gali būti vienintelė šifruota dalis atvirame dokumente arba vienintelė nešifruota dalis šifruotame. Užfiksuokite tris filtro priskyrimus kaip atskirus laukus eilutėms, srautams bei įterptiesiems failams ir išvengsite šių spąstų. Saugokite vieną loginę reikšmę, ir klaida bus tik laiko klausimas.

Šifravimo pašalinimas ir pasirinkimas naujiems failams

Auditas dažnai baigiasi sprendimu pašalinti apsaugą, ir čia techninės kliūtys nėra problema. Metodas DecryptFile(InputFileName, OutputFileName, Password) įrašo dešifruotą kopiją be pilno dokumento įkėlimo, o jau atidarytam dokumentui tą patį atmintyje atlieka Decrypt. Abiem atvejais reikalingas teisingas slaptažodis; nė vienas iš jų neaplenkia pačios kriptografijos. Tikroji kliūtis yra saugumo politika, o ne kodas, todėl jūsų taisyklėse turėtų būti aiškiai nurodyta, kada leidžiama pašalinti šifravimą, ir užfiksuota, kurios klasės slaptažodis tai autorizavo, nes pats techninis žingsnis nepalieka jokių pėdsakų.

Naujų failų pasirinkimas yra siauresnis, nei rodo penkios Strength reikšmės. Naudokite 4 lygį (AES-256 revizija 6), nebent turite atidaryti failus labai senose peržiūros programose (senesnėse nei „Acrobat X“). 2 lygis (AES-128) yra praktinis minimumas pasenusiai programinei įrangai, kurios negalima atnaujinti. RC4 parinktys (0 ir 1) yra skirtos tik istorinių archyvų skaitymui bei auditui, o ne naujų failų kūrimui; jų naudojimas 2026 m. architektūroje rodo, kad reikalavimai yra pasenę.

Šifravimo būsena tiesiogiai lemia pasirašymo sprendimus, nes darbo aplinka, kuri tikrina ir pasirašo dokumentus, reikalauja to paties perskaitymo atgal principo, kuriuo remiasi ir šis auditas. Ši tema išsamiau aprašyta straipsnyje Atitikties ir pasirašymo darbo aplinka. Kai šifravimas pritaikomas tūkstančiams didelių dokumentų grupiniame režime, tiesioginės prieigos vadovas dideliems PDF failams paaiškina, kaip išlaikyti stabilias atminties sąnaudas. Pilną šifravimo API dokumentaciją rasite PDFlibPas produkto puslapyje.