Blog Post

Secure Documents with PSPDFKit for iOS

Illustration: Secure Documents with PSPDFKit for iOS

PSPDFKit for iOS allows you to work with secured and encrypted documents. In addition to having default PDF password encryption, PSPDFKit also allows you to work with both large encrypted files and an [encrypted library][encryption in pspdflibrary guide], database, and cache.

In this article, we’ll discuss the various strategies to secure PDFs in your app.

So let’s get started!

PDF Password Encryption

You can open and create PDF documents using default PDF password encryption. Below, we’ll see how to accomplish these tasks in just a few lines of code.

Open a Password-Protected Document

If you open a PDF document that is password-protected, PSPDFKit will show a password prompt to unlock the document.

locked-document

You can also programmatically unlock the document before presenting it by using -[PSPDFDocument unlockWithPassword:], like so:

let document = ...

// Programmatically unlock the document.
document.unlock(withPassword: "test123")

// Use the unlocked document in a `PSPDFViewController`.
let pdfController = PSPDFViewController(document: document)
PSPDFDocument *document = ...

// Programmatically unlock the document.
[document unlockWithPassword:@"test123"];

// Use the unlocked document in a `PSPDFViewController`.
PSPDFViewController *controller = [[PSPDFViewController alloc] initWithDocument:document];

See our Password preset and Password not preset examples from our Catalog app for more information.

Create a Password-Protected Document

PSPDFKit also allows you to create password-protected documents using an owner and user password via the Document Processing API, like so:

let userPassword = "test123"
let ownerPassword = "test456"
let lockedDocumentURL = // URL to store the newly created document to.
let originalDocument = // `PSPDFDocument` that should be locked.

// By default, a newly initialized `PSPDFProcessorConfiguration` results in an exported document that is the same as the input.
let processorConfiguration = PSPDFProcessorConfiguration(document: originalDocument)

// Set the proper password and key length in `PSPDFDocumentSecurityOptions`.
let documentSecurityOptions = try PSPDFDocumentSecurityOptions(ownerPassword: ownerPassword, userPassword: userPassword, keyLength: PSPDFDocumentSecurityOptionsKeyLengthAutomatic)

DispatchQueue.global(qos: .default).async {
    let processor = PSPDFProcessor(configuration: processorConfiguration!, securityOptions: documentSecurityOptions)
    processor.delegate = self
    try? processor.write(toFileURL: tempURL)
    DispatchQueue.main.async {
        // Show the newly created locked PDF.
        let lockedDocument = PSPDFDocument(url: lockedDocumentURL)
        let pdfController = PSPDFViewController(document: lockedDocument)
    }
}
NSString *userPassword = @"test123";
NSString *ownerPassword = @"test456";
NSURL *lockedDocumentURL = // URL to store the newly created document to.
PSPDFDocument *originalDocument = // `PSPDFDocument` that should be locked.

// By default, a newly initialized `PSPDFProcessorConfiguration` results in an exported document that is the same as the input.
PSPDFProcessorConfiguration *processorConfiguration = [[PSPDFProcessorConfiguration alloc] initWithDocument:originalDocument];

// Set the proper password and key length in `PSPDFDocumentSecurityOptions`.
PSPDFDocumentSecurityOptions *documentSecurityOptions = [[PSPDFDocumentSecurityOptions alloc] initWithOwnerPassword:ownerPassword userPassword:userPassword keyLength:PSPDFDocumentSecurityOptionsKeyLengthAutomatic error:NULL];
dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
    PSPDFProcessor *processor = [[PSPDFProcessor alloc] initWithConfiguration:processorConfiguration securityOptions:documentSecurityOptions];
    [processor writeToFileURL:lockedDocumentURL error:NULL];

    dispatch_async(dispatch_get_main_queue(), ^{
        // Show the newly created locked PDF.
        PSPDFDocument *lockedDocument = [[PSPDFDocument alloc] initWithURL:lockedDocumentURL];
        PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:lockedDocument];
    });
});

Take a look at the Create password protected PDF example from our Catalog sample project for more details.

In-Memory Decryption Using PSPDFAESCryptoDataProvider

You can add an additional layer of security with support for state-of-the-art, fast, in-memory AES-256 decryption using the PSPDFAESCryptoDataProvider class.

