Artykuł techniczny

HotPDF: AES-256 encryption and permission policy in Delphi

Flaga uprawnień PDF to nie zamek. To prośba, którą plik kieruje do tego, co go otwiera, a przeglądarka może ją zupełnie zignorować. Ten jeden fakt decyduje o tym, jak należy rozumować o każdym innym wyborze na tej stronie. Prawdziwa poufność pochodzi tylko z jednego miejsca: szyfrowania AES-256 z kluczem opartym na haśle, którego czytelnik nie ma. Wszystko inne, pola wyboru "bez drukowania" i "bez kopiowania", to zasady, które zgodne oprogramowanie zgadza się respektować, a wrogie oprogramowanie nie. Pomieszaj te dwie warstwy, a wyślesz coś, co w demo wygląda bezpiecznie, a w terenie wycieka

HotPDF to natywny komponent PDF VCL dla Delphi i C++Builder, który udostępnia model ochrony ISO 32000 poprzez niewielki zestaw właściwości. Te właściwości łatwo ustawić. Trudną częścią jest wiedzieć, która z nich kupuje ochronę kryptograficzną, a która grzeczną sugestię, oraz zachować właściwą kolejność przypisań, aby szyfrowanie, o które prosiłeś, było naprawdę szyfrowaniem, które dostajesz

Co naprawdę obiecują dwa hasła

Szyfrowanie PDF definiuje dwa poświadczenia o różnych zadaniach, a ich pomylenie to najczęstszy błąd projektowy w kodzie generującym chroniony dokument. Hasło użytkownika kontroluje dostęp do odszyfrowania. Bez niego, lub bez hasła właściciela, zgodny czytnik nie może odtworzyć klucza pliku, a zawartość pozostaje kryptograficznie nieczytelna. Hasło właściciela kontroluje natomiast ustawienia uprawnień: czytnik, który otrzyma hasło właściciela, dostaje pełny dostęp bez względu na to, co mówią flagi ograniczeń

Bity uprawnień stoją na słabszym gruncie. Drukowanie, wyodrębnianie treści, wypełnianie formularzy: każdy z nich to flaga, którą przeglądarka odczytuje i decyduje się uszanować (ISO 32000-2 §7.6.4). Szyfrowanie chroni bajty. Flagi uprawnień jedynie instruują zgodne oprogramowanie, i robią to już po fakcie. Każdy, kto otworzy dokument hasłem użytkownika, ma już odszyfrowaną treść w pamięci, więc "bez kopiowania" i "bez drukowania" coś znaczą dla dobrze zachowującej się przeglądarki, a nic dla zdeterminowanej. Zbuduj model zagrożeń wokół tej linii. Poufność mieszka w haśle użytkownika. Uprawnienia kształtują to, co oferują popularne przeglądarki, i to jest cała ich rola

Diagram HotPDF poświadczeń zaszyfrowanego PDF, gdzie hasło użytkownika wyprowadza klucz pliku i odblokowuje deszyfrowanie, hasło właściciela daje pełny dostęp nadpisując flagi uprawnień, a pas ostrzegawczy notuje, że bity uprawnień ProtectOptions to żądania honorowane tylko przez zgodne oprogramowanie
Hasło użytkownika niesie poufność, podczas gdy hasło właściciela jedynie zdejmuje ograniczenia; flagi uprawnień kierują zgodnymi czytnikami i nie wiążą nikogo wrogiego

Kolejność konfiguracji: wszystko przed BeginDoc

HotPDF buduje słownik szyfrowania i wyprowadza klucz pliku w chwili, gdy uruchamia się BeginDoc. To, co właściwości ochrony zawierają w tym właśnie momencie, jest tym, co dostaje dokument, a zmiana ich potem niczego nie zmienia. Najważniejszą właściwością jest tu CryptKeyLength, która wybiera schemat spośród wartości THPDFKeyType: k40, k128, aes128 i aes256. Przypisz ją po BeginDoc, a nie dostaniesz żadnego wyjątku, żadnego ostrzeżenia, tylko plik, który po cichu zachował to, z czym zaczynał. Ten rodzaj cichej rozbieżności jest najgorszy z możliwych: przechodzi każdy lokalny test, a miesiące później wypływa jako ustalenie zgodności na biurku klienta

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'statement.pdf';
    Pdf.ActivateProtection := True;
    Pdf.CryptKeyLength := aes256;        // musi być ustawione przed BeginDoc
    Pdf.UserPassword := 'open-secret';
    Pdf.OwnerPassword := 'admin-secret';
    Pdf.UseAES256R6 := False;            // R=5: najszersza obsługa przeglądarek
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 720, 0, 'Account statement, June 2026');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Hasła są w UTF-8 i ograniczone do 127 bajtów, co jest limitem ISO 32000-2 dla schematów AES-256. Jeśli twoja polityka haseł dostarcza dłuższe sekrety, wykonaj obcięcie samodzielnie, po swojej stronie, gdzie masz pełną kontrolę nad tym, gdzie dokładnie pada cięcie. Zostaw to przypadkowi, a biblioteka i jakaś przyszła przeglądarka mogą nie zgodzić się co do miejsca obcięcia, co da plik, który otwiera się u ciebie, a odmawia tego samego hasła gdzie indziej

