Decrypt PDFs with AES on iOS

PSPDFKit supports fast, in-memory AES-256 decryption using the AESCryptoDataProvider class. You can encrypt an entire file with a password and salt, and by using 10,000 PBKDF iterations, the file is well protected against brute-force attacks (unlike solutions from Adobe). We also provide the AES Crypt Mac app that encrypts your PDF documents. Unlike NSData-based solutions, the PDF is never fully decrypted, and this even works with very large (> 500 MB) documents. Additionally, the file will never be written out to disk unencrypted, and the cache is automatically disabled for encrypted content.

The AESCryptoDataProvider allows a transparent decryption of AES-256 encrypted files using the RNCryptor file format. The data format expected by the data provider is structured like this:

Byte:     |    0    |    1    |      2-9       |  10-17   | 18-33 | <-      ...     -> | n-32 - n |
Contents: | version | options | encryptionSalt | HMACSalt |  IV   |    Encrypted PDF   |   HMAC   |

How to Decrypt Documents

To programmatically decrypt your encrypted PDF files using PSPDFKit, you need to use the AESCryptoDataProvider with the password and salt you used to encrypt the PDF:

// Location of your encrypted PDF.
let encryptedPDF = ...

// Note: For shipping apps, you need to protect this string better, making it harder for hackers to simply disassemble and receive the key from the binary. Or, you can add an internet service that fetches the key from an SSL API. But then there's still the slight risk of memory dumping with an attached GDB. Or screenshots. Security is never 100 percent perfect, but using AES makes it more difficult to get the PDF. You can even combine AES and a PDF password.
let passphrase = "afghadöghdgdhfgöhapvuenröaoeruhföaeiruaerub"
let salt = "ducrXn9WaRdpaBfMjDTJVjUf3FApA6gtim0e61LeSGWV9sTxB0r26mPs59Lbcexn"

// PSPDFKit doesn't want to keep the passphrase in memory any longer than it has to. This is the reason we use a passphrase provider.
// For optimal results, always fetch the passphrase/password from secure storage (like the keychain) and never keep it in memory.
let passphraseProvider = { passphrase }

guard let cryptoWrapper = AESCryptoDataProvider(url: encryptedPDF, passphraseProvider: passphraseProvider, salt: salt, rounds: AESCryptoDataProvider.defaultNumberOfPBKDFRounds) else {
    return
}

// Create a document.
let document = Document(dataProviders: [cryptoWrapper])
document.uid = encryptedPDF.lastPathComponent // Manually set a UID for encrypted documents.
// Location of your encrypted PDF.
NSURL *const encryptedPDF = ...

// Note: For shipping apps, you need to protect this string better, making it harder for hackers to simply disassemble and receive the key from the binary. Or, you can add an internet service that fetches the key from an SSL API. But then there's still the slight risk of memory dumping with an attached GDB. Or screenshots. Security is never 100 percent perfect, but using AES makes it more difficult to get the PDF. You can even combine AES and a PDF password.
NSString *const passphrase = @"afghadöghdgdhfgöhapvuenröaoeruhföaeiruaerub";
NSString *const salt = @"ducrXn9WaRdpaBfMjDTJVjUf3FApA6gtim0e61LeSGWV9sTxB0r26mPs59Lbcexn";

// PSPDFKit doesn't want to keep the passphrase/password in memory any longer than it has to. This is the reason we use a passphrase provider.
// For optimal results, always fetch the passphrase from secure storage (like the keychain) and never keep it in memory.
NSString * (^const passphraseProvider)(void) = ^() {
    return passphrase;
};

PSPDFAESCryptoDataProvider *cryptoWrapper = [[PSPDFAESCryptoDataProvider alloc] initWithURL:encryptedPDF passphraseProvider:passphraseProvider salt:salt rounds:PSPDFDefaultPBKDFNumberOfRounds];

// Create a document.
PSPDFDocument *document = [[PSPDFDocument alloc] initWithDataProviders:@[cryptoWrapper]];
document.UID = encryptedPDF.lastPathComponent; // Manually set a UID for encrypted documents.

For more information about this process, please take a look at AESCryptoDataProviderExample.swift in our Catalog example.

Dealing with Large Encrypted Files

If you implement custom encryption, you should provide an object implementing the DataProviding protocol and initialize the Document using init(dataProviders:).

PSPDFKit already implements the AESCryptoDataProvider. It uses DataProviding and provides all the delegates to dynamically decrypt just the required part of the PDF on the fly. All you need to do is provide the unencrypted file size and the callbacks for direct access.

The naive approach to this would be using Data with DataContainerProvider, but this is limited to the amount of available memory, and it’s generally not a good idea once your documents are larger than 20 MB.

❗Important: You should disable the disk cache by setting its allowedDiskSpace to 0. Otherwise, it’ll essentially invalidate the benefits of encrypting the PDF.