You can encrypt an entire file with a password and a salt and by using 10,000 PBKDF iterations. This will ensure that the file is well protected against brute-force attacks.

We also provide the AES Crypt app for Mac, which encrypts your PDF documents. Unlike with NSData-based solutions, the PDF is never fully decrypted, and the app even works with very large (> 500 MB) documents. The file also will never be written out unencrypted to disk, and the cache is automatically disabled for encrypted content.

The implementation would look like this:

// Note: For shipping apps, you need to protect this string better,
// in order to make it more difficult for a hacker to simply disassemble and receive
// the key from the binary. Or, 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 way harder to get the PDF. You can even
// combine AES and a PDF password.
let passphrase = "afghadöghdgdhfgöhapvuenröaoeruhföaeiruaerub"
let salt = "ducrXn9WaRdpaBfMjDTJVjUf3FApA6gtim0e61LeSGWV9sTxB0r26mPs59Lbcexn"

// Generate the crypto data provider.
guard let encryptedPDF = Bundle.main.resourceURL?.appendingPathComponent("encrypted.pdf.aes"),
    let cryptoDataProvider = PSPDFAESCryptoDataProvider(url: encryptedPDF, passphraseProvider: { passphrase }, salt: salt, rounds: PSPDFDefaultPBKDFNumberOfRounds) else {
        return
}

// Create the `PSPDFDocument`.
let document = PSPDFDocument(dataProviders: [cryptoDataProvider])
document.uid = encryptedPDF.lastPathComponent // Manually set a UID for encrypted documents.

// `PSPDFAESCryptoDataProvider` automatically disables `useDiskCache` to restrict using the disk cache for encrypted documents.
// If you use a custom crypto solution, don’t forget to disable `useDiskCache` on your custom data provider or on the document,
// in order to avoid leaking out encrypted data as cached images.
// document.useDiskCache = false

// Show the controller.
let pdfController = PSPDFViewController(document: document)
navigationController.pushViewController(pdfController, animated: true)
// Note: For shipping apps, you need to protect this string better,
// in order to make it more difficult for a hacker to simply disassemble and receive
// the key from the binary. Or, 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 way harder to get the PDF. You can even
// combine AES and a PDF password.
NSString *passphrase = @"afghadöghdgdhfgöhapvuenröaoeruhföaeiruaerub";
NSString *salt = @"ducrXn9WaRdpaBfMjDTJVjUf3FApA6gtim0e61LeSGWV9sTxB0r26mPs59Lbcexn";

// Generate the crypto data provider.
NSURL *encryptedPDF = [[[NSBundle mainBundle] resourceURL] URLByAppendingPathComponent:@"encrypted.pdf.aes"];
PSPDFAESCryptoDataProvider *cryptoDataProvider = [[PSPDFAESCryptoDataProvider alloc] initWithURL:encryptedPDF passphraseProvider:^{ return passphrase; } salt:salt rounds:PSPDFDefaultPBKDFNumberOfRounds];

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

// `PSPDFAESCryptoDataProvider` automatically disables `useDiskCache` to restrict using the disk cache for encrypted documents.
// If you use a custom crypto solution, don’t forget to disable `useDiskCache` on your custom data provider or on the document,
// in order to avoid leaking out encrypted data as cached images.
// document.useDiskCache = NO;

// Show the controller.
PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:document];
[self.navigationController pushViewController:pdfController animated:YES];

Please take a look at our PSPDFAESCryptoDataProvider and XFDF Annotation Provider, Encrypted examples from the Catalog app for more details.

PSPDFLibrary and SQLite Database Encryption

SQLite databases created by PSPDFKit are not encrypted by default. However, they are still protected by iOS data protection just like all other application data is.

In case you need to add an additional level of security, there are a few third-party SQLite extensions that enable database encryption that you can integrate into your codebase. PSPDFDatabaseEncryptionProvider acts as a bridge between this third-party code and PSPDFLibrary.

In our [documentation][encryption in pspdflibrary guide], we use SQLCipher as an example, but the implementation should be very similar, if not identical, for other providers. To integrate SQLCipher, follow the instructions for either the commercial edition or the community edition. Once SQLCipher is correctly set up, you have to add an implementation of the PSPDFDatabaseEncryptionProvider protocol.