Rewizja 5 czy rewizja 6: jedna wartość logiczna, dwa ekosystemy

UseAES256R6 wybiera między dwoma sposobami uzgadniania AES-256, a ten wybór ma większe konsekwencje, niż sugeruje jego logiczny typ. Zostaw False, a HotPDF zapisze rewizję 5, schemat AES-256, który pojawił się jako rozszerzenie PDF 1.7 i który potrafi otworzyć coś w rodzaju piętnastu lat przeglądarek. Ustaw True, a dostaniesz rewizję 6, wzmocnione wyprowadzanie klucza znormalizowane w ISO 32000-2 dla PDF 2.0, które zamyka znaną słabość w sposobie, w jaki rewizja 5 weryfikuje hasło

Rewizja 6 jest więc kryptograficznie lepszą historią. Jest też tą, która psuje rzeczy. Plik w rewizji 6 wymaga przeglądarki zbudowanej dla PDF 1.7 Extension Level 3 albo PDF 2.0, a spora część wdrożonego oprogramowania nie jest żadnym z nich: archiwa zarządzania dokumentacją, wbudowane silniki renderujące w innych produktach, narzędzia biznesowe, których nikt nie dotykał od lat. Te odmówią otwarcia pliku od razu, i zrobią to na maszynie klienta, nigdy na twojej. Praktycznym domyślnym wyborem jest więc rewizja 5. Sięgaj po rewizję 6 tylko wtedy, gdy polityka bezpieczeństwa wskazuje ISO 32000-2 według rewizji, i gdy faktycznie potwierdziłeś, że każdy odbiorca potrafi go odczytać. Tak czy inaczej, zapisz, którą wybrałeś i dlaczego, bo kolejna osoba dotykająca tego kodu będzie się zastanawiać

Starsze typy kluczy zasługują na jedno zdanie, żebyś wiedział, że można je pominąć. THPDFKeyType wciąż wymienia k40, k128 i aes128, ale istnieją po to, by odtwarzać archiwa historyczne, nie by chronić nowe. 40-bitowy RC4 pada pod naporem zwykłego sprzętu, a schematy 128-bitowe poprzedzają rewizje AES-256, których oczekuje dziś każdy przegląd bezpieczeństwa. Dla dokumentu tworzonego w 2026 roku realnym pytaniem jest wyłącznie rewizja 5 kontra rewizja 6; jeśli w nowym projekcie sięgasz po typy przestarzałe, coś wcześniej poszło nie tak

Flagi uprawnień bez hasła otwarcia

Często wymóg jest odwrotnością tajności. Każdy powinien móc odczytać dokument, ale drukowanie lub wyodrębnianie ma być ograniczone. Wyrażasz to pustym hasłem użytkownika i niepustym hasłem właściciela, co PDF nazywa trybem otwartego hasła, a operacje, które chcesz dopuścić, wymieniasz w ProtectOptions

Przepływy Delphi obok siebie pokazujące właściwości ochrony, takie jak CryptKeyLength aes256 przypisane przed BeginDoc dające plik AES-256, podczas gdy to samo przypisanie po BeginDoc zostawia oryginalny schemat nietknięty bez wyjątku ani ostrzeżenia
BeginDoc zamraża słownik szyfrowania, więc pas właściwej kolejności daje żądaną rewizję AES-256, podczas gdy pas złej kolejności po cichu wysyła domyślny schemat
Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.UserPassword := '';                      // każdy może otworzyć plik
Pdf.OwnerPassword := 'rotate-me-quarterly';  // chroni zestaw uprawnień
Pdf.ProtectOptions := [prPrint, prPrint12bit, prExtractContent];
Pdf.BeginDoc;
// ... treść strony ...
Pdf.EndDoc;

