Szyfrowanie pliku PDF z ograniczeniem uprawnień
W skrócie
Dział zatytułowany „W skrócie”Ten przepis pokazuje, jak zaszyfrować dokument za pomocą standardowego mechanizmu zabezpieczeń Advanced Encryption Standard (AES)-256. Ustawiasz hasło użytkownika (wymagane do otwarcia), hasło właściciela (pełny dostęp) oraz maskę bitową uprawnień ograniczającą operacje. Traktuj te uprawnienia jako zależne od współpracy czytnika: szyfrowanie zapewnia poufność, a nie integralność, i tylko współpracujące oprogramowanie respektuje bity uprawnień. Przepis jest zgodny z examples/22-protection.php.
Granica zaufania (pamiętaj o niej przy każdym opisie uprawnień). Szyfrowanie PDF chroni poufność treści przed podmiotami, które nie znają hasła (ISO 32000-2 §7.6). Nie chroni integralności: nie wykrywa ani nie zapobiega modyfikacjom. Wpis uprawnień
Pto 32-bitowy zestaw flag bez znaku, który prosi zgodne czytniki o respektowanie ograniczeń; nie jest to mechanizm kontroli dostępu. Narzędzie niezgodne ze standardem lub dowolne narzędzie użyte z hasłem właściciela może wykonać każdą „zabronioną” operację. Nie opisuj zaszyfrowanego pliku PDF jako „bezpiecznego”, „odpornego na manipulacje” ani „zabezpieczonego przed kopiowaniem”.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Włącz rozszerzenie PHP openssl. Mechanizm szyfrowania AES-256 używa go do operacji szyfrowania i wyprowadzania klucza.
Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”Kody V/R w słowniku szyfrowania wybierają standardowy mechanizm zabezpieczeń (ISO 32000-2 §7.6). W klasie Aes256Encryptor NextPDF implementuje filtr szyfrowania AESV3 dla mechanizmu zabezpieczeń w wersji 6 (V=5/R=6). Używa losowego 256-bitowego klucza szyfrowania pliku, wyprowadzania klucza z użyciem solonego haszowania iteracyjnego (Algorytm 2.B) oraz szyfrowania AES-256-CBC poszczególnych obiektów z losowym wektorem inicjującym. Cipher Block Chaining (CBC) to tryb poufności (NIST SP 800-38A). Wektory inicjujące dla CBC muszą być nieprzewidywalne.
Wektor inicjujący jest nowy dla każdego obiektu i każdego uruchomienia, więc surowe bajty różnią się między uruchomieniami. Profil odtwarzalności jest zatem structural. Przed porównaniem dwóch uruchomień środowisko testowe kanonizuje wektor inicjujący szyfrowania, kolejność obiektów oraz /ID w zwiastunie. Ten profil jest bardziej rygorystyczny niż profil przepisu bez szyfrowania.
Maska bitowa uprawnień ustawia wpis P. Bit 3 przyznaje prawo do drukowania, a bit 6 przyznaje annotation/form-fill. Wartość jest udokumentowaną 32-bitową liczbą bez znaku.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”NextPDF\Core\Concerns\HasSecurity (dołączone do Document):
setEncryption(#[SensitiveParameter] string $userPassword, #[SensitiveParameter] string $ownerPassword = '', int $permissions = -1): static— konfiguruje szyfrowanie standardowym mechanizmem zabezpieczeń AES-256.permissions = -1przyznaje wszystkie uprawnienia. GdyownerPasswordjest puste, hasło użytkownika jest ponownie używane jako hasło właściciela. Wywołaj przedaddPage().getEncryptor(): ?Aes256Encryptor— skonfigurowany mechanizm szyfrowania lubnull.useAesGcm(?bool $enabled = true): static— włącza AES-256-GCM zgodne z ISO/TS 32003; zgłasza wyjątek, jeśli host OpenSSL/libsodium nie udostępnia tego szyfru.
Oba parametry haseł są oznaczone #[SensitiveParameter], więc PHP usuwa je ze śladów stosu.
Bity uprawnień (wpis P, niskie bity 3–6 w typowym użyciu):
| Bit | Wartość | Operacja |
|---|---|---|
| 3 | 4 | Drukowanie dokumentu |
| 4 | 8 | Modyfikowanie zawartości dokumentu |
| 5 | 16 | Kopiowanie / wyodrębnianie tekstu i grafiki |
| 6 | 32 | Dodawanie lub modyfikowanie adnotacji i wypełnianie pól formularzy |
Przykład kodu — Szybki start
Dział zatytułowany „Przykład kodu — Szybki start”<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setTitle('Confidential Memo');
// Grant printing only (bit 3 = 4). MUST run before addPage().$doc->setEncryption( userPassword: 'open-me', ownerPassword: 'owner-secret', permissions: 4,);
$doc->addPage();$doc->setFont('helvetica', '', 12);$doc->cell(0, 10, 'Encrypted with AES-256; printing allowed only.', newLine: true);
$doc->save(__DIR__ . '/confidential.pdf');echo "Wrote confidential.pdf\n";Przykład kodu — Produkcja
Dział zatytułowany „Przykład kodu — Produkcja”Poniższy pełny przykład odzwierciedla examples/22-protection.php i zapisuje do NEXTPDF_COOKBOOK_OUTPUT na potrzeby środowiska testowego.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$userPassword = 'demo';$ownerPassword = 'admin';
// Grant ONLY printing (bit 3 = 4); deny copy/modify/annotate.$permissions = 4;
$doc = Document::createStandalone();$doc->setTitle('Encrypted Document — Restricted Permissions');$doc->setAuthor('NextPDF Example');
// setEncryption() MUST be called before addPage().$doc->setEncryption( userPassword: $userPassword, ownerPassword: $ownerPassword, permissions: $permissions,);
$doc->addPage();$doc->setFont('helvetica', 'B', 20);$doc->cell(0, 14, 'Encrypted PDF Document', newLine: true);$doc->ln(8);
$doc->setFont('helvetica', '', 11);$doc->multiCell(0, 7, 'This document is protected with AES-256 encryption ' . '(standard security handler, revision 6). The user password is required ' . 'to open it; the owner password grants full access. The permission ' . 'bits below are honoured by conforming readers only.');$doc->ln(5);
$permissionTable = [ ['Bit 3 (4)', 'Printing', 'ALLOWED'], ['Bit 4 (8)', 'Content modification', 'DENIED'], ['Bit 5 (16)', 'Text copying / extraction', 'DENIED'], ['Bit 6 (32)', 'Annotations / form fields', 'DENIED'],];$doc->setFont('helvetica', 'B', 10);$doc->cell(30, 7, 'Flag');$doc->cell(60, 7, 'Operation');$doc->cell(0, 7, 'Status', newLine: true);foreach ($permissionTable as [$bit, $operation, $status]) { $doc->setFont('courier', '', 9); $doc->cell(30, 7, $bit); $doc->setFont('helvetica', '', 10); $doc->cell(60, 7, $operation); $doc->setFont('helvetica', 'B', 10); $doc->cell(0, 7, $status, newLine: true);}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$doc->save($out !== false ? $out : __DIR__ . '/encrypted.pdf');
echo "Wrote encrypted PDF (AES-256, printing only)\n";Oczekiwane wyjście:
Wrote encrypted PDF (AES-256, printing only)Po otwarciu pliku czytnik poprosi o hasło. Hasło użytkownika otwiera dokument z ograniczonym zestawem uprawnień. Hasło właściciela otwiera go z pełnym dostępem.
Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Kolejność wywołań.
setEncryption()wywołane poaddPage()nie szyfruje wstecznie wcześniejszej zawartości. Najpierw skonfiguruj szyfrowanie; silnik szyfruje treść każdego obiektu podczas zapisu. - Domyślne hasło właściciela. Puste hasło właściciela sprawia, że silnik ponownie używa hasła użytkownika jako hasła właściciela. W efekcie nie ma odrębnej uprzywilejowanej roli. Ustaw odrębne hasła, gdy obie role muszą się różnić.
- Semantyka uprawnień ma charakter doradczy. Tylko zgodne czytniki respektują te bity. Nie są one wymuszane kryptograficznie: narzędzie niezgodne ze standardem lub dowolne narzędzie użyte z hasłem właściciela może wykonać operacje objęte ograniczeniami. Traktuj uprawnienia jako sygnał polityki dla współpracującego oprogramowania, nigdy jako mechanizm kontroli dostępu odporny na zdeterminowany podmiot.
- Brak gwarancji integralności. Szyfrowanie to poufność, a nie integralność. Atakujący, który nie zna hasła, nie może odczytać treści, ale sam format nie wykrywa manipulacji. Ochrona integralności wymaga odrębnego mechanizmu, takiego jak podpis cyfrowy lub kod MAC dokumentu zgodny z ISO/TS 32004.
- Konflikt z PDF/A. PDF/A zabrania klucza
Encryptw zwiastunie. WywołaniesetEncryption()na dokumencie PDF/A, w dowolnej kolejności, zgłasza wyjątek niezgodności. - Włączenie AES-256-GCM.
useAesGcm()wybiera szyfrowanie masowe GCM zgodne z ISO/TS 32003, gdy udostępnia je hostowy OpenSSL lub libsodium. W przeciwnym razie zgłaszaInvalidConfigException. Z tego samego powodu jest niezgodne z PDF/A. - Szyfrowanie kluczem publicznym nie jest jeszcze podłączone do zapisu.
setPublicKeyEncryption()zamraża powierzchnię API, alesave()zgłasza wyjątek, dopóki ścieżka zapisu nie zostanie podłączona (znany defekt). Nie używaj go produkcyjnie w Core.
Wydajność
Dział zatytułowany „Wydajność”Wyprowadzanie klucza wykonuje iterowane haszowanie Algorytmu 2.B raz na dokument. Szyfrowanie AES-256-CBC poszczególnych obiektów jest liniowe względem rozmiaru treści obiektu. Dla typowych dokumentów koszt bez problemu mieści się w budżecie 1500 ms / 64 MB. W bardzo dużych dokumentach koszt zależy od przepustowości AES dla każdego obiektu. Tryb Galois/Counter Mode (GCM) z AES-NI jest szybszy na hostach, które go obsługują.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”- Tylko poufność. Powtórzmy granicę zaufania: szyfrowanie chroni treść przed podmiotami, które nie znają hasła. Nie dowodzi, że plik jest niezmieniony, a bity uprawnień zależą od współpracy czytnika.
- Siła hasła zależy od Ciebie. Mechanizm jest tylko tak silny, jak hasła. Gdy ktoś zdobędzie plik, słabe hasło użytkownika można złamać metodą brute-force w trybie offline; format nie może ograniczać liczby prób.
- Hasło właściciela jest kluczem głównym. Każdy, kto zna hasło właściciela, omija wszystkie ograniczenia. Traktuj je jak poświadczenie root; nigdy nie dostarczaj go wraz z dokumentem ani nie zapisuj w logach.
#[SensitiveParameter]to obrona w głąb. Usuwa hasła ze śladów stosu PHP, ale nadal musisz trzymać je z dala od własnych logów, komunikatów wyjątków i raportów awarii.
Rezydencja danych i ograniczanie PII
Dział zatytułowany „Rezydencja danych i ograniczanie PII”Biblioteka wykonuje szyfrowanie wewnątrz procesu. Nigdzie nie przesyła dokumentu ani haseł. Silnik nie zapisuje na dysku żadnego hasła, klucza ani bajtu dokumentu poza zaszyfrowanym wyjściem, które zapisujesz. Lokalizacja pliku wyjściowego i sposób przechowywania haseł to kwestie wdrożenia, za które odpowiada integrator. Biblioteka nie zapewnia żadnej gwarancji rezydencji. Jeśli dokument w postaci jawnej zawiera dane osobowe, są one chronione tylko na tyle, na ile pozwala najsłabsze hasło oraz powyższe zastrzeżenie o współpracującym czytniku. Szyfrowanie nie zastępuje minimalizacji danych osobowych (PII) umieszczanych w dokumencie.
Bezpieczna telemetria i czyszczenie logów
Dział zatytułowany „Bezpieczna telemetria i czyszczenie logów”Mechanizm szyfrowania emituje zdarzenie EncryptionAppliedEvent, które zawiera wyłącznie nazwę algorytmu (AES-256) oraz trzy wartości logiczne podsumowujące, czy operacje print/copy/modify są dozwolone — żadne hasło, klucz, sól ani wektor inicjujący nigdy nie trafiają do zdarzenia (src/Event/Security/EncryptionAppliedEvent.php). Ścieżka OpenTelemetry przepuszcza atrybuty span przez sanitizer oparty na liście dozwolonych (src/Telemetry/AttributeSanitizer.php), który bezwarunkowo odrzuca hasła i ścieżki plików; przetrwają tylko dozwolone klucze o wartościach skalarnych. Nie dodawaj haseł ani materiału klucza do span, logów ani komunikatów wyjątków we własnym kodzie integracji. Znaczniki #[SensitiveParameter] chronią ślady stosu, ale nie chronią ciągów, które budujesz samodzielnie.
Model zagrożeń
Dział zatytułowany „Model zagrożeń”W zakresie: przeciwnik, który zdobywa zaszyfrowany plik, ale nie zna haseł. Nie może odczytać treści, z zastrzeżeniem siły hasła, a plik nie ujawnia postaci jawnej. Poza zakresem: przeciwnik, który ma hasło użytkownika lub właściciela; czytnik niezgodny ze standardem, który ignoruje bity uprawnień; offline’owy brute-force słabego hasła; wykrywanie manipulacji (szyfrowanie zapewnia poufność, a nie integralność); kanały boczne w hostowej kompilacji OpenSSL; oraz przechowywanie kluczy, które w całości leży w gestii integratora. Udokumentowanie tych zagrożeń nie potwierdza braku podatności.
Zachowanie w trybie FIPS
Dział zatytułowany „Zachowanie w trybie FIPS”Hostowa kompilacja OpenSSL dostarcza prymitywy kryptograficzne, więc stan FIPS jest właściwością hosta, a nie ustawieniem biblioteki. CryptoCapabilities::detectFipsMode() zwraca trójstanowy FipsModeDetection (src/Security/FipsModeDetection.php): FIPS_ACTIVE, FIPS_ABSENT lub INDETERMINATE. Rozszerzenie PHP openssl nie udostępnia powiązania z modelem dostawców OpenSSL 3, więc sondowanie działa w trybie best-effort. INDETERMINATE jest traktowane jako „FIPS niepotwierdzone” (fail-closed) i można je odróżnić w telemetrii, aby operator mógł zareagować. NextPDF nie deklaruje walidacji FIPS 140; uruchamianie na OpenSSL walidowanym pod kątem FIPS jest odpowiedzialnością operatora, a wynik wykrywania ma charakter doradczy.
Zgodność
Dział zatytułowany „Zgodność”| Stwierdzenie | Specyfikacja | Punkt | reference_id |
|---|---|---|---|
Kod V słownika szyfrowania wybiera algorytm szyfrowania. | ISO 32000-2 | §7.6 | |
Metoda filtra szyfrowania AESV3 jest nazwana przez wpis CFM. | ISO 32000-2 | §7.6 | |
Wpis P to 32-bitowa liczba uprawnień dostępu bez znaku. | ISO 32000-2 | §7.6 | |
| Bit uprawnień 3 steruje drukowaniem. | ISO 32000-2 | §7.6 | |
| Bit uprawnień 6 steruje adnotacjami / wypełnianiem formularzy. | ISO 32000-2 | §7.6 | |
| Szyfrowanie chroni zawartość przed nieautoryzowanym dostępem (poufność). | ISO 32000-2 | §7.6 | |
| Wyprowadzanie klucza w wersji 6 wykorzystuje solone iteracyjne haszowanie (Algorytm 2.B). | ISO 32000-2 | §7.6 | |
| CBC to tryb poufności (a nie tryb integralności). | NIST SP 800-38A | §6.2 | |
| Wektory inicjujące CBC muszą być nieprzewidywalne. | NIST SP 800-38A | Zał. C |
NextPDF implementuje cytowane punkty. Nie deklaruje przy tym ogólnej zgodności z ISO 32000-2, walidacji FIPS 140 ani żadnej prawnej ani umownej gwarancji poufności. „Obsługa standardowego mechanizmu zabezpieczeń” nie jest certyfikacją bezpieczeństwa w Twoim wdrożeniu. Bezpieczeństwo wdrożenia zależy od przechowywania haseł i polityki weryfikatora poza biblioteką.