2929import java .io .FileOutputStream ;
3030import java .io .IOException ;
3131import java .io .OutputStream ;
32+ import java .io .UnsupportedEncodingException ;
33+ import java .nio .ByteBuffer ;
34+ import java .nio .CharBuffer ;
35+ import java .nio .charset .Charset ;
3236import java .text .SimpleDateFormat ;
3337import java .util .ArrayList ;
3438import java .util .HashMap ;
5458import android .util .Log ;
5559import android .util .Pair ;
5660
61+ import java .io .UnsupportedEncodingException ;
62+
5763/**
5864 * Exposes methods to manage a SQLCipher database.
5965 * <p>SQLiteDatabase has methods to create, delete, execute SQL commands, and
@@ -68,6 +74,7 @@ public class SQLiteDatabase extends SQLiteClosable {
6874 private static final String TAG = "Database" ;
6975 private static final int EVENT_DB_OPERATION = 52000 ;
7076 private static final int EVENT_DB_CORRUPT = 75004 ;
77+ private static final String KEY_ENCODING = "UTF-8" ;
7178
7279 /**
7380 * The version number of the SQLCipher for Android Java client library.
@@ -101,7 +108,11 @@ public void changePassword(String password) throws SQLiteException {
101108 throw new SQLiteException ("database not open" );
102109 }
103110 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+ }
105116 }
106117 }
107118
@@ -122,8 +133,12 @@ public void changePassword(char[] password) throws SQLiteException {
122133 throw new SQLiteException ("database not open" );
123134 }
124135 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+ }
127142 }
128143
129144 private static void loadICUData (Context context , File workingDir ) {
@@ -2332,41 +2347,94 @@ private SQLiteDatabase(String path, CursorFactory factory, int flags, DatabaseEr
23322347 mErrorHandler = errorHandler ;
23332348 }
23342349
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 );
23402377 }
2378+ shouldCloseConnection = false ;
2379+ } else {
2380+ throw ex ;
2381+ }
23412382
2342- if (password != null ){
2343- native_key (password );
2383+ } finally {
2384+ if (shouldCloseConnection ) {
2385+ dbclose ();
2386+ if (SQLiteDebug .DEBUG_SQL_CACHE ) {
2387+ mTimeClosed = getTime ();
23442388 }
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 ;
23482393 }
2394+ }
2395+ }
2396+
2397+ }
23492398
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 ;
23682407 }
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 );
23692422 }
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+ }
23702438
23712439 private String getTime () {
23722440 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
27612829 return attachedDbs ;
27622830 }
27632831
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+
27642841 /**
27652842 * Sets the root directory to search for the ICU data file
27662843 */
@@ -2836,4 +2913,8 @@ private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbO
28362913 private native void native_key (char [] key ) throws SQLException ;
28372914
28382915 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 ;
28392920}
0 commit comments