Standard and Public Key Encryption Reading and writing PDF documents with standard password encryption

Introduction

A PDF document can be encrypted to protect its content from unauthorized access. The PDF specification defines 2 security handlers: Standard Security Handler and a Public-Key Security Handler.

The Standard Security handler allows you to define access permissions and up to two passwords: An user password, to open the document with respect to the defined access permissions and an owner password which will give you full access to the document.

The Public-Key Security handler allows you to specify unique access permissions for different recipients. Only specified recipients will be able to open the encrypted PDF document. For encryption and decryption a pair of public and private key is used. 

Different Versions and Algorithms

The PDF specification defines different algorithms and key lengths to be used to encrypt and/or decrypt a PDF documents content: RC4 40 bits, RC4 128 bits, AES 128 bits, AES 256 bits. 

The SetaPDF-Core component supports all available versions and revisions. The different algorithms are represented by individual factory methods in SetaPDF (Standard and Public-key). 

Permissions

Permissions are represented in SetaPDF via class constants of the SetaPDF_Core_SecHandler class. An additional permission flag for public-key security is available in the SetaPDF_Core_SecHandler_PublicKey class. 

All flags are integer values and can be combined by binary or operation:

PHP
// allow printing and copying
$permission = SetaPDF_Core_SecHandler::PERM_PRINT | SetaPDF_Core_SecHandler::PERM_COPY;

Following definitions are taken from PDF 32000-1:2008 - Table 22 - User access permissions:  

public const integer SetaPDF_Core_SecHandler::PERM_ACCESSIBILITY = 512

Permission constant.

For handlers of revision 3 or greater: Extract text and graphics (in support of accessibility to users with disabilities or for other purposes).

See
  • PDF 32000-1:2008 - Table 22 - User access permissions
public const integer SetaPDF_Core_SecHandler::PERM_ANNOT = 32

Permission constant.

Add or modify text annotations, fill in interactive form fields, and, if SetaPDF_Core_SecHandler::PERM_MODIFY is also set, create or modify interactive form fields (including signature fields).

See
  • PDF 32000-1:2008 - Table 22 - User access permissions
public const integer SetaPDF_Core_SecHandler::PERM_ASSEMBLE = 1024

Permission constant.

For handlers of revision 3 or greater: Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if SetaPDF_Core_SecHandler::PERM_MODIFY is not set.

See
  • PDF 32000-1:2008 - Table 22 - User access permissions
public const integer SetaPDF_Core_SecHandler::PERM_COPY = 16

Permission constant.

For handlers of revision 2: Copy or otherwise extract text and graphics from the document, including extracting text and graphics (in support of accessibility to users with disabilities or for other purposes).

For handlers of revision 3 or greater: Copy or otherwise extract text and graphics from the document by operations other than that controlled by bit SetaPDF_Core_SecHandler::PERM_ACCESSIBILITY.

See
  • PDF 32000-1:2008 - Table 22 - User access permissions
public const integer SetaPDF_Core_SecHandler::PERM_DIGITAL_PRINT = 2048

Permission constant.

Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set (and SetaPDF_Core_SecHandler::PERM_PRINT is set), printing is limited to a low-level representation of the appearance, possibly of degraded quality.

See
  • PDF 32000-1:2008 - Table 22 - User access permissions
public const integer SetaPDF_Core_SecHandler::PERM_FILL_FORM = 256

Permission constant.

For handlers of revision 3 or greater: Fill in existing interactive form fields (including signature fields), even if SetaPDF_Core_SecHandler::PERM_ANNOT is not set.

See
  • PDF 32000-1:2008 - Table 22 - User access permissions
public const integer SetaPDF_Core_SecHandler::PERM_MODIFY = 8

Permission constant.

Modify the contents of the document by operations other than those controlled by SetaPDF_Core_SecHandler::PERM_ANNOT, SetaPDF_Core_SecHandler::PERM_FILL_FORM and SetaPDF_Core_SecHandler::PERM_ASSEMBLE.

See
  • PDF 32000-1:2008 - Table 22 - User access permissions
public const integer SetaPDF_Core_SecHandler::PERM_PRINT = 4

Permission constant.

For handlers of revision 2: Print the document.

Handlers of a revision of 3 or greater: Print the document (possibly not at the highest quality level, depending on whether SetaPDF_Core_SecHandler::PERM_DIGITAL_PRINT is also set).

See
  • PDF 32000-1:2008 - Table 22 - User access permissions

