Skip to content

Commit 9effb6d

Browse files
Add change password support on SQLiteDatabase
1 parent ea4639f commit 9effb6d

File tree

6 files changed

+123
-6
lines changed

6 files changed

+123
-6
lines changed

sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22

33
import android.database.Cursor;
44

5-
import net.zetetic.database.sqlcipher.SQLiteConnection;
65
import net.zetetic.database.sqlcipher.SQLiteDatabase;
7-
import net.zetetic.database.sqlcipher.SQLiteDatabaseHook;
6+
import net.zetetic.database.sqlcipher.SQLiteDatabaseConfiguration;
7+
import net.zetetic.database.sqlcipher.SQLiteException;
88

9-
import org.junit.Before;
109
import org.junit.Test;
1110

12-
import java.io.File;
13-
1411
import static org.hamcrest.MatcherAssert.assertThat;
1512
import static org.hamcrest.core.Is.is;
13+
import static org.hamcrest.core.IsNull.nullValue;
1614

1715
public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase {
1816

@@ -26,6 +24,7 @@ public void testConnectionWithPassword(){
2624
if(cursor != null && cursor.moveToFirst()){
2725
a = cursor.getInt(0);
2826
b = cursor.getInt(1);
27+
cursor.close();
2928
}
3029
assertThat(a, is(1));
3130
assertThat(b, is(2));
@@ -42,8 +41,59 @@ public void insertDataQueryByObjectParams(){
4241
if(cursor != null && cursor.moveToFirst()){
4342
a1 = cursor.getFloat(0);
4443
b1 = cursor.getDouble(1);
44+
cursor.close();
4545
}
4646
assertThat(a1, is(a));
4747
assertThat(b1, is(b));
4848
}
49+
50+
@Test
51+
public void shouldChangeDatabasePasswordSuccessfully(){
52+
int a = 3, b = 4;
53+
int a1 = 0, b1 = 0;
54+
String originalPassword = "foo", newPassword = "bar";
55+
database = SQLiteDatabase.openOrCreateDatabase(databasePath, originalPassword, null, null);
56+
database.execSQL("create table t1(a,b);");
57+
database.execSQL("insert into t1(a,b) values(?,?)", new Object[]{a, b});
58+
database.changePassword(newPassword);
59+
database.close();
60+
database = null;
61+
try {
62+
database = SQLiteDatabase.openOrCreateDatabase(databasePath, originalPassword, null, null);
63+
assertThat(database, is(nullValue()));
64+
} catch (SQLiteException ex){
65+
database = SQLiteDatabase.openOrCreateDatabase(databasePath, newPassword, null, null);
66+
Cursor cursor = database.rawQuery("select * from t1;");
67+
if(cursor != null && cursor.moveToFirst()){
68+
a1 = cursor.getInt(0);
69+
b1 = cursor.getInt(1);
70+
cursor.close();
71+
}
72+
assertThat(a1, is(a));
73+
assertThat(b1, is(b));
74+
}
75+
}
76+
77+
@Test(expected = IllegalStateException.class)
78+
public void shouldThrowExceptionWhenChangingPasswordOnClosedDatabase(){
79+
database = SQLiteDatabase.openOrCreateDatabase(databasePath, "foo", null, null);
80+
database.close();
81+
database.changePassword("bar");
82+
}
83+
84+
@Test(expected = IllegalStateException.class)
85+
public void shouldThrowExceptionOnChangePasswordWithReadOnlyDatabase(){
86+
database = SQLiteDatabase.openOrCreateDatabase(databasePath.getAbsolutePath(), "foo", null, null);
87+
database.execSQL("create table t1(a,b);");
88+
database.close();
89+
database = null;
90+
database = SQLiteDatabase.openDatabase(databasePath.getAbsolutePath(), "foo", null, SQLiteDatabase.OPEN_READONLY, null, null);
91+
database.changePassword("bar");
92+
}
93+
94+
@Test(expected = IllegalStateException.class)
95+
public void shouldThrowExceptionOnChangePasswordWithInMemoryDatabase(){
96+
database = SQLiteDatabase.openOrCreateDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH, "foo", null, null, null);
97+
database.changePassword("bar");
98+
}
4999
}

sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import android.database.Cursor;
44

5+
import net.zetetic.database.sqlcipher.SQLiteDatabase;
6+
57
import org.junit.Test;
68