Zbiór THPDFProtectOptions odwzorowuje się na bity uprawnień ISO: prPrint i prPrint12bit dla drukowania w wysokiej rozdzielczości, prInformationCopy dla ogólnego kopiowania i wyodrębniania, prExtractContent dla wyodrębniania przez technologie wspomagające, plus prModifyStructure, prEditAnnotations, prFillAnnotations i prAssemble. Dwa z nich zasługują na ostrzeżenie. Zostaw prExtractContent włączone niemal w każdym budowanym profilu. To bit, którego potrzebuje czytnik ekranu, aby dotrzeć do tekstu, a jego wyczyszczenie po cichu zamienia decyzję dotyczącą praw w defekt dostępności, na który trafi osoba z niepełnosprawnością, a ty nigdy go nie zobaczysz. Druga pułapka to samotne prPrint, bez prPrint12bit: kilka przeglądarek reaguje na to degradacją jakości druku, a twoi użytkownicy zgłoszą to jako błąd renderowania, a nie ustawienie uprawnień, którym w rzeczywistości jest

Weryfikacja zajmuje pięć minut i należy do listy kontrolnej wydania. Otwórz próbkę każdego profilu w Acrobacie, otwórz Właściwości dokumentu i przeczytaj zakładkę Zabezpieczenia, która wprost podaje algorytm ("AES 256-bit") i wymienia dozwolone operacje jedna po drugiej. Potem otwórz ten sam plik w najstarszej przeglądarce, jakiej faktycznie używają twoi klienci, nie w najnowszej na twojej maszynie. To drugie otwarcie to tania polisa ubezpieczeniowa przed sytuacją, w której plik w rewizji 6 przechodzi gładko przez rozwój, a potem umiera u klienta, który nigdy nie zaktualizował swojej przeglądarki

Usuwanie ochrony z istniejących plików

Odszyfrowanie uruchamia ten sam model właściwości w drugą stronę. Wczytaj dokument prawidłowym poświadczeniem, wyłącz ochronę i zapisz wynik już bez niej

var
  Pdf: THotPDF;
  PageCount: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('encrypted.pdf', 'open-secret');
    if PageCount > 0 then
    begin
      Pdf.ActivateProtection := False;   // usuń szyfrowanie przy zapisie
      Pdf.SaveLoadedDocument('plain.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Ta droga parsuje cały dokument do pamięci, co jest w porządku dla zwykłych plików, a marnotrawstwem dla ogromnych. Gdy wejście sięga setek megabajtów, DecryptFile jest tańszą opcją: odszyfrowuje podczas kopiowania na poziomie pliku, obierając bezpośrednią ścieżkę przepisywania AES-256, która pomija budowanie pełnego drzewa obiektów, kiedy tylko wejście na to pozwala. Jest to część Direct File API omówionego w artykule towarzyszącym o przetwarzaniu dużych plików PDF z Delphi

Ograniczenia wchodzące w interakcję z szyfrowaniem

Dwa ograniczenia warto poznać, zanim zaprojektujesz coś wokół szyfrowania, a nie po fakcie. Pierwsze dotyczy zgodności archiwalnej. ISO 19005 zabrania szyfrowania w PDF/A, więc każdy przepływ, który szyfruje dokument, a jednocześnie deklaruje zgodność z PDF/A, jest sprzeczny z definicji; HotPDF nie pozwoli mieć obu naraz w jednym pliku. Gdy naprawdę potrzebujesz obu, odpowiedzią są dwa artefakty: zaszyfrowana kopia do dystrybucji i osobna, niezaszyfrowana kopia do archiwum

Drugie ograniczenie jest bardziej surowe. Szyfrowanie PDF nie ma ani depozytu, ani odzyskiwania. Zgub hasło użytkownika w pliku R5 lub R6, a twoimi opcjami są brute force albo poddanie się. Traktuj więc sekrety właściciela i użytkownika tak, jak każde poświadczenie produkcyjne. Generuj je, przechowuj w skarbcu, rotuj według harmonogramu. Jedną rzeczą, której nigdy nie wolno robić, jest zaszywanie ich na stałe jako stałych w module, skąd trafiają prosto do systemu kontroli wersji i zostają w kopii roboczej każdego programisty na zawsze

Jeszcze jeden odruch wart wyrobienia. Zmiana ochrony pliku, którego sam nie stworzyłeś, to ten sam mechanizm co odszyfrowanie, nie osobna funkcja: wczytaj go jego hasłem przez LoadFromFile, edytuj ProtectOptions lub hasła na miejscu i zapisz z powrotem przez SaveLoadedDocument. Jeśli potrafisz odszyfrować plik, potrafisz też zmienić jego uprawnienia, a kod wygląda niemal identycznie jak w przykładzie powyżej

Właściwości ochrony pokazane tutaj są częścią standardowego HotPDF Delphi Component dla Delphi i C++Builder; strona produktu zawiera pełne odniesienie do szyfrowania, w tym kompletne wyliczenie uprawnień