Permission constant.

When set permits change of encryption and enables all other permissions.

See
  • PDF 32000-1:2008 - Table 24 - Public-Key security handler user access permissions

Authenticate Against an Existing Security Handler

If a PDF document is loaded by the SetaPDF-Core component you may check for a security handler and authenticate on it.

All components will check for a security handler and its permissions when doing modifications of the document. If no authentication is done by you, the component will try to authenticate with an empty user password, which will only work for PDF documents using standard security.

If the security handler rejects a permission an SetaPDF_Core_SecHandler_Exception will be thrown.

With User or Owner Password (Standard)

The standard security handler allows you to authenticate with an user or owner password: 

PHP
if ($document->hasSecHandler()) {
    // get the security handler
    $secHandler = $document->getSecHandler();

    // authenticate with a password without knowing if it is the owner or user password:
    if ($secHandler->auth('a secret password')) {
        echo 'authenticated as ' . $secHandler->getAuthMode();
    } else {
        echo 'authentication failed - neither user nor owner password did match.';
    }

    // authenticate with the user password:
    if ($secHandler->authByUserPassword('a secret password')) {
        echo 'authenticated as user';
    } else {
        echo 'authentication failed with the user password.';
    }

    // authenticate with the owner password:
    if ($secHandler->authByOwnerPassword('a secret password')) {
        echo 'authenticated as owner';
    } else {
        echo 'authentication failed with the owner password.';
    }
}

With Certificate and Private Key (Public-key)

The public-key security handler allows you to authenticate with a certificate and private-key: 

PHP
if ($document->hasSecHandler()) {
    // get the security handler
    $secHandler = $document->getSecHandler();

    // authenticate with private key in a .pdf/.p12 file:
    $cert = file_get_contents('/path/to/pkcs12-container.pfx');
    $data = array();
    if (!openssl_pkcs12_read($cert, $data, 'password')) {
        // error handling
    }

    if ($secHandler->auth($data['cert'], $data['pkey'])) {
        echo 'authenticated as ' . $secHandler->getAuthMode();
    } else {
        echo 'authentication failed.';
    }

    // authenticate with a private key and certificate in a single PEM file:
    $cert = file_get_contents('/path/to/cert-with-private-key.pem');
    if ($secHandler->auth($cert)) {
        echo 'authenticated as ' . $secHandler->getAuthMode();
    } else {
        echo 'authentication failed.';
    }
}

Remove a Security Handler

A security handler can be removed from a loaded document instance if you are authenticated as the "owner" against it.

It can be simply removed by setting the handler to null then: 

PHP
$document->setSecHandler(null);

A simple but complete PHP script, that loads an encrypted PDF document, authenticate as the owner, removes the security handler and save it back will look like:

PHP
<?php
require_once('library/SetaPDF/Autoload.php');

$writer = new \SetaPDF_Core_Writer_Http('decrypted.pdf');
$documnet = \SetaPDF_Core_Document::loadByFilename(
    'files/pdfs/Brand-Guide-Encrypted (owner-pw setasign).pdf',
    $writer
);

$documnet->getSecHandler()->auth('setasign');
$documnet->setSecHandler(null);

$documnet->save()->finish();

Create Standard Security Handlers

A standard security handler can be created by the factory() method of a helper class that represents the algorithm and revision:

SetaPDF_Core_SecHandler_Standard_Arcfour40::factory()
Algorithm uses RC4 and an encryption key length of 40 bits. (Version: 1, Revision: 2)
SetaPDF_Core_SecHandler_Standard_Arcfour128::factory()
Algorithm uses RC4 and a key length of 128 bits. (Version: 2, Revision: 3)
SetaPDF_Core_SecHandler_Standard_Arcfour128Cf::factory()
Algorithm uses RC4 and a key length of 128 bits. The version uses crypt filters that may allow you to specify if metadata will get encrypted or not. (Version: 4, Revision: 4)
SetaPDF_Core_SecHandler_Standard_Aes128::factory()
Algorithm uses AES and a key length of 128 bits. The version uses crypt filters that may allow you to specify if metadata will get encrypted or not. (Version: 4, Revision: 4)
SetaPDF_Core_SecHandler_Standard_Aes256R5::factory()
DEPRECATED: Algorithm uses AES and a key length of 256 bits. The version uses crypt filters that may allow you to specify if metadata will get encrypted or not. INFO: This algorithm will be marked as deprecated in PDF 2.0 because of security issues. (Version: 5, Revision: 5)
SetaPDF_Core_SecHandler_Standard_Aes256::factory()
Algorithm uses AES and a key length of 256 bits. The version uses crypt filters that may allow you to specify if metadata will get encrypted or not. It's an revision of the deprecated (re)vision 5. (Version: 5, Revision: 6)

