Ga naar inhoud

Een PDF versleutelen en rechten beperken

Dit recipe versleutelt een document met de standaardbeveiligingshandler van Advanced Encryption Standard (AES)-256. Je stelt een gebruikerswachtwoord in (vereist om te openen), een eigenaarswachtwoord (volledige toegang) en een rechtenbitmasker dat bewerkingen beperkt. Behandel die rechten als afhankelijk van medewerking van de reader: versleuteling biedt vertrouwelijkheid, geen integriteit, en alleen meewerkende software respecteert de rechtenbits. Het recipe volgt examples/22-protection.php.

Vertrouwensgrens (neem dit mee bij elke bewering over rechten). PDF-versleuteling beschermt de vertrouwelijkheid van de inhoud tegen partijen zonder het wachtwoord (ISO 32000-2 §7.6). Het beschermt niet de integriteit: het detecteert noch voorkomt wijziging. De rechtenvermelding P is een 32-bits unsigned rechtenwaarde waarmee conforme readers wordt gevraagd beperkingen te respecteren; het is geen toegangscontrole. Een niet-conforme tool, of elke tool die met het eigenaarswachtwoord wordt gebruikt, kan elke “geweigerde” bewerking uitvoeren. Beschrijf een versleutelde PDF niet als “veilig”, “manipulatiebestendig” of “kopieerbeveiligd”.

Terminal window
composer require nextpdf/core:^3

Schakel de PHP-extensie openssl in. De AES-256-encryptor gebruikt die voor de cipher en de sleutelafleiding.

De V/R-codes van het versleutelingswoordenboek selecteren de standaardbeveiligingshandler (ISO 32000-2 §7.6). De Aes256Encryptor van NextPDF implementeert het AESV3-cryptfilter op revisie 6 van de beveiligingshandler (V=5/R=6). Het gebruikt een willekeurige 256-bits bestandsversleutelingssleutel, gezouten iteratieve-hash-sleutelafleiding (Algoritme 2.B) en AES-256-CBC-versleuteling per object met een willekeurige initialisatievector. Cipher Block Chaining (CBC) is een vertrouwelijkheidsmodus (NIST SP 800-38A). De initialisatievectoren daarvan moeten onvoorspelbaar zijn.

De initialisatievector is nieuw voor elk object en elke uitvoering, waardoor de ruwe bytes per uitvoering verschillen. Het reproduceerbaarheidsprofiel is daarom structural. Voordat de harness twee uitvoeringen vergelijkt, canonicaliseert die de versleutelings-IV, de objectvolgorde en de trailer-/ID. Dit profiel is strenger dan het profiel voor een recipe zonder versleuteling.

Het rechtenbitmasker stelt de vermelding P in. Bit 3 staat afdrukken toe en bit 6 staat annotation/form-fill toe. De waarde is de gedocumenteerde 32-bits unsigned waarde.