For more details and sample code, please follow the detailed instructions outlined in our Encryption in PDFLibrary and SQLite Database Encryption articles.

Disk Cache Encryption

PSPDFKit renders PDF pages as images and caches them in memory and on disk. Read more about Rendering and Caching in the related guide article.

There are a few strategies you can adopt to ensure that all sensitive data is secured. These include:

In addition to these approaches, you can use an encrypted disk cache:

let cache = PSPDFKit.sharedInstance.cache

// Clear existing cache.
cache.clear()

// Optional: Set a new cache directory.
cache.diskCache.cacheDirectory = "PSPDFKit_encrypted"

// In a real use case, you should protect the password better and not hardcode it like in this example.
let password: String = "unsafe-testpassword"

// Set up cache encryption handlers.
// Encrypting the images will cause a 5 to 10 percent slowdown, but nothing substantial.
var encryptedData: Data!
cache.diskCache.encryptionHelper = { request, data in
    do {
        encryptedData = try RNEncryptor.encryptData(data, with: kRNCryptorAES256Settings, password: password)
    } catch {
        print("Failed to encrypt: \(error.localizedDescription)")
    }
    return encryptedData
}

cache.diskCache.decryptionHelper = { request, encryptedData in
    var decryptedData: Data!
    do {
        decryptedData = try RNDecryptor.decryptData(encryptedData, withPassword: password)
    } catch {
        print("Failed to decrypt: \(error.localizedDescription)")
    }
    return decryptedData
}

// Open a sample document.
let document: PSPDFDocument = ...
let pdfController = PSPDFViewController(document: document)
PSPDFCache *cache = PSPDFKit.sharedInstance.cache;

// Clear existing cache.
[cache clearCache];

// Optional: Set a new cache directory.
cache.diskCache.cacheDirectory = @"PSPDFKit_encrypted";

// In a real use case, you should protect the password better and not hardcode it like in this example.
NSString *password = @"unsafe-testpassword";

// Set up cache encryption handlers.
// Encrypting the images will cause a 5 to 10 percent slowdown, but nothing substantial.
[cache.diskCache setEncryptionHelper:^NSData *_Nullable(PSPDFRenderRequest *request, NSData *data) {
    NSError *error;
    NSData *encryptedData = [RNEncryptor encryptData:data withSettings:kRNCryptorAES256Settings password:password error:&error];
    if (!encryptedData) {
        NSLog(@"Failed to encrypt: %@", error.localizedDescription);
    }
    return encryptedData;
}];
[cache.diskCache setDecryptionHelper:^NSData *_Nullable(PSPDFRenderRequest *request, NSData *encryptedData) {
    NSError *error;
    NSData *decryptedData = [RNDecryptor decryptData:encryptedData withPassword:password error:&error];
    if (!decryptedData) {
        NSLog(@"Failed to decrypt: %@", error.localizedDescription);
    }
    return decryptedData;
}];

// Open a sample document.
PSPDFDocument *document = ...
PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:document];

For a working example, please take a look at our Enable PSPDFCache encryption examples from the Catalog app.

Encrypt and Decrypt Files on the Server

We also offer a standalone command-line tool written in Go. It works on Windows, Mac, and Linux/Unix, and it can be found in the distribution .dmg under Extras/cryptor-cli. This tool allows you to encrypt or decrypt files on a server using a password and a salt. It can be very useful for encrypting and decrypting documents on your app’s server backend.

Please refer to our Encrypt or Decrypt Files on the Server article for the build and usage instructions.

Conclusion

In this article, we provided you with an overview of how to secure your document’s data by using encrypted and secured PDF documents and encrypting the library, database, and cache.

If your project has strict security requirements, please consult our guides about SDK Security and Security-Related Considerations. We also recommend that you consider disabling features like text and image extraction, drag and drop in external apps, and Document Sharing, to name a few.

Related Products
Share Post
Free 60-Day Trial Try PSPDFKit in your app today.
Free Trial

Related Articles

Explore more
DEVELOPMENT  |  iOS • Swift • Tips

Privacy Manifests and Required Reason APIs on iOS

PRODUCTS  |  iOS • Releases

PSPDFKit 13.4 for iOS Introduces Revamped API Documentation and Improves Multiple Annotation Selection

DEVELOPMENT  |  visionOS • iOS

Apple’s Vision of Our Digital Future