An example: 

PHP
/* define a handler with both user and owner password, allow
 * print and copy, and do not encrypt metadata
 */
$secHandler = \SetaPDF_Core_SecHandler_Standard_Aes128::factory(
    $document,
    'the-owner-password',
    'the-user-password',
    \SetaPDF_Core_SecHandler::PERM_PRINT | \SetaPDF_Core_SecHandler::PERM_COPY,
    false
);

// Attach the handler to the document
$document->setSecHandler($secHandler);

Create Public-Key Security Handlers

A public-key security handler can be created by the factory() method of a helper class that represents the algorithm. Recipients are represented by a recipient class and need to be passed as an array to the factory() method.

In comparsion to the standard security handler, the public-key security handler allows you to define individual rights per recipient. The permissions of an owner, who is allowed, e.g. to change the security settings, is represented by the constant SetaPDF_Core_SecHandler_PublicKey::PERM_OWNER.

SetaPDF_Core_SecHandler_PublicKey_Arcfour128::factory()
Algorithm uses RC4 and a key length of 128 bits. (Version: 2)
SetaPDF_Core_SecHandler_PublicKey_Arcfour128Cf::factory()
Algorithm uses RC4 and a key length of 128 bits. The version uses crypt filters that may allow you to specify if metadata will get encrypted or not. (Version: 4)
SetaPDF_Core_SecHandler_PublicKey_Aes128::factory()
Algorithm uses AES and a key length of 128 bits. The version uses crypt filters that may allow you to specify if metadata will get encrypted or not. (Version: 4)
SetaPDF_Core_SecHandler_PublicKey_Aes256::factory()
Algorithm uses AES and a key length of 256 bits. The version uses crypt filters that may allow you to specify if metadata will get encrypted or not. (Version: 5)

An example: 

PHP
/* lets define some recipients:
 */
$recipients = array();
// Variant A: Simply pass the certificate data
$recipients[] = new SetaPDF_Core_SecHandler_PublicKey_Recipient(
    file_get_contents('/path/to/public/key/certificate-a.cer'),
    \SetaPDF_Core_SecHandler::PERM_PRINT | \SetaPDF_Core_SecHandler::PERM_DIGITAL_PRINT
);

// another recipient with the same permissions
// Variant B: Pass a resource received by openssl_x509_read()
$recipientB = openssl_x509_read(file_get_contents('/path/to/public/key/certificate-b.cer'));
$recipients[] = new \SetaPDF_Core_SecHandler_PublicKey_Recipient(
    $recipientB,
    \SetaPDF_Core_SecHandler::PERM_PRINT | \SetaPDF_Core_SecHandler::PERM_DIGITAL_PRINT
);

// another recipient: the owner
// Variant C: Get the private key from a pfx file
$certs = array();
$read = openssl_pkcs12_read(file_get_contents('/path/to/certificate-c.pfx'), $certs, 'password');
$recipients[] = new \SetaPDF_Core_SecHandler_PublicKey_Recipient(
    $certs['cert'],
    \SetaPDF_Core_SecHandler_PublicKey::PERM_OWNER
);

/* define a handler with the given recipients and do not encrypt metadata
 */
$secHandler = \SetaPDF_Core_SecHandler_PublicKey_Aes256::factory(
    $document, $recipients, false
);

// Attach the handler to the document
$document->setSecHandler($secHandler);

Encryption Engine

The SetaPDF-Core component comes with support for 2 encryption engines: OpenSSL and Mcrypt.

Releases before revision 864 used Mcrypt as their standard engine. Since 864 this has been changed to OpenSSL. You should avoid the usage of Mcrypt at all because it is going to be removed from PHP 7.1 and already was a candiate in the PHP RFC: Removal of dead or not yet PHP7 ported SAPIs and extensions.  

Actually you can switch the engine through a static property of the SetaPDF_Core_SecHandler class: 

PHP
// OpenSSL (default)
SetaPDF_Core_SecHandler::$engine = 'openssl';

// mcrypt
SetaPDF_Core_SecHandler::$engine = 'mcrypt';