Skip to content

Encrypt a PDF with AES-256 and set permissions

Use this recipe to encrypt a document with AES-256. You set a user password, an owner password, and a permission bitmask that restricts allowed operations. The recipe follows examples/22-protection.php.

Reproducibility — why this recipe is structural, not bitwise. An AES-256 PDF is never byte-identical between runs, even with the same input and passwords. Before it derives the file-encryption key, the revision-6 standard security handler generates 16 fresh random bytes (the user/owner validation and key salts) for each encryption with a strong random number generator (ISO 32000-2 §7.6.4, Algorithm 2.B). AES-256-CBC uses a random initialization vector for each object. The trailer /ID is also an array of two byte strings. Its first element is a permanent identifier derived from the file at creation time (ISO 32000-2 §14.4). The reproducibility profile is therefore structural: before it compares two runs, the harness canonicalises the encryption salts/IV, object order, and trailer /ID, instead of asserting an impossible byte match.

  • Core installed: composer require nextpdf/core:^3.
  • The openssl PHP extension, which the AES-256 encryptor requires.
  1. Create the document.
  2. Call setEncryption() before addPage(). The encryptor must be ready before any content object is written.
  3. Pass a user password (required to open the document), an owner password (for full access), and a permission bitmask.
  4. Add content, and save. The writer encrypts each object body.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
// Permission bits (ISO 32000-2, encryption dictionary P entry):
// bit 3 (4) allow printing
// bit 4 (8) allow content modification
// bit 5 (16) allow text extraction / copying
// bit 6 (32) allow annotation and form editing
// Grant printing only:
$permissions = 4;
$doc = Document::createStandalone();
$doc->setTitle('Encrypted Document');
// setEncryption() MUST run before addPage(). Order matters.
$doc->setEncryption(
userPassword: 'open-me',
ownerPassword: 'owner-secret',
permissions: $permissions,
);
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'This document is encrypted with AES-256.', newLine: true);
$doc->save(__DIR__ . '/encrypted.pdf');
echo "Wrote encrypted.pdf (AES-256, printing only)\n";
Wrote encrypted.pdf (AES-256, printing only)

When you open encrypted.pdf, the reader prompts for a password. The user password opens the document with the restricted permission set. The owner password opens it with full access.

  • Call order. setEncryption() after addPage() does not encrypt earlier content retroactively. Always configure encryption first.
  • Owner password default. When the owner password is empty, the engine reuses the user password as the owner password. Set distinct passwords when these roles must differ.
  • Permission semantics — boundary. Conforming readers honor the permission bits. They are not cryptographically enforced: a reader that ignores the bits, or a tool used with the owner password, can perform restricted operations. Treat permissions as a policy signal for cooperating software, not as access control that withstands a determined party.
  • PDF/A conflict. PDF/A prohibits the Encrypt trailer key. Calling setEncryption() on a PDF/A document, in either order, throws an incompatibility exception. See PDF/A-4 conformance gate.
  • AES-256-GCM. useAesGcm() opts in to ISO/TS 32003 GCM bulk encryption when the host OpenSSL or libsodium offers the cipher. Otherwise, it throws InvalidConfigException.
StatementSpecClausereference_id
The standard security handler defines the encryption algorithm and key length.ISO 32000-2§7.6
The encryption dictionary P entry carries the permission bits.ISO 32000-2§7.6

Encryption protects content confidentiality from parties without the password. Permission bits are advisory to readers and, on their own, do not prevent a non-conforming tool from acting.