Technical Article

Одит на PDF шифриране и права в Delphi с PDFlibPas

Флагът за права не е защитен механизъм. Битът, който забранява копирането, се намира в същия речник /Encrypt, където се съхранява и криптографията. Това му придава илюзия за сигурност, каквато той всъщност не притежава, и в момента, в който разглеждате двете като едно и също нещо, вашият одит започва да дава грешни резултати. Единственият съществен въпрос при PDF е не дали е шифриран, а нещо по-конкретно и трудно: кой алгоритъм е използван, коя ревизия на модула за сигурност, коя от двете пароли е зададена, какви права са заявени и кои части от файла са засегнати от шифрирането. Един файл може да бъде формално шифриран и практически отворен. Той може да не позволява четене на съдържанието, но да остави своите метаданни в нешифриран текст. Може да забрани печатането чрез флаг, който всеки четец може да игнорира. Одитът на PDF означава да се анализира всеки от тези аспекти поотделно, и PDFlibPas, PDF ядрото на losLab за Delphi и C++Builder, разкрива всеки от тях чрез плосък API с целочислени дескриптори и чрез слой от типове.

Какво всъщност записва речникът /Encrypt

Стандартът ISO 32000-1 §7.6 дефинира защитата на документи чрез няколко записа в речника, а PDFlibPas ги отразява едно към едно в записа TPDFEncryption. Версията на филтъра V и ревизията R избират фамилията на алгоритъма. Свойството Length съдържа размера на ключа. Битовете за права се намират в P, низовете за валидиране на паролите на собственика и потребителя - в O и U (като за AES-256 са добавени OE и UE), флагът EncryptMetadata се намира до тях, а три други полета указват филтрите за шифриране, приложени съответно към текстови низове, потоци и вградени файлове.

Предимството на този запис е, че той не интерпретира данните вместо вас. Той ви предоставя оригиналния речник и ви позволява сами да си направите изводите, което е от съществено значение при одит. Случаят с нешифриран текст вътре в шифриран файл се вижда от свойствата StringFilterIdentity и StreamFilterIdentity: когато някое от тях е вярно, съответните данни преминават през филтъра Identity непокътнати, независимо какво показва статусът на шифриране на документа. Скенер, който проверява само за наличието на речник /Encrypt, ще отчете файла като защитен, въпреки че низовете и потоците в него се съхраняват в чист вид. Същият детайл важи и за метаданните: когато EncryptMetadata е лъжа (false), XMP пакетът остава четим за търсещи машини, докато съдържанието на страниците е скрито, което е важно, ако правилата ви за обработка на документи зависят от заглавието или автора.

Кратка проверка на сигурността чрез основния API

За повечето процеси четири извиквания от основния API дават отговор на ежедневните въпроси. LoadFromFile връща 1 при успех и след като документът е отворен, инструментите за проверка докладват за неговото състояние след дешифриране:

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;

Функцията CheckPassword е по-важна, отколкото показва нейният сигнатурен ред. PDF дефинира две пароли с различна степен на достъп: потребителската парола е необходима за самото отваряне на файла, а паролата на собственика предоставя пълни права и отменя битовете за ограничения. Байтовете на диска са идентични и в двата случая, но сесия, отворена с паролата на собственика, позволява операции, които потребителската сесия забранява. Поради тази причина одит, който не регистрира кои права са представени, съдържа само половината истина. Класовият слой прави тази разлика видима: свойствата TPDFDocument.HasUserPassword и HasOwnerPassword показват какво изисква файлът, докато IsUserPassword и IsOwnerPassword показват коя парола действително е отворила текущата сесия. Записвайте този факт в дневника, но никога не записвайте самите пароли.

Степени на шифриране: къде AES-256 означава две различни неща

Функциите Encrypt и EncryptFile от основния API приемат целочислена стойност за сила (Strength) с пет възможни опции: 0 за 40-битов RC4, 1 за 128-битов RC4, 2 за 128-битов AES (съвместим с Acrobat 7), 3 за 256-битов AES (въведен с Acrobat 9) и 4 за 256-битов AES (изискван от Acrobat X и по-нови версии).

Интересното е, че 3 и 4 са обозначени като AES-256, но не представляват една и съща схема. Сила 3 отговаря на ревизия 5 на модула за сигурност: междинен вариант, въведен с Acrobat 9, който стандартът ISO никога не приема. Сила 4 отговаря на ревизия 6, при която функцията за деривация на ключове е подсилена и стандартизирана в ISO 32000-2. За документи, които създавате днес, няма причина да изберете 3 вместо 4. При одит обаче тази разлика е критична: политика, изискваща "AES-256 съгласно ISO 32000-2", се изпълнява само от R6, докато R5 файл, деклариращ AES-256, се проваля при тази проверка, въпреки че преминава стандартния тест за сила. Класовият слой ги разграничава по име: esAES256Bit за R5 срещу esAES256BitAcroX за R6, а свойството EncryptionAcroX отговаря на въпроса за ревизията с булева стойност.

Битове за права и спецификата на дължината на ключа

Функцията EncodePermissions пакетира осем флага в цяло число, което се очаква от Encrypt и EncryptFile. Печат, копиране, промяна и добавяне на бележки съставят основния набор, докато попълването на полета, достъпността, сглобяването на документа и пълноформатният печат съставят разширения набор. Важен детайл е, че четирите разширени опции влизат в сила само при сила от 128 бита нагоре. Флагът за пълноформатен печат следва същото правило: ако го изчистите, за да наложите печат с ниска разделителна способност, 40-битовият документ ще игнорира това ограничение, тъй като тази промяна също изисква поне 128-битово шифриране. Задаването на ограничение за ниска разделителна способност в 40-битов файл ще бъде пренебрегнато от четците, които ще отпечатат документа с максимално качество.

