Pop PDF
Build

Encryption & Permissions

A Document\Security object on the document is all it takes to encrypt what gets written. Set it and Pdf::writeToFile() produces a password-protected PDF; leave it unset and nothing changes.

Encryption support is consistent across readers in a way form support is not — every mainstream reader opens an AES-encrypted PDF given the password. Permissions are the part that varies: they are flags a reader agrees to honor, not locks the file enforces, so treat them as intent rather than protection.

Password-Protecting a Document#

Security takes a user password and an owner password, and setSecurity() puts it on the document:

PHP
use Pop\Pdf\Document;
use Pop\Pdf\Document\Font;
use Pop\Pdf\Document\Page;
use Pop\Pdf\Document\Page\Text;
use Pop\Pdf\Document\Security;
use Pop\Pdf\Pdf;

$document = new Document(new Page(Page::LETTER));
$document->addFont(Font::HELVETICA);
$document->getPage(1)->addText(new Text('Confidential', size: 24), 'Helvetica', x: 72, y: 700);

$document->setSecurity(new Security('open-me', 'admin123'));

Pdf::writeToFile($document, filename: __DIR__ . '/protected.pdf');

The two passwords do different jobs:

  • the user password is what opens the document at all, and is subject to the permissions below
  • the owner password grants full access regardless of them
  • either one may be null — an owner password left unset is generated for you, so permissions cannot be bypassed by leaving it blank
  • hasSecurity() and getSecurity() report what a document is carrying; a document with no setSecurity() call writes out unencrypted

Encryption is AES-256 by default. Security::AES_128 selects the older algorithm, which older readers are likelier to open:

PHP
use Pop\Pdf\Document\Security;

$security = new Security('open-me', algorithm: Security::AES_128);
$security->getAlgorithm(); // 'AES128' — 'AES256' is the default

Restricting What a Reader Allows#

A Permissions object narrows what the document allows once the user password has opened it. Every permission starts allowed, so only the ones being taken away need naming:

PHP
use Pop\Pdf\Document\Permissions;
use Pop\Pdf\Document\Security;

$permissions = new Permissions();
$permissions->allowPrinting(false)->allowCopying(false);

$security = new Security('open-me', 'admin123');
$security->setPermissions($permissions);

The eight permissions, each with an allow*() setter and an is*Allowed() getter:

Permission Governs
allowPrinting() printing at all
allowHighResPrinting() printing at full resolution rather than a degraded copy
allowModifying() changing the document's contents
allowCopying() selecting and copying text and graphics
allowAnnotating() adding comments and annotations
allowFillingForms() filling in form fields
allowExtractingForAccessibility() extraction by screen readers and other assistive software
allowAssembling() inserting, rotating and deleting pages

They compile down to the single integer the PDF specification calls /P, which toPValue() reports:

PHP
use Pop\Pdf\Document\Permissions;

$permissions = new Permissions();
$permissions->isPrintingAllowed(); // true — everything starts allowed

$permissions->allowPrinting(false)->allowCopying(false);
$permissions->isPrintingAllowed(); // false
$permissions->toPValue();          // -24

Whether any of it is obeyed is up to the reader, which is why a permission is not a substitute for simply not handing someone the file.

What Gets Encrypted#

Everything Pop PDF writes: page content streams, embedded images, embedded font files, and the literal strings it authors — metadata, annotation URLs and form field names and values. Compression and encryption work together, compression first, so setCompression(true) needs no special handling.

One gap is worth knowing before you rely on it. Calling setSecurity() on a document that came from importFromFile(), importRawData(), merge() or mergeRawData() encrypts every stream, but leaves literal strings carried over verbatim from the source — an original annotation's URL, an original form field's name — unencrypted, while the file claims all strings are encrypted. A reader then tries to decrypt text that was never encrypted and corrupts it. Until that is closed, encrypt documents you built rather than documents you imported.

See Also#