Un indicator de permisiune nu este un mecanism de securitate. Bitul care spune „fără copiere” stă în același dicționar /Encrypt ca și criptografia, ceea ce îi dă un aer de constrângere pe care nu îl are, iar în clipa în care tratezi cele două ca pe un singur lucru auditul tău începe să dea răspunsuri greșite. Singura întrebare care merită pusă despre un PDF nu este „este criptat?”. Este una mai precisă și mai grea: care algoritm, care revizie a handler-ului de securitate, care dintre cele două parole a fost setată, care biți de permisiune sunt revendicați și ce părți din fișier atinge criptarea cu adevărat. Un fișier poate fi criptat formal și deschis practic. Poate refuza să fie citit și totuși să-și lase metadatele în clar. Poate bloca tipărirea printr-un indicator pe care orice vizualizator este liber să-l ignore. Auditarea unui PDF înseamnă lămurirea separată a tuturor acestor aspecte, iar PDF Library for Delphi, motorul PDF de la losLab pentru Delphi și C++Builder, le expune pe fiecare atât printr-un API plat cu handle-uri întregi, cât și printr-un strat de clase tipizate
Ce înregistrează de fapt dicționarul /Encrypt
ISO 32000-1 §7.6 definește securitatea documentului prin câteva intrări de dicționar, iar PDF Library for Delphi le oglindește una câte una în înregistrarea TPDFEncryption. Versiunea de filtru V și revizia R aleg familia de algoritmi. Length poartă dimensiunea cheii. Biții de permisiune stau în P, șirurile de validare a parolelor de proprietar și de utilizator în O și U (cu OE și UE adăugate pentru AES-256), un indicator EncryptMetadata le însoțește, iar alte trei câmpuri numesc filtrele de criptare aplicate șirurilor, fluxurilor și, respectiv, fișierelor încorporate
Valoarea acestei înregistrări stă în faptul că nu interpretează nimic în locul tău. Îți dă înapoi dicționarul brut și te lasă pe tine să tragi concluziile, exact ce îi trebuie unui audit. Cazul „text în clar în interiorul unui fișier criptat” se vede în StringFilterIdentity și StreamFilterIdentity: când oricare dintre ele este adevărat, datele corespunzătoare trec neatinse prin filtrul Identity, indiferent ce raportează starea de criptare a documentului. Un scaner care se oprește la „există un dicționar /Encrypt” va declara protejat un astfel de fișier, deși șirurile și fluxurile lui stau la vedere. Aceeași nuanță guvernează metadatele. Când EncryptMetadata este fals, pachetul XMP rămâne lizibil pentru orice indexator, în timp ce conținutul paginilor nu, ceea ce merită știut în clipa în care regulile tale de rutare se bazează pe un câmp de titlu sau de autor
O sondă scurtă de securitate cu API-ul plat
Pentru majoritatea fluxurilor, patru apeluri plate răspund la întrebările de zi cu zi. LoadFromFile returnează 1 în caz de succes, iar odată ce documentul este deschis, inspectorii de criptare raportează în raport cu starea lui decriptată:
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); // decriptat / criptat / necunoscut
Writeln('algorithm : ', PDF.EncryptionAlgorithm); // familia RC4 vs AES
Writeln('strength : ', PDF.EncryptionStrength); // clasa de lungime a cheii
Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
finally
PDF.Free;
end;
end;
CheckPassword contează mai mult decât lasă să se vadă semnătura lui de o singură linie. PDF definește două parole cu puteri inegale. Parola de utilizator este necesară pentru a deschide fișierul în general. Parola de proprietar acordă drepturi depline și trece peste orice bit de permisiune. Octeții de pe disc sunt identici în ambele cazuri, dar o sesiune deschisă cu parola de proprietar poate face lucruri pe care o sesiune cu parolă de utilizator nu le poate face, așa că un audit care nu consemnează ce credențial a fost prezentat consemnează doar jumătate de adevăr. Stratul de clase face distincția interogabilă. TPDFDocument.HasUserPassword și HasOwnerPassword raportează ce cere fișierul, în timp ce IsUserPassword și IsOwnerPassword raportează ce parolă a deschis efectiv sesiunea curentă. Consemnează faptul acesta. Nu consemna niciodată valorile parolelor
Scara Strength, unde „AES-256” înseamnă două lucruri
Funcțiile plate Encrypt și EncryptFile primesc un Strength întreg cu cinci valori cu sens: 0 pentru RC4 pe 40 de biți, 1 pentru RC4 pe 128 de biți, 2 pentru AES pe 128 de biți, lizibil începând cu Acrobat 7, 3 pentru AES pe 256 de biți așa cum a fost introdus cu Acrobat 9 și 4 pentru AES pe 256 de biți așa cum cere Acrobat X și versiunile ulterioare
Partea interesantă este că 3 și 4 sunt amândouă etichetate AES-256 și nu sunt aceeași schemă. Strength 3 corespunde reviziei 5 a handler-ului de securitate, un proiect intermediar pe care Acrobat 9 l-a livrat, iar ISO nu l-a adoptat niciodată. Strength 4 corespunde reviziei 6, a cărei funcție de derivare a cheii a fost întărită și standardizată în ISO 32000-2. Pentru un document pe care îl creezi astăzi nu există niciun motiv să alegi 3 în locul lui 4. Pentru un audit, diferența este decisivă: o politică formulată ca „AES-256 conform ISO 32000-2” este îndeplinită doar de R6, iar un fișier R5 care se autointitulează AES-256 pică politica aceea, deși trece o verificare naivă de putere. Stratul de clase le ține separate prin nume, esAES256Bit pentru R5 față de esAES256BitAcroX pentru R6, iar proprietatea EncryptionAcroX răspunde la întrebarea despre revizie cu un singur boolean
Biții de permisiune și clauzele mărunte despre lungimea cheii
EncodePermissions împachetează opt indicatori în întregul pe care îl așteaptă Encrypt și EncryptFile. Tipărirea, copierea, modificarea și adăugarea de note alcătuiesc setul de bază; completarea câmpurilor, copierea pentru accesibilitate, asamblarea și tipărirea la calitate maximă alcătuiesc setul extins. Clauza măruntă, pe care demonstrația de criptare a bibliotecii o spune pe față, este că cele patru din setul extins au efect doar de la 128 de biți în sus. Indicatorul de tipărire la calitate maximă cade sub aceeași regulă: șterge-l ca să forțezi tipărirea la rezoluție joasă și un document pe 40 de biți te va ignora, fiindcă și declasarea aceea cere criptare pe 128 de biți sau mai puternică. Codifică o politică de tipul „doar tipărire la rezoluție joasă” într-un fișier pe 40 de biți și orice vizualizator va tipări oricum la calitate maximă
Întrebarea mai adâncă este cine impune vreunul dintre acești biți, iar răspunsul este: nimeni în care să te poți încrede. Permisiunile sunt instrucțiuni pentru cititoarele conforme, nu restricții criptografice. Cheia de decriptare este identică indiferent dacă copierea este permisă sau refuzată, așa că un set de permisiuni blocat nu face decât să țină cinstite vizualizatoarele cinstite. Un cititor care alege să ignore biții nu întâmpină niciun obstacol criptografic. Dacă obligația este să împiedici extragerea, nu doar să o descurajezi, fișierul are nevoie de o parolă de utilizator, iar fluxul de lucru are nevoie de controale la nivel de proces în jurul ei, iar un raport de audit ar trebui să numească sub care dintre cele două regimuri se află de fapt fiecare fișier, în loc să trateze un indicator de permisiune drept lacăt
Stabilirea politicii și dovada că a prins
Aplicarea criptării pe fișiere existente nu cere încărcarea lor în arborele de obiecte. EncryptFile prelucrează intrarea în ieșire printr-un singur apel, iar bucla de audit redeschide rezultatul ca să confirme ce a ajuns pe disc. Demonstrația de criptare livrată cu biblioteca urmează aceeași formă de scriere urmată de recitire:
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, // tipărire permisă; copiere/modificare/note refuzate
0, 0, 0, 1)); // set extins: doar tipărire la calitate maximă
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;
Echipele care lucrează la nivelul documentului primesc aceeași operație cu mulțimi tipizate în locul împachetării de biți, ceea ce trece prin code review cu mult mai puțină mijire a ochilor:
if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
[ppCanPrint], [ppCanPrintFull]) then
raise Exception.Create('Encryption failed');
Oricum ai proceda, pasul de recitire nu este o ceremonie opțională. El prinde greșelile de implementare care altfel ies la iveală peste luni de zile pe mașina unui client: o versiune veche de bibliotecă ce declasează tăcut puterea cerută, o cale de ieșire care nu a fost niciodată scrisă fiindcă directorul era doar în citire, un întreg de permisiuni ale cărui argumente au intrat în ordine greșită. Toate trei trec un smoke test local și cad pe teren, iar redeschiderea ieșirii transformă fiecare dintre ele într-o excepție pe care o vezi chiar în timpul rulării care a creat fișierul. GetEncryptionFingerprint returnează o valoare compactă pe care o poți stoca odată cu înregistrarea lucrării, astfel încât o comparație ulterioară să poată spune dacă două ieșiri au aceeași configurație de criptare, fără să redeschidă vreuna dintre ele
Fals pozitive de audit pentru care merită să scrii cod
Câteva tipare împing constant scanerele de securitate spre concluzia greșită, iar fiecare vine din reducerea unei întrebări cu mai multe părți la un răspuns de tip da sau nu. Filtrul de criptare Identity este exemplul cel mai curat. Dicționarul /Encrypt există, fișierul se raportează drept criptat și totuși șirurile și fluxurile trec neschimbate prin filtrul Identity, așa că adevăratul conținut este text în clar. Citirea StringFilterIdentity și StreamFilterIdentity înainte de a declara ceva protejat este soluția
Separarea metadatelor este mai subtilă. EncryptMetadata poate fi în dezacord cu restul documentului în ambele sensuri, lăsând un fișier criptat cu un pachet XMP lizibil sau, mai rar, invers. „Fișierul este criptat” nu spune nimic despre metadatele lui, ceea ce contează în clipa în care un indexator sau o regulă de rutare întinde mâna după titlu. Fișierele încorporate adaugă o a treia axă: PDF permite un filtru de criptare dedicat doar atașamentelor, deci atașamentele pot fi singura parte criptată dintr-un document altfel deschis sau singura parte în clar dintr-unul criptat. Captează cele trei atribuiri de filtre ca pe câmpuri separate pentru șiruri, fluxuri și fișiere încorporate și niciuna dintre aceste capcane nu te mai poate prinde. Stochează un singur boolean și decizia greșită este doar o chestiune de timp
Eliminarea criptării și alegerea ei pentru fișiere noi
Un audit se termină adesea cu decizia de a elimina protecția, iar mecanica nu este obstacolul acolo. DecryptFile(InputFileName, OutputFileName, Password) scrie o copie decriptată fără o încărcare completă, iar Decrypt pe documentul încărcat face același lucru în memorie, odată ce fișierul este deja deschis. Ambele cer o parolă validă; niciuna nu ocolește criptografia. Adevărata poartă ține de politică, nu de cod, așa că fă-ți regulile de recepție să spună limpede când este permisă eliminarea și consemnează clasa de parolă care a autorizat-o, fiindcă pasul tehnic în sine nu lasă nicio urmă
Alegerea pentru ieșirile noi este mai îngustă decât lasă să se creadă cele cinci valori Strength. Folosește Strength 4, AES-256 revizia 6, în afară de cazul în care trebuie să deschizi fișiere în vizualizatoare mai vechi decât Acrobat X. Strength 2, AES-128, este pragul pragmatic pentru un parc îmbătrânit de vizualizatoare care nu poate fi actualizat. Opțiunile RC4 de la 0 și 1 există ca să poți citi și audita arhive istorice, nu ca să produci ceva nou cu ele; să apelezi la ele într-un proiect din 2026 este semn că o cerință din amonte a rămas în urmă
Starea criptării alimentează direct deciziile de semnare, fiindcă un banc de lucru care validează și semnează documente are nevoie de aceeași disciplină de recitire pe care se sprijină acest audit. Terenul acela este acoperit în articolul despre bancul de lucru pentru conformitate și semnare. Când un lot aplică EncryptFile pe mii de documente mari, ghidul de acces direct pentru PDF-uri mari arată cum să menții memoria plată în timp ce rulează. Referința completă a API-ului de criptare se află pe pagina de produs PDF Library for Delphi