По-важният въпрос е кой контролира изпълнението на тези ограничения, и отговорът е: никой, на когото можете да се доверите. Правата са просто инструкции към съвместимите четци, а не криптографски ограничения. Ключът за дешифриране е идентичен независимо дали копирането е разрешено или забранено, така че ограниченията просто гарантират добросъвестно поведение от страна на потребителя. Четец, който реши да игнорира тези битове, не се сблъсква с никакви криптографски пречки. Ако изискването е да предотвратите извличането на информация, а не просто да го ограничите, файлът изисква потребителска парола, а процесът се нуждае от допълнителен контрол, като одитният отчет трябва да посочи какъв е режимът на сигурност на всеки файл, вместо да разглежда флаговете за права като реална защита.

Прилагане на политики и потвърждаване на резултата

Прилагането на шифриране към съществуващи файлове не изисква зареждането им в обектното дърво. Функцията EncryptFile обработва входящия файл директно към изходния в едно извикване, а одитният процес отваря отново резултата, за да потвърди записите на диска. Приложеното демонстрационно приложение за шифриране следва същия модел на запис и последващо четене:

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;

Разработчиците, работещие на ниво документ, изпълняват същата операция с типизирани набори вместо пакетиране на битове, което улеснява прегледа на кода:

if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,

  [ppCanPrint], [ppCanPrintFull]) then

  raise Exception.Create('Encryption failed');

Така или иначе, обратното четене е критично важна стъпка. То открива грешки при внедряването, които иначе биха се проявили месеци по-късно при клиента: стара версия на библиотеката, която безшумно намалява силата на шифриране, грешен изходен път поради липса на права за запис в директорията или объркани аргументи в целочислената стойност за права. И трите проблема преминават локалните тестове, но се провалят в реална среда, а повторното отваряне ги превръща в изключения по време на самото изпълнение. Методът GetEncryptionFingerprint връща компактна стойност, която можете да запишете с хронологията на задачата, така че последващо сравнение да установи дали два файла споделят една и съща конфигурация на сигурност без отварянето им.

Фалшиви одитни резултати, за които да предвидите код

Няколко шаблона често водят скенерите за сигурност до погрешни заключения, като това обикновено се дължи на опростяване на комплексен въпрос до отговор с да или не. Филтърът за шифриране Identity е най-ясният пример: наличието на речник /Encrypt показва, че файлът е защитен, но текстовите низове и потоците преминават през филтъра Identity без промяна, което означава, че съдържанието е в чист вид. Проверката на StringFilterIdentity и StreamFilterIdentity, преди да декларирате файла като защитен, е правилното решение.

Разделението при метаданните е по-фино. Свойството EncryptMetadata може да се разминава с останалата част от документа в двете посоки, оставяйки шифриран файл с четим XMP пакет или по-рядко обратното. Твърдението "файлът е шифриран" не дава информация дали метаданните също са шифрирани, което е важно, ако търсеща машина или процес се опитват да прочетат заглавието на документа. Прикачените файлове добавят трета посока: PDF стандартът позволява отделен филтър за шифриране само за прикачени файлове, така че те могат да бъдат единствената защитена част в отворен документ или обратно - единствената нешифрирана част в защитен файл. Отчитането на трите филтъра като отделни полета за низове, потоци и вградени файлове предотвратява тези грешки, докато съхраняването на една булева стойност рано или късно ще доведе до грешка в бизнес логиката.

Премахване на шифрирането и избор за нови файлове

Одитът често приключва с решение за премахване на защитата, при което техническата реализация не е пречка. Методът DecryptFile(InputFileName, OutputFileName, Password) записва дешифрирано копие на файла без пълно зареждане, а методът Decrypt за зареден документ извършва същата операция в паметта, ако файлът е отворен. И двата изискват валидна парола; никой от тях не заобикаля криптографската защита. Истинското ограничение тук са фирмените правила, така че регламентирайте ясно кога е позволено премахването на защитата и записвайте типа на паролата, която е разрешила операцията, тъй като самата техническа стъпка не оставя следи в историята на файла.

Изборът за нови файлове е по-тесен, отколкото предполагат петте възможни стойности за Strength. Използвайте сила 4 (AES-256 ревизия 6), освен ако не е наложително да отваряте файлове на четци, по-стари от Acrobat X. Сила 2 (AES-128) е практическият минимум за по-стари системи, които не могат да бъдат актуализирани. Опциите за RC4 при 0 и 1 се поддържат само с цел съвместимост и одит на стари архиви, а не за производство на нови файлове; използването им в нови проекти през 2026 г. е индикация за остарели изисквания.

Състоянието на шифриране влияе директно на решенията за подписване, тъй като работната среда за валидиране и подписване изисква същата дисциплина за обратно четене, на която се основава този одит. Темата е разгледана в статията за работна среда за съвместимост и подписване. Когато обработвате пакетно голям брой файлове чрез EncryptFile, ръководството за директен достъп до големи PDF документи показва как да ограничите натоварването на паметта. Пълната документация за шифриране е достъпна на продуктовата страница на PDFlibPas.