79
import static org.hamcrest.MatcherAssert.assertThat;
@@ -13,7 +15,8 @@ public class SQLCipherVersionTest extends AndroidSQLCipherTestCase {
1315
@Test
1416
public void testCipherVersionReported(){
1517
String cipherVersion = "";
16-
Cursor cursor = database.rawQuery("PRAGMA cipher_version;", new String[]{});
18+
database = SQLiteDatabase.openOrCreateDatabase(databasePath, "foo", null, null);
19+
Cursor cursor = database.rawQuery("PRAGMA cipher_version;");
1720
if(cursor != null && cursor.moveToFirst()){
1821
cipherVersion = cursor.getString(0);
1922
cursor.close();

sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnection.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
118118
private int mCancellationSignalAttachCount;
119119

120120
private static native int nativeKey(long connectionPtr, byte[] password);
121+
private static native int nativeReKey(long connectionPtr, byte[] newPassword);
121122
private static native long nativeOpen(String path, int openFlags, String label,
122123
boolean enableTrace, boolean enableProfile);
123124
private static native void nativeClose(long connectionPtr);
@@ -209,6 +210,14 @@ void close() {
209210
dispose(false);
210211
}
211212

213+
void changePassword(byte[] newPassword){
214+
int result = nativeReKey(mConnectionPtr, newPassword);
215+
Log.i(TAG, String.format("Database rekey operation returned:%s", result));
216+
if(result != 0){
217+
throw new SQLiteException(String.format("Failed to rekey database, result code:%s", result));
218+
}
219+
}
220+
212221
private void open() {
213222
mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
214223
mConfiguration.label,

sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnectionPool.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import java.io.Closeable;
3333
import java.util.ArrayList;
34+
import java.util.Arrays;
3435
import java.util.Map;
3536
import java.util.WeakHashMap;
3637
import java.util.concurrent.atomic.AtomicBoolean;
@@ -294,6 +295,13 @@ public void reconfigure(SQLiteDatabaseConfiguration configuration) {
294295
}
295296
}
296297

298+
boolean passwordChanged = !Arrays.equals(configuration.password, mConfiguration.password);
299+
if(passwordChanged){
300+
mAvailablePrimaryConnection.changePassword(configuration.password);
301+
mConfiguration.updateParametersFrom(configuration);
302+
reconfigureAllConnectionsLocked();
303+
}
304+
297305
if (mConfiguration.openFlags != configuration.openFlags) {
298306
// If we are changing open flags and WAL mode at the same time, then
299307
// we have no choice but to close the primary connection beforehand

sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2375,6 +2375,30 @@ public boolean isDatabaseIntegrityOk() {
23752375
return true;
23762376
}
23772377

2378+
public void changePassword(String newPassword) {
2379+
changePassword(getBytes(newPassword));
2380+
}
2381+
2382+
public void changePassword(byte[] newPassword) {
2383+
synchronized (mLock) {
2384+
throwIfNotOpenLocked();
2385+
if (isReadOnlyLocked()) {
2386+
throw new IllegalStateException("Can't change password for readonly databases.");
2387+
}
2388+
if (mConfigurationLocked.isInMemoryDb()) {
2389+
throw new IllegalStateException("Can't change password for in-memory databases.");
2390+
}
2391+
byte[] originalPassword = mConfigurationLocked.password;
2392+
mConfigurationLocked.password = newPassword;
2393+
try {
2394+
mConnectionPoolLocked.reconfigure(mConfigurationLocked);
2395+
} catch (RuntimeException ex) {
2396+
mConfigurationLocked.password = originalPassword;
2397+
throw ex;
2398+
}
2399+
}
2400+
}
2401+
23782402
@Override
23792403
public String toString() {
23802404
return "SQLiteDatabase: " + getPath();

sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteConnection.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,27 @@ static jint nativeKey(JNIEnv* env, jclass clazz, jlong connectionPtr, jbyteArray
159159
return rc;
160160
}
161161

162+
static jint nativeReKey(JNIEnv* env, jclass clazz, jlong connectionPtr, jbyteArray keyArray) {
163+
int rc = SQLITE_ERROR;
164+
jsize size = 0;
165+
jbyte *key = nullptr;
166+
auto* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
167+
if(connection) {
168+
ALOGV("ReKeying connection %p", connection->db);
169+
key = env->GetByteArrayElements(keyArray, nullptr);
170+
size = env->GetArrayLength(keyArray);
171+
rc = sqlite3_rekey(connection->db, key, size);
172+
}
173+
if(key) {
174+
env->ReleaseByteArrayElements(keyArray, key, JNI_ABORT);
175+
}
176+
if (rc != SQLITE_OK) {
177+
ALOGE("sqlite3_rekey(%p) failed: %d", connection->db, rc);
178+
throw_sqlite3_exception(env, connection->db, "Could not rekey db.");
179+
}
180+
return rc;
181+
}
182+
162183
static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags,
163184
jstring labelStr, jboolean enableTrace, jboolean enableProfile) {
164185
int sqliteFlags;
@@ -835,6 +856,8 @@ static JNINativeMethod sMethods[] =
835856
/* name, signature, funcPtr */
836857
{"nativeKey", "(J[B)I",
837858
(void*)nativeKey },
859+
{"nativeReKey", "(J[B)I",
860+
(void*)nativeReKey },
838861
{"nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZ)J",
839862
(void*)nativeOpen },
840863
{ "nativeClose", "(J)V",

0 commit comments

Comments
 (0)