NextPDF\Core\Concerns\HasSecurity (vermengd in Document):

  • setEncryption(#[SensitiveParameter] string $userPassword, #[SensitiveParameter] string $ownerPassword = '', int $permissions = -1): static — configureert AES-256-versleuteling met de standaardhandler. permissions = -1 verleent alle rechten. Als ownerPassword leeg is, wordt het gebruikerswachtwoord hergebruikt als eigenaarswachtwoord. Roep dit aan vóór addPage().
  • getEncryptor(): ?Aes256Encryptor — de geconfigureerde encryptor, of null.
  • useAesGcm(?bool $enabled = true): static — kiest voor ISO/TS 32003 AES-256-GCM; werpt een uitzondering als de host-OpenSSL/libsodium de cipher niet biedt.

Beide wachtwoordparameters zijn gemarkeerd als #[SensitiveParameter], zodat PHP ze uit stacktraces weglaat.

Rechtenbits (de vermelding P, lage bits 3–6 in gangbaar gebruik):

BitWaardeBewerking
34Het document afdrukken
48Documentinhoud wijzigen
516Tekst en afbeeldingen kopiëren / extraheren
632Annotaties toevoegen of wijzigen en formuliervelden invullen
<?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";

Het volledige voorbeeld hieronder weerspiegelt examples/22-protection.php en schrijft naar NEXTPDF_COOKBOOK_OUTPUT voor de harness.

<?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";

Verwachte uitvoer:

Wrote encrypted PDF (AES-256, printing only)

Wanneer je het bestand opent, vraagt de reader om een wachtwoord. Het gebruikerswachtwoord opent het met de beperkte set rechten. Het eigenaarswachtwoord opent het met volledige toegang.

  • Aanroepvolgorde. setEncryption() na addPage() versleutelt eerdere inhoud niet met terugwerkende kracht. Configureer de versleuteling eerst; de engine versleutelt elke objectbody zodra die wordt geschreven.
  • Standaard eigenaarswachtwoord. Bij een leeg eigenaarswachtwoord hergebruikt de engine het gebruikerswachtwoord als eigenaarswachtwoord. Daardoor blijft er feitelijk geen bevoorrechte rol over. Stel afzonderlijke wachtwoorden in wanneer de twee rollen moeten verschillen.
  • Rechtensemantiek is adviserend. Alleen conforme readers respecteren de bits. Ze worden niet cryptografisch afgedwongen: een niet-conforme tool, of elke tool die met het eigenaarswachtwoord wordt gebruikt, kan beperkte bewerkingen uitvoeren. Behandel rechten als een beleidssignaal aan meewerkende software, nooit als toegangscontrole die bestand is tegen een vastberaden partij.
  • Geen integriteitsgarantie. Versleuteling is vertrouwelijkheid, geen integriteit. Een aanvaller zonder het wachtwoord kan de inhoud niet lezen, maar het formaat zelf detecteert geen manipulatie. Integriteitsbescherming vereist een afzonderlijk mechanisme, zoals een digitale handtekening of een ISO/TS 32004 document-MAC.
  • PDF/A-conflict. PDF/A verbiedt de trailersleutel Encrypt. Het aanroepen van setEncryption() op een PDF/A-document werpt, in welke volgorde dan ook, een incompatibiliteitsuitzondering.
  • AES-256-GCM-opt-in. useAesGcm() selecteert ISO/TS 32003 GCM-bulkversleuteling wanneer de host-OpenSSL of libsodium deze biedt. Anders werpt het InvalidConfigException. Het is om dezelfde reden incompatibel met PDF/A.
  • Versleuteling met openbare sleutel is nog niet aangesloten. setPublicKeyEncryption() bevriest het API-oppervlak, maar save() werpt een uitzondering totdat de writer-aansluiting gereed is (een bekend defect). Gebruik dit niet in productie op Core.

Sleutelafleiding voert de geïtereerde hash van Algoritme 2.B eenmaal per document uit. AES-256-CBC per object schaalt lineair met de grootte van de objectbody. Voor typische documenten blijven de kosten ruim binnen het budget van 1500 ms / 64 MB. Zeer grote documenten brengen per object AES-doorvoerkosten met zich mee. Galois/Counter Mode (GCM) met AES-NI is sneller op geschikte hosts.

  • Alleen vertrouwelijkheid. Ter herhaling van de vertrouwensgrens: versleuteling houdt inhoud weg bij partijen zonder het wachtwoord. Het bewijst niet dat het bestand ongewijzigd is, en de rechtenbits hangen af van medewerking van de reader.
  • Wachtwoordsterkte is jouw verantwoordelijkheid. De handler is slechts zo sterk als de wachtwoorden. Zodra iemand het bestand verkrijgt, kan een zwak gebruikerswachtwoord offline met brute kracht worden gekraakt; het formaat kan pogingen niet beperken.
  • Het eigenaarswachtwoord is een hoofdsleutel. Iedereen met het eigenaarswachtwoord omzeilt elke beperking. Behandel het als een root-credential; lever het nooit mee met het document en log het niet.
  • #[SensitiveParameter] is verdediging in de diepte. Het verwijdert de wachtwoorden uit PHP-stacktraces, maar je moet ze nog steeds uit je eigen logs, uitzonderingsberichten en crashrapporten houden.

De bibliotheek voert versleuteling in-process uit. Ze verzendt het document of de wachtwoorden nergens naartoe. De engine schrijft geen wachtwoord, sleutel of documentbyte naar schijf, behalve de versleutelde uitvoer die je opslaat. Waar het uitvoerbestand zich bevindt en hoe wachtwoorden worden bewaard, zijn implementatiekeuzes waarvoor de integrator verantwoordelijk is. De bibliotheek geeft geen locatiegarantie. Als het document persoonsgegevens in platte tekst bevat, zijn die gegevens slechts zo goed beschermd als het zwakste wachtwoord en het hierboven genoemde voorbehoud over de meewerkende reader. Versleuteling is geen vervanging voor het minimaliseren van de persoonlijk identificeerbare informatie (PII) die je in het document plaatst.

Versleuteling zendt een EncryptionAppliedEvent uit dat alleen de algoritmenaam (AES-256) en drie booleans bevat die samenvatten of print/copy/modify zijn toegestaan — er wordt nooit een wachtwoord, sleutel, salt of IV op het event geplaatst (src/Event/Security/EncryptionAppliedEvent.php). Het OpenTelemetry-pad leidt span-attributen door een allowlist-sanitizer (src/Telemetry/AttributeSanitizer.php) die wachtwoorden en bestandspaden onvoorwaardelijk afwijst; alleen toegestane sleutels met scalaire waarden blijven over. Voeg in je eigen integratiecode geen wachtwoord- of sleutelmateriaal toe aan spans, logs of uitzonderingsberichten. De #[SensitiveParameter]-markeringen beschermen stacktraces, maar niet de strings die je zelf opbouwt.

In scope: een tegenstander die het versleutelde bestand verkrijgt, maar niet de wachtwoorden. Die kan de inhoud niet lezen, afhankelijk van de wachtwoordsterkte, en het bestand lekt geen platte tekst. Buiten scope: een tegenstander die het gebruikers- of eigenaarswachtwoord heeft; een niet-conforme reader die de rechtenbits negeert; offline brute kracht op een zwak wachtwoord; manipulatiedetectie (versleuteling biedt vertrouwelijkheid, geen integriteit); zijkanalen in de host-OpenSSL-build; en sleutelbeheer, dat volledig de verantwoordelijkheid van de integrator is. Het documenteren van deze dreigingen beweert niet dat er geen kwetsbaarheden zijn.

De host-OpenSSL-build levert de cryptografische primitieven, dus de FIPS-houding is een eigenschap van de host en geen bibliotheekinstelling. CryptoCapabilities::detectFipsMode() retourneert een drietoestands-FipsModeDetection (src/Security/FipsModeDetection.php): FIPS_ACTIVE, FIPS_ABSENT, of INDETERMINATE. De PHP-openssl-extensie biedt geen binding voor het OpenSSL 3-providermodel, dus de detectie is beste-inspanning. INDETERMINATE wordt behandeld als “FIPS niet bewezen” (fail-closed), met onderscheidbare telemetrie die operators kunnen gebruiken. NextPDF claimt geen FIPS 140-validatie; draaien op een FIPS-gevalideerde OpenSSL is de verantwoordelijkheid van de operator, en het detectieresultaat is adviserend.

BeweringSpecClausulereference_id
De V-code van het versleutelingswoordenboek selecteert het versleutelingsalgoritme.ISO 32000-2§7.6
De AESV3-cryptfiltermethode wordt aangeduid door de vermelding CFM.ISO 32000-2§7.6
De vermelding P is een 32-bits toegangsrechtengrootheid zonder teken.ISO 32000-2§7.6
Rechtenbit 3 regelt het afdrukken.ISO 32000-2§7.6
Rechtenbit 6 regelt annotatie / formulierinvulling.ISO 32000-2§7.6
Versleuteling beschermt de inhoud tegen ongeoorloofde toegang (vertrouwelijkheid).ISO 32000-2§7.6
De sleutelafleiding van revisie 6 gebruikt gezouten iteratieve hashing (Algoritme 2.B).ISO 32000-2§7.6
CBC is een vertrouwelijkheidsmodus (geen integriteitsmodus).NIST SP 800-38A§6.2
CBC-initialisatievectoren moeten onvoorspelbaar zijn.NIST SP 800-38ABijl. C

NextPDF implementeert de aangehaalde clausules. Het claimt geen algehele ISO 32000-2-conformiteit, FIPS 140-validatie, of enige juridische of contractuele garantie van vertrouwelijkheid. “Ondersteuning voor de standaardbeveiligingshandler” is geen beveiligingscertificering voor jouw implementatie. Die hangt af van wachtwoordbeheer en verificatiebeleid buiten de bibliotheek.