Note: Updated to clarify that this guidance applies to all projects built with Xcode 8, including for non-iOS targets).
Apple has recently released XCode 8, along with new SDKs (e.g. iOS 10, watchOS 3, mac OS, tvOS), with many new features and enhancements. The majority of projects that include SQLCipher should have no problems after upgrading. However, there are some important changes and edge cases, so we have prepared the following important recommendations for all SQLCipher developers upgrading to the new platforms:
- Adjust Project Build Settings to link SQLCipher first
- Include an Application Runtime Check for SQLCipher
- Testing and Validation
Background
One of the most important prerequisites for using SQLCipher is that an application should never link in other versions of SQLite. As a result, implementation requirements for SQLCipher on iOS have always required the removal of any references to the standard SQLite system library.
With previous versions of the standard SQLite library, there were a few symbols that were not implemented (e.g. sqlite3_key). This would typically result in a linker error if a project was improperly configured. However, with the latest XCode and associated SDKs that is unfortunately no longer the case.
As a result, if a project setup were to inadvertantly include a linking reference against the standard SQLite library before SQLCipher, it is possible for the application to build and run, but not use SQLCipher for encryption. For most projects this is not a problem, but there are certain circumstances where an unintentional SQLite link could occur. One such example is when using CocoaPods, or some other sub-project, that declares a dependency on the sqlite3 library. In that case, adding a pod to a project could “silently” modify the project settings in such a way that SQLCipher would not be properly linked. This might not be immediately apparent to a developer unless they are looking closely at the dependencies and testing.
Please note that we DO NOT SUPPORT using SQLCipher with a project that includes a separate sqlite3 dependency, including via CocoaPods. That sort of configuration carries multiple inherent risks, including undefined behavior, deadlocks, loss of data, loss of encryption functionality, etc.
That said, as a preventative measure, we recommend that applications using SQLCipher make the following adjustments.
Project Setup for Linking
One way to prevent undefined linking behavior or silent failures is to ensure that SQLCipher is linked into the application first. XCode provides a convenient inheritance structure for these types of settings.
Start by open the Project level Build Settings. These are the global project settings, not for your individual application target. Locate the Other Linker Flags setting and add one of the following, depending on how you are integrating SQLCipher
- When using SQLCipher Commercial Edition static libraries:
$(PROJECT_DIR)/sqlcipher-static-ios/ios-libs/libsqlcipher-ios.a
(adjust according to the the path to the libsqlcipher-ios.a you received as part of the package). - When using the sqlcipher.xcodeproj included in the SQLCipher git repository:
$(BUILT_PRODUCTS_DIR)/libsqlcipher.a
- When using the SQLCipher CocoaPod with the use_frameworks Podfile setting enabled:
-framework SQLCipher
- When using the SQLCipher CocoaPod without the use_frameworks Podfile setting enabled:
-lSQLCipher
To verify that the setting is enabled, switch to the Target Build Settings, and ensure that the resolved Other Linker Flags
shows the appropriate setting first. Clean the project before attempting to rebuild.
Runtime SQLCipher Check
In order to avoid situations where SQLite might be used improperly at runtime, we strongly recommend that applications institute a runtime test to ensure that the application is actually using SQLCipher on the active connection. This is very simple to implement, and will incur no performance penalty.
All recent versions of SQLCipher include a pragma called cipher_version. This pragma can be used to determine the version of the library in use. Notably, it will only return a value when run under SQLCipher. Therefore, it can be used as an effective test to check that SQLCipher is in use with the current database connection. Here is a brief example:
BOOL is_sqlcipher = NO;
sqlite3_stmt *stmt;
if(sqlite3_prepare_v2(database, "PRAGMA cipher_version;", -1, &stmt, NULL) == SQLITE_OK) {
if(sqlite3_step(stmt)== SQLITE_ROW) {
const unsigned char *ver = sqlite3_column_text(stmt, 0);
if(ver != NULL) {
is_sqlcipher = YES;
}
}
sqlite3_finalize(stmt);
}
We strongly recommend that applications use code like this as a failsafe to ensure that SQLCipher is being used at runtime before operating on a database.
Testing and Verification
Testing is critically important after any major build environment or SDK upgrade. There are a number of ways that you can verify SQLCipher is working as expected in your applications before its release to users, including:
- Attempt to open a database with a correct key and verify that the operation succeeds
- Attempt to open a database with an incorrect key and verify that the operation fails
- Attempt to open a database without any key, and verify the operation fails
- Programtically inspect the first 16 bytes of the database file and ensure that it contains random data (i.e. no the string ‘SQLite Format 3\0’
- Extract a database created by your application from a device or emulator and inspect it using hexdump (or similar) to ensure the data is encrypted
It is a good practice to test all application scenarios, including new application installs, upgrades, etc.
To summarize, when upgrading to XCode 8 and the new SDKs, it is important to take these preventative measures, and perform adequate application testing, to minimize the risk of impacts resulting from the platform updates. Please make it a priority to implement these recommendations in your projects. Of course, if you encounter any issues, please let us know.