Encryption in PDFLibrary

PDFLibrary supports encryption. This guide outlines how to enable the encryption feature. If encryption is enabled, the main database will be encrypted.

Step 1: Register a DatabaseEncryptionProvider Implementation

The standard SQLite library does not support encryption out of the box. You therefore have to integrate a third-party option into your codebase. DatabaseEncryptionProvider acts as a bridge between this third-party code and PDFLibrary. We’ll 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 DatabaseEncryptionProvider protocol. For SQLCipher, the implementation will look like this:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class YourEncryptionProvider: NSObject, DatabaseEncryptionProvider {

    public func encryptDatabase(_ db: UnsafeMutableRawPointer, withKey keyData: Data) -> Bool {
        let data = keyData as NSData
        assert(data.length == 32, "Danger: key is 32 byte long, hence it will not be pbkdf2'ed by SQLCipher!")
        let error = sqlite3_key(OpaquePointer(db), data.bytes, Int32(data.length))
        return error == SQLITE_OK
    }

    public func reEncryptDatabase(_ db: UnsafeMutableRawPointer, withKey keyData: Data) -> Bool {
        let data = keyData as NSData
        assert(data.length == 32, "Danger: key is 32 byte long, hence it will not be pbkdf2'ed by SQLCipher!")
        let error = sqlite3_rekey(OpaquePointer(db), data.bytes, Int32(data.length))
        return error == SQLITE_OK
    }
}
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface YourEncryptionProvider : NSObject <PSPDFDatabaseEncryptionProvider>
@end

@implementation YourEncryptionProvider
- (BOOL)encryptDatabase:(void *)db withKey:(NSData *)keyData {
  NSAssert([keyData length] == 32, @"Danger: key is 32 byte long, hence it will not be pbkdf2'ed by SQLCipher!");
  int err = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
  return (err == SQLITE_OK);
}

- (BOOL)reEncryptDatabase:(void *)db withKey:(NSData *)keyData {
  NSAssert([keyData length] == 32, @"Danger: key is 32 byte long, hence it will not be pbkdf2'ed by SQLCipher!");
  int err = sqlite3_rekey(db, [keyData bytes], (int)[keyData length]);
  return (err == SQLITE_OK);
}
@end

Finally, you must register your encryption provider with PSPDFKit. To do so, simply set the databaseEncryptionProvider property on SDK. You have to do this early on. We recommend the following setup code:

Copy
1
2
3
4
5
6
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
    SDK.setLicenseKey("YOUR_LICENSE_KEY_GOES_HERE")
    SDK.sharedInstance.databaseEncryptionProvider = YourEncryptionProvider()

    return true
}
Copy
1
2
3
4
5
6
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [PSPDFKitGlobal setLicenseKey:@"YOUR_LICENSE_KEY_GOES_HERE"];
  PSPDFKitGlobal.sharedInstance.databaseEncryptionProvider = [YourEncryptionProvider new];

  return YES;
}

That’s it. PSPDFKit is now configured to support encryption.

Step 2: Use an Encrypted PDFLibrary

To use an encrypted PDFLibrary, simply call the PDFLibrary.encryptedLibrary(withPath:encryptionKeyProvider:error:) factory method. The first argument is a path to a directory of your choice. The second parameter is the so-called encryption key provider. An encryption key provider is a simple block that returns the key in the form of an NSData instance. The provider block will be called whenever the library needs access to the encryption key. This ensures we only keep the encryption key in memory for a very short amount of time. Your encryption key provider implementation should always fetch the key from secure storage, e.g. Apple’s keychain. Your key provider implementation should also be free from side effects in the sense that it should always return the same key on every call.

❗Important: Depending on the driver you chose, there might be different requirements for the key provided by the encryption key provider block!

PSPDFKit does not process the key in any way, rather it passes it directly to your DatabaseEncryptionProvider implementation. For example, if you use SQLCipher, your key will be treated as a passphrase. By default, SQLCipher will derive a key by using PBKDF2 with 64,000 iterations. However, if the key is exactly 32-bytes long, it will be used directly as the encryption key. Other drivers might have similar pitfalls, so be cautious and read the applicable documentation!

The returned instance of the factory is either nil or an encrypted PDFLibrary instance. nil is returned if the path points to an existing library that is not encrypted or is encrypted with a different key. Otherwise, a new instance of PDFLibrary, with the PDFLibrary.isEncrypted flag set to true, will be returned. Once this is done, you can use the library as usual.

Step 3: Using an Encrypted Library by Default (Optional)

You can override the default library. To do so, simply create a library (either encrypted or unencrypted) and call SDK.sharedInstance.library = yourCustomLibrary. Please note that you should do this early on, e.g. in application(_:didFinishLaunchingWithOptions:). If you change the library property later in your app’s lifecycle, some parts of SDK might still use the previous instance!

Step 4: Verifying the Encryption (Optional)

You should always ensure your library is properly encrypted. An easy way to do this is the hexdump command. Simply run hexdump -C /path/to/your/encrypted/library/index.sqlite. If the database is encrypted, the output should contain no human-readable text. You can also use the sqlite3 command to attempt to read from an encrypted library.