29
29
import java .io .FileOutputStream ;
30
30
import java .io .IOException ;
31
31
import java .io .OutputStream ;
32
+ import java .io .UnsupportedEncodingException ;
33
+ import java .nio .ByteBuffer ;
34
+ import java .nio .CharBuffer ;
35
+ import java .nio .charset .Charset ;
32
36
import java .text .SimpleDateFormat ;
33
37
import java .util .ArrayList ;
34
38
import java .util .HashMap ;
54
58
import android .util .Log ;
55
59
import android .util .Pair ;
56
60
61
+ import java .io .UnsupportedEncodingException ;
62
+
57
63
/**
58
64
* Exposes methods to manage a SQLCipher database.
59
65
* <p>SQLiteDatabase has methods to create, delete, execute SQL commands, and
@@ -68,6 +74,7 @@ public class SQLiteDatabase extends SQLiteClosable {
68
74
private static final String TAG = "Database" ;
69
75
private static final int EVENT_DB_OPERATION = 52000 ;
70
76
private static final int EVENT_DB_CORRUPT = 75004 ;
77
+ private static final String KEY_ENCODING = "UTF-8" ;
71
78
72
79
/**
73
80
* The version number of the SQLCipher for Android Java client library.
@@ -101,7 +108,11 @@ public void changePassword(String password) throws SQLiteException {
101
108
throw new SQLiteException ("database not open" );
102
109
}
103
110
if (password != null ) {
104
- native_rekey (password );
111
+ byte [] keyMaterial = getBytes (password .toCharArray ());
112
+ rekey (keyMaterial );
113
+ for (byte data : keyMaterial ) {
114
+ data = 0 ;
115
+ }
105
116
}
106
117
}
107
118
10000
@@ -122,8 +133,12 @@ public void changePassword(char[] password) throws SQLiteException {
122
133
throw new SQLiteException ("database not open" );
123
134
}
124
135
if (password != null ) {
125
- native_rekey (String .valueOf (password ));
126
- }
136
+ byte [] keyMaterial = getBytes (password );
137
+ rekey (keyMaterial );
138
+ for (byte data : keyMaterial ) {
139
+ data = 0 ;
140
+ }
141
+ }
127
142
}
128
143
129
144
private static void loadICUData (Context context , File workingDir ) {
@@ -2332,41 +2347,94 @@ private SQLiteDatabase(String path, CursorFactory factory, int flags, DatabaseEr
2332
2347
mErrorHandler = errorHandler ;
2333
2348
}
2334
2349
2335
- private void openDatabaseInternal (char [] password , SQLiteDatabaseHook databaseHook ) {
2336
- dbopen (mPath , mFlags );
2337
-
2338
- if (databaseHook != null ) {
2339
- databaseHook .preKey (this );
2350
+ private void openDatabaseInternal (final char [] password , SQLiteDatabaseHook hook ) {
2351
+ boolean shouldCloseConnection = true ;
2352
+ final byte [] keyMaterial = getBytes (password );
2353
+ dbopen (mPath , mFlags );
2354
+ try {
2355
+
2356
+ keyDatabase (hook , new Runnable () {
2357
+ public void run () {
2358
+ if (keyMaterial != null && keyMaterial .length > 0 ) {
2359
+ key (keyMaterial );
2360
+ }
2361
+ }
2362
+ });
2363
+ shouldCloseConnection = false ;
2364
+
2365
+ } catch (RuntimeException ex ) {
2366
+
2367
+ if (containsNull (password )) {
2368
+ keyDatabase (hook , new Runnable () {
2369
+ public void run () {
2370
+ if (password != null ) {
2371
+ key_mutf8 (password );
2372
+ }
2373
+ }
2374
+ });
2375
+ if (keyMaterial != null && keyMaterial .length > 0 ) {
2376
+ rekey (keyMaterial );
2340
2377
}
2378
+ shouldCloseConnection = false ;
2379
+ } else {
2380
+ throw ex ;
2381
+ }
2341
2382
2342
- if (password != null ){
2343
- native_key (password );
2383
+ } finally {
2384
+ if (shouldCloseConnection ) {
2385
+ dbclose ();
2386
+ if (SQLiteDebug .DEBUG_SQL_CACHE ) {
2387
+ mTimeClosed = getTime ();
2344
2388
}
2345
-
2346
- if (databaseHook != null ){
2347
- databaseHook .postKey (this );
2389
+ }
2390
+ if (keyMaterial != null && keyMaterial .length > 0 ) {
2391
+ for (byte data : keyMaterial ) {
2392
+ data = 0 ;
2348
2393
}
2394
+ }
2395
+ }
2396
+
2397
+ }
2349
2398
2350
- if (SQLiteDebug .DEBUG_SQL_CACHE ) {
2351
- mTimeOpened = getTime ();
2352
- }
2353
- try {
2354
- Cursor cursor = rawQuery ("select count(*) from sqlite_master;" , new String []{});
2355
- if (cursor != null ){
2356
- cursor .moveToFirst ();
2357
- int count = cursor .getInt (0 );
2358
- cursor .close ();
2359
- }
2360
- //setLocale(Locale.getDefault());
2361
- } catch (RuntimeException e ) {
2362
- Log .e (TAG , "Failed to setLocale() when constructing, closing the database" , e );
2363
- dbclose ();
2364
- if (SQLiteDebug .DEBUG_SQL_CACHE ) {
2365
- mTimeClosed = getTime ();
2366
- }
2367
- throw e ;
2399
+ private boolean containsNull (char [] data ) {
2400
+ char defaultValue = '\u0000' ;
2401
+ boolean status = false ;
2402
+ if (data != null && data .length > 0 ) {
2403
+ for (char datum : data ) {
2404
+ if (datum == defaultValue ) {
2405
+ status = true ;
2406
+ break ;
2368
2407
}
2408
+ }
2409
+ }
2410
+ return status ;
2411
+ }
2412
+
2413
+ private void keyDatabase (SQLiteDatabaseHook databaseHook , Runnable keyOperation ) {
2414
+ if (databaseHook != null ) {
2415
+ databaseHook .preKey (this );
2416
+ }
2417
+ if (keyOperation != null ){
2418
+ keyOperation .run ();
2419
+ }
2420
+ if (databaseHook != null ){
2421
+ databaseHook .postKey (this );
2369
2422
}
2423
+ if (SQLiteDebug .DEBUG_SQL_CACHE ) {
2424
+ mTimeOpened = getTime ();
2425
+ }
2426
+ try {
2427
+ Cursor cursor = rawQuery ("select count(*) from sqlite_master;" , new String []{});
2428
+ if (cursor != null ){
2429
+ cursor .moveToFirst ();
2430
+ int count = cursor .getInt (0 );
2431
+ cursor .close ();
2432
+ }
2433
+ } catch (RuntimeException e ) {
2434
+ Log .e (TAG , e .getMessage (), e );
2435
+ throw e ;
2436
+ }
2437
+ }
2370
2438
2371
2439
private String getTime () {
2372
2440
return new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss.SSS " ).format (System .currentTimeMillis ());
@@ -2761,6 +2829,15 @@ private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbO
2761
2829
return attachedDbs ;
2762
2830
}
2763
2831
2832
+ private byte [] getBytes (char [] data ) {
2833
+ if (data == null || data .length == 0 ) return null ;
2834
+ CharBuffer charBuffer = CharBuffer .wrap (data );
2835
+ ByteBuffer byteBuffer = Charset .forName (KEY_ENCODING ).encode (charBuffer );
2836
+ byte [] result = new byte [byteBuffer .limit ()];
2837
+ byteBuffer .get (result );
2838
+ return result ;
2839
+ }
2840
+
2764
2841
/**
2765
2842
* Sets the root directory to search for the ICU data file
2766
2843
*/
@@ -2836,4 +2913,8 @@ private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbO
2836
2913
private native void native_key (char [] key ) throws SQLException ;
2837
2914
2838
2915
private native void native_rekey (String key ) throws SQLException ;
2916
+
2917
+ private native void key (byte [] key ) throws SQLException ;
2918
+ private native void key_mutf8 (char [] key ) throws SQLException ;
2919
+ private native void rekey (byte [] key ) throws SQLException ;
2839
2920
}
0 commit comments