diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7167add --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2008-2023, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index b9f5580..0163492 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,9 @@ SQLCipher for Android provides a library replacement for `android.database.sqlite` on the Android platform for use on [SQLCipher](https://github.com/sqlcipher/sqlcipher) databases. This library is based on the upstream [Android Bindings](https://www.sqlite.org/android/doc/trunk/www/index.wiki) project and aims to be a long-term replacement for the original [SQLCipher for Android](https://github.com/sqlcipher/android-database-sqlcipher) library. -***N.B.*** This library is currently distributed in source-only format at this time, and requires [other external dependencies](#external-dependencies) to build. Community edition AAR artifacts will be distributed eventually. - ### Compatibility -SQLCipher for Android supports Android API 16 and up on `armeabi-v7a`, `x86`, `x86_64`, and `arm64_v8a` architectures. +SQLCipher for Android supports Android API 21 and up on `armeabi-v7a`, `x86`, `x86_64`, and `arm64_v8a` architectures. ### Contributions @@ -17,19 +15,19 @@ We welcome contributions, to contribute to SQLCipher for Android, a [contributor Add a local reference to the local library and dependency: -``` -implementation files('libs/sqlcipher-android-4.5.2-release.aar') +```groovy +implementation files('libs/sqlcipher-android-4.5.6-release.aar') implementation 'androidx.sqlite:sqlite:2.2.0' ``` or source a Community edition build from Maven Central: -``` -implementation 'net.zetetic:sqlcipher-android:4.5.2@aar' +```groovy +implementation 'net.zetetic:sqlcipher-android:4.5.6@aar' implementation 'androidx.sqlite:sqlite:2.2.0' ``` -``` +```java import net.zetetic.database.sqlcipher.SQLiteDatabase; System.loadLibrary("sqlcipher"); @@ -40,20 +38,51 @@ SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(databaseFile, pass To perform operations on the database instance immediately before or after the keying operation is performed, provide a `SQLiteDatabaseHook` instance when creating your database connection: -``` +```java SQLiteDatabaseHook hook = new SQLiteDatabaseHook() { public void preKey(SQLiteConnection connection) { } public void postKey(SQLiteConnection connection) { } }; ``` +### API Usage + +There are two main options for using SQLCipher for Android within an Application: + +1. [Using the SQLCipher for Android classes](#sqlcipher-for-android-classes) +2. [Using SQLCipher for Android in conjunction with the Android Room API](#sqlcipher-for-android-room-integration) + +In both cases, prior to using any portion of the SQLCipher for Android library, the native SQLCipher core library must be loaded into the running application process. The SQLCipher core library is bundled within the AAR of SQLCipher for Android, however, the developer must load this library explicitly. An example below: + +```java +System.loadLibrary("sqlcipher"); +``` + +#### SQLCipher for Android classes + +SQLCipher for Android provides two classes for opening and access database files. The `SQLiteDatabase` provides static methods for opening/creating database files and general data access. +Additionally, applications may choose to subclass the `SQLiteOpenHelper` class which provides mechanisms for performing database migrations, as well as general data access. + +#### SQLCipher for Android Room Integration + +SQLCipher for Android may also integrate with the Room API via the `SupportOpenHelperFactory`, an example is given below: + +```java +System.loadLibrary("sqlcipher"); +String password = "Password1!"; +File databaseFile = context.getDatabasePath("demo.db"); +SupportOpenHelperFactory factory = new SupportOpenHelperFactory("password.getBytes(StandardCharsets.UTF_8)); +db = Room.databaseBuilder(context, AppDatabase.class, databaseFile.getAbsolutePath()) + .openHelperFactory(factory).build(); +``` + ### Building ## Android NDK Currently, SQLCipher for Android uses NDK version "25.2.9519653". -## External dependencies: +## External dependencies This repository is not batteries-included. Specifically, you will need to build `libcrypto.a`, the static library from OpenSSL using the NDK for the [supported platforms](#compatibility), and bundle the top-level `include` folder from OpenSSL. Additionally, you will need to build a SQLCipher amalgamation. These files will need to be placed in the following locations: diff --git a/README.md.template b/README.md.template index 8b43a7c..1139295 100644 --- a/README.md.template +++ b/README.md.template @@ -2,11 +2,9 @@ SQLCipher for Android provides a library replacement for `android.database.sqlite` on the Android platform for use on [SQLCipher](https://github.com/sqlcipher/sqlcipher) databases. This library is based on the upstream [Android Bindings](https://www.sqlite.org/android/doc/trunk/www/index.wiki) project and aims to be a long-term replacement for the original [SQLCipher for Android](https://github.com/sqlcipher/android-database-sqlcipher) library. -***N.B.*** This library is currently distributed in source-only format at this time, and requires [other external dependencies](#external-dependencies) to build. Community edition AAR artifacts will be distributed eventually. - ### Compatibility -SQLCipher for Android supports Android API 16 and up on `armeabi-v7a`, `x86`, `x86_64`, and `arm64_v8a` architectures. +SQLCipher for Android supports Android API <%=minSdkVersion%> and up on `armeabi-v7a`, `x86`, `x86_64`, and `arm64_v8a` architectures. ### Contributions @@ -17,19 +15,19 @@ We welcome contributions, to contribute to SQLCipher for Android, a [contributor Add a local reference to the local library and dependency: -``` -implementation files('libs/sqlcipher-android-4.5.2-release.aar') +```groovy +implementation files('libs/sqlcipher-android-<%=libraryVersion%>-release.aar') implementation 'androidx.sqlite:sqlite:<%=androidXSQLiteVersion%>' ``` or source a Community edition build from Maven Central: -``` -implementation 'net.zetetic:sqlcipher-android:4.5.2@aar' +```groovy +implementation 'net.zetetic:sqlcipher-android:<%=libraryVersion%>@aar' implementation 'androidx.sqlite:sqlite:<%=androidXSQLiteVersion%>' ``` -``` +```java import net.zetetic.database.sqlcipher.SQLiteDatabase; System.loadLibrary("sqlcipher"); @@ -40,20 +38,51 @@ SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(databaseFile, pass To perform operations on the database instance immediately before or after the keying operation is performed, provide a `SQLiteDatabaseHook` instance when creating your database connection: -``` +```java SQLiteDatabaseHook hook = new SQLiteDatabaseHook() { public void preKey(SQLiteConnection connection) { } public void postKey(SQLiteConnection connection) { } }; ``` +### API Usage + +There are two main options for using SQLCipher for Android within an Application: + +1. [Using the SQLCipher for Android classes](#sqlcipher-for-android-classes) +2. [Using SQLCipher for Android in conjunction with the Android Room API](#sqlcipher-for-android-room-integration) + +In both cases, prior to using any portion of the SQLCipher for Android library, the native SQLCipher core library must be loaded into the running application process. The SQLCipher core library is bundled within the AAR of SQLCipher for Android, however, the developer must load this library explicitly. An example below: + +```java +System.loadLibrary("sqlcipher"); +``` + +#### SQLCipher for Android classes + +SQLCipher for Android provides two classes for opening and access database files. The `SQLiteDatabase` provides static methods for opening/creating database files and general data access. +Additionally, applications may choose to subclass the `SQLiteOpenHelper` class which provides mechanisms for performing database migrations, as well as general data access. + +#### SQLCipher for Android Room Integration + +SQLCipher for Android may also integrate with the Room API via the `SupportOpenHelperFactory`, an example is given below: + +```java +System.loadLibrary("sqlcipher"); +String password = "Password1!"; +File databaseFile = context.getDatabasePath("demo.db"); +SupportOpenHelperFactory factory = new SupportOpenHelperFactory("password.getBytes(StandardCharsets.UTF_8)); +db = Room.databaseBuilder(context, AppDatabase.class, databaseFile.getAbsolutePath()) + .openHelperFactory(factory).build(); +``` + ### Building ## Android NDK Currently, SQLCipher for Android uses NDK version "<%=androidNdkVersion%>". -## External dependencies: +## External dependencies This repository is not batteries-included. Specifically, you will need to build `libcrypto.a`, the static library from OpenSSL using the NDK for the [supported platforms](#compatibility), and bundle the top-level `include` folder from OpenSSL. Additionally, you will need to build a SQLCipher amalgamation. These files will need to be placed in the following locations: diff --git a/build.gradle b/build.gradle index 16255ee..a8815c9 100644 --- a/build.gradle +++ b/build.gradle @@ -28,8 +28,9 @@ project.ext { if(project.hasProperty('sqlcipherAndroidVersion') && "${sqlcipherAndroidVersion}") { libraryVersion = "${sqlcipherAndroidVersion}" } else { - libraryVersion = "4.5.3" + libraryVersion = "4.5.6" } + minSdkVersion = 21 androidXSQLiteVersion = "2.2.0" roomVersion = "2.5.0" androidNdkVersion = "25.2.9519653" @@ -65,7 +66,8 @@ task generateReadMe { def binding = [ libraryVersion: project.ext.libraryVersion, androidXSQLiteVersion: project.ext.androidXSQLiteVersion, - androidNdkVersion: project.ext.androidNdkVersion + androidNdkVersion: project.ext.androidNdkVersion, + minSdkVersion: project.ext.minSdkVersion, ] def template = engine.createTemplate(reader).make(binding) readme.write(template.toString()) diff --git a/sqlcipher/build.gradle b/sqlcipher/build.gradle index 714392e..381b0dd 100644 --- a/sqlcipher/build.gradle +++ b/sqlcipher/build.gradle @@ -6,12 +6,13 @@ android { compileSdkVersion 33 defaultConfig { - minSdkVersion 21 + minSdkVersion "${rootProject.ext.minSdkVersion}" targetSdkVersion 33 versionCode 1 versionName "${rootProject.ext.libraryVersion}" project.archivesBaseName = "sqlcipher-android-${versionName}" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + consumerProguardFile 'consumer-rules.pro' } buildTypes { diff --git a/sqlcipher/consumer-rules.pro b/sqlcipher/consumer-rules.pro new file mode 100644 index 0000000..0f8a20c --- /dev/null +++ b/sqlcipher/consumer-rules.pro @@ -0,0 +1,18 @@ +-keep class net.zetetic.** { + native ; + private native ; + public (...); + long mNativeHandle; +} + +-keepclassmembers class net.zetetic.database.sqlcipher.SQLiteCustomFunction { + public java.lang.String name; + public int numArgs; + private void dispatchCallback(java.lang.String[]); +} + +-keepclassmembers class net.zetetic.database.sqlcipher.SQLiteDebug$PagerStats { + public int largestMemAlloc; + public int memoryUsed; + public int pageCacheOverflow; +} diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java index 781a6ee..9acb9f0 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java @@ -24,28 +24,34 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SupportOpenHelperFactory; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.UUID; @RunWith(AndroidJUnit4.class) public class SupportAPIRoomTest { private AppDatabase db; private UserDao userDao; + private File databaseFile; @Before public void before(){ Context context = ApplicationProvider.getApplicationContext(); System.loadLibrary("sqlcipher"); + databaseFile = context.getDatabasePath("users.db"); SupportOpenHelperFactory factory = new SupportOpenHelperFactory("user".getBytes(StandardCharsets.UTF_8)); - db = Room.databaseBuilder(context, AppDatabase.class, "users.db") + db = Room.databaseBuilder(context, AppDatabase.class, databaseFile.getAbsolutePath()) .openHelperFactory(factory).build(); db.clearAllTables(); userDao = db.userDao(); @@ -92,10 +98,22 @@ public void shouldQueryDataByParametersViaDao(){ assertThat(foundUser.lastName, is(user.lastName)); } + @Test + public void shouldSupportChangingPasswordWithRoom(){ + userDao.insert(new User("foo", "bar")); + SQLiteDatabase database = (SQLiteDatabase)db.getOpenHelper().getWritableDatabase(); + database.changePassword(UUID.randomUUID().toString()); + List users = userDao.getAll(); + assertThat(users.size(), is(1)); + } + @After public void after(){ if(db != null){ db.close(); + if(databaseFile != null){ + databaseFile.delete(); + } } } diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnectionPool.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnectionPool.java index bb9d695..0750189 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnectionPool.java +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnectionPool.java @@ -299,6 +299,7 @@ public void reconfigure(SQLiteDatabaseConfiguration configuration) { if(passwordChanged){ mAvailablePrimaryConnection.changePassword(configuration.password); mConfiguration.updateParametersFrom(configuration); + closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked(); reconfigureAllConnectionsLocked(); }