一、注意事项
1、对数据库访问进行控制。@b@2、反注入。@b@3、对敏感数据进行保护。
二、操作数据库
1.正确的设置数据库文件的位置和访问权限。 @b@2.当与其他应用共享数据库的时候,使用content provider进行访问控制。 @b@3.在进行数据库操作的时候,使用占位符进行变量操作。
三、反注入
1、使用SQLiteOpenHelper创建数据库@b@2、使用占位符。@b@3、验证输入数据。
package org.jssec.android.sqlite;@b@ @b@import android.content.Context;@b@import android.database.SQLException;@b@import android.database.sqlite.SQLiteDatabase;@b@import android.database.sqlite.SQLiteOpenHelper;@b@import android.util.Log;@b@import android.widget.Toast;@b@ @b@public class SampleDbOpenHelper extends SQLiteOpenHelper {@b@ private SQLiteDatabase mSampleDb; //Database to store the data to be handled@b@ @b@ public static SampleDbOpenHelper newHelper(Context context)@b@ {@b@ //*** POINT 1 *** SQLiteOpenHelper should be used for database creation.@b@ return new SampleDbOpenHelper(context);@b@ }@b@ @b@ public SQLiteDatabase getDb() {@b@ return mSampleDb;@b@ }@b@ @b@ //Open DB by Writable mode@b@ public void openDatabaseWithHelper() {@b@ try {@b@ if (mSampleDb != null && mSampleDb.isOpen()) {@b@ if (!mSampleDb.isReadOnly())// Already opened by writable mode@b@ return;@b@ mSampleDb.close();@b@ }@b@ mSampleDb = getWritableDatabase(); //It's opened here.@b@ } catch (SQLException e) {@b@ //In case fail to construct database, output to log@b@ Log.e(mContext.getClass().toString(), mContext.getString(R.string.DATABASE_OPEN_ERROR_MESSAGE));@b@ Toast.makeText(mContext, R.string.DATABASE_OPEN_ERROR_MESSAGE, Toast.LENGTH_LONG).show();@b@ }@b@ }@b@ @b@ //Open DB by ReadOnly mode.@b@ public void openDatabaseReadOnly() {@b@ try {@b@ if (mSampleDb != null && mSampleDb.isOpen()) {@b@ if (mSampleDb.isReadOnly())// Already opened by ReadOnly.@b@ return;@b@ mSampleDb.close();@b@ }@b@ SQLiteDatabase.openDatabase(mContext.getDatabasePath(CommonData.DBFILE_NAME).getPath(), null, SQLiteDatab ase.OPEN_READONLY);@b@ } catch (SQLException e) {@b@ //In case failed to construct database, output to log@b@ Log.e(mContext.getClass().toString(), mContext.getString(R.string.DATABASE_OPEN_ERROR_MESSAGE)); Toast.makeText(mContext, R.string.DATABASE_OPEN_ERROR_MESSAGE, Toast.LENGTH_LONG).show();@b@ }@b@ }@b@ @b@ //Database Close@b@ public void closeDatabase() {@b@ try {@b@ if (mSampleDb != null && mSampleDb.isOpen()) {@b@ mSampleDb.close(); }@b@ } catch (SQLException e) {@b@ //In case failed to construct database, output to log@b@ Log.e(mContext.getClass().toString(), mContext.getString(R.string.DATABASE_CLOSE_ERROR_MESSAGE);@b@ Toast.makeText(mContext, R.string.DATABASE_CLOSE_ERROR_MESSAGE, Toast.LENGTH_LONG).show();@b@ }@b@ }@b@ @b@ //Remember Context@b@ private Context mContext;@b@ @b@ //Table creation command@b@ private static final String CREATE_TABLE_COMMANDS@b@ = "CREATE TABLE " + CommonData.TABLE_NAME + " ("@b@ + "_id INTEGER PRIMARY KEY AUTOINCREMENT, "@b@ + "idno INTEGER UNIQUE, "@b@ + "name VARCHAR(" + CommonData.TEXT_DATA_LENGTH_MAX + ") NOT NULL, "@b@ + "info VARCHAR(" + CommonData.TEXT_DATA_LENGTH_MAX + ")"@b@ + ");";@b@ @b@ public SampleDbOpenHelper(Context context) {@b@ super(context, CommonData.DBFILE_NAME, null, CommonData.DB_VERSION);@b@ mContext = context;@b@ }@b@ @b@ @Override@b@ public void onCreate(SQLiteDatabase db) {@b@ try {@b@ db.execSQL(CREATE_TABLE_COMMANDS); //Execute DB construction command@b@ } catch (SQLException e) {@b@ //In case failed to construct database, output to log@b@ Log.e(this.getClass().toString(), mContext.getString(R.string.DATABASE_CREATE_ERROR_MESSAGE));@b@ }@b@ }@b@ @b@ @Override@b@ public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {@b@ // It's to be executed when database version up. Write processes like data transition.@b@ }@b@}
package org.jssec.android.sqlite.task;@b@ @b@import org.jssec.android.sqlite.CommonData;@b@import org.jssec.android.sqlite.DataValidator;@b@import org.jssec.android.sqlite.MainActivity;@b@import org.jssec.android.sqlite.R;@b@import android.database.Cursor;@b@import android.database.SQLException;@b@import android.database.sqlite.SQLiteDatabase;@b@import android.os.AsyncTask;@b@import android.util.Log;@b@ @b@//Data search task@b@public class DataSearchTask extends AsyncTask<String, Void, Cursor> {@b@ private MainActivity mActivity;@b@ private SQLiteDatabase mSampleDB;@b@ @b@ public DataSearchTask(SQLiteDatabase db, MainActivity activity) {@b@ mSampleDB = db;@b@ mActivity = activity;@b@ }@b@ @b@ @Override@b@ protected Cursor doInBackground(String... params) {@b@ String idno = params[0];@b@ String name = params[1];@b@ String info = params[2];@b@ String cols[] = {"_id", "idno","name","info"};@b@ @b@ Cursor cur;@b@ @b@ //*** POINT 3 *** Validate the input value according the application requirements.@b@ if (!DataValidator.validateData(idno, name, info))@b@ {@b@ return null;@b@ }@b@ @b@ //When all parameters are null, execute all search@b@ if ((idno == null || idno.length() == 0) &&@b@ (name == null || name.length() == 0) &&@b@ (info == null || info.length() == 0) ) {@b@ try{@b@ cur = mSampleDB.query(CommonData.TABLE_NAME, cols, null, null, null, null, null);@b@ } catch (SQLException e) {@b@ Log.e(DataSearchTask.class.toString(), mActivity.getString(R.string.SEARCHING_ERROR_MESSAGE));@b@ return null;@b@ }@b@ return cur;@b@ }@b@ @b@ //When No is specified, execute searching by No@b@ if (idno != null && idno.length() > 0) {@b@ String selectionArgs[] = {idno};@b@ @b@ try{@b@ //*** POINT 2 *** Use place holder.@b@ cur = mSampleDB.query(CommonData.TABLE_NAME, cols, "idno = ?", selectionArgs, null, null, null);@b@ } catch (SQLException e) {@b@ Log.e(DataSearchTask.class.toString(), mActivity.getString(R.string.SEARCHING_ERROR_MESSAGE));@b@ return null;@b@ }@b@ return cur;@b@ }@b@ @b@ //When Name is specified, execute perfect match search by Name@b@ if (name != null && name.length() > 0) {@b@ String selectionArgs[] = {name};@b@ try{@b@ //*** POINT 2 *** Use place holder.@b@ cur = mSampleDB.query(CommonData.TABLE_NAME, cols, "name = ?", selectionArgs, null, null, null);@b@ } catch (SQLException e) {@b@ Log.e(DataSearchTask.class.toString(), mActivity.getString(R.string.SEARCHING_ERROR_MESSAGE));@b@ return null;@b@ }@b@ return cur;@b@ }@b@ @b@ //Other than above, execute partly match searching with the condition of info.@b@ String argString = info.replaceAll("@", "@@"); //Escape $ in info which was received as input.@b@ argString = argString.replaceAll("%", "@%"); //Escape % in info which was received as input.@b@ argString = argString.replaceAll("_", "@_"); //Escape _ in info which was received as input.@b@ String selectionArgs[] = {argString};@b@ @b@ try {@b@ //*** POINT 2 *** Use place holder.@b@ cur = mSampleDB.query(CommonData.TABLE_NAME, cols, "info LIKE '%' || ? || '%' ESCAPE '@'", seletionArgs,null, null, null);@b@ } catch (SQLException e) {@b@ Log.e(DataSearchTask.class.toString(), mActivity.getString(R.string.SEARCHING_ERROR_MESSAGE));@b@ return null;@b@ }@b@ return cur;@b@ }@b@ @b@ @Override@b@ protected void onPostExecute(Cursor resultCur) {@b@ mActivity.updateCursor(resultCur);@b@ }@b@}
public class DataValidator {@b@ //Validate the Input value@b@ //validate numeric characters@b@ public static boolean validateNo(String idno) {@b@ //null and blank are OK@b@ if (idno == null || idno.length() == 0) {@b@ return true;@b@ }@b@ @b@ //Validate that it's numeric character.@b@ try {@b@ if (!idno.matches("[1-9][0-9]*")) {@b@ //Error if it's not numeric value@b@ return false;@b@ }@b@ } catch (NullPointerException e) {@b@ //Detected an error@b@ return false;@b@ }@b@ @b@ return true;@b@ }@b@ @b@ // Validate the length of a character string@b@ public static boolean validateLength(String str, int max_length) {@b@ //null and blank are OK@b@ if (str == null || str.length() == 0) {@b@ return true;@b@ }@b@ @b@ //Validate the length of a character string is less than MAX@b@ try {@b@ if (str.length() > max_length) {@b@ //When it's longer than MAX, error@b@ return false;@b@ }@b@ } catch (NullPointerException e) {@b@ //Bug@b@ return false;@b@ }@b@ return true; }@b@ @b@ // Validate the Input value@b@ public static boolean validateData(String idno, String name, String info) {@b@ if (!validateNo(idno)) {@b@ return false;@b@ }@b@ if (!validateLength(name, CommonData.TEXT_DATA_LENGTH_MAX)) {@b@ return false;@b@ }else if(!validateLength(info, CommonData.TEXT_DATA_LENGTH_MAX)) {@b@ return false;@b@ }@b@ return true;@b@ }@b@}
四、其他
1、当在like语句中使用通配符,应使用escape 转义
Example of ESCAPE process in case of using LIKE@b@ @b@//Data search task@b@public class DataSearchTask extends AsyncTask<String, Void, Cursor> {@b@ private MainActivity mActivity;@b@ private SQLiteDatabase mSampleDB;@b@ private ProgressDialog mProgressDialog;@b@ @b@ public DataSearchTask(SQLiteDatabase db, MainActivity activity) {@b@ mSampleDB = db;@b@ mActivity = activity;@b@ }@b@ @b@ @Override@b@ protected Cursor doInBackground(String... params) {@b@ String idno = params[0];@b@ String name = params[1];@b@ String info = params[2];@b@ String cols[] = {"_id", "idno","name","info"};@b@ @b@ Cursor cur;@b@ @b@ ... Abbreviation ...@b@ @b@ //Execute like search(partly match) with the condition of info@b@ //Point:Escape process should be performed on characters which is applied to wild card@b@ String argString = info.replaceAll("@", "@@"); // Escape $ in info which was received as input@b@ argString = argString.replaceAll("%", "@%"); // Escape % in info which was received as input@b@ argString = argString.replaceAll("_", "@_"); // Escape _ in info which was received as input@b@ String selectionArgs[] = {argString};@b@ @b@ try {@b@ //Point:Use place holder@b@ cur = mSampleDB.query("SampleTable", cols, "info LIKE '%' || ? || '%' ESCAPE '@'",selectionArgs, null, null, null);@b@ } catch (SQLException e) {@b@ Toast.makeText(mActivity,R.string.SERCHING_ERROR_MESSAGE,Toast.LENGTH_LONG).show();@b@ return null;@b@ }@b@ return cur;@b@ }@b@ @b@ @Override@b@ protected void onPostExecute(Cursor resultCur) {@b@ mProgressDialog.dismiss();@b@ mActivity.updateCursor(resultCur);@b@ }@b@}
2、当有外部输入时,不可以使用占位符。
外部输入应进行校验。@b@当使用字符串参数时,进行转义。@b@当使用数字参数时,非数字参数不在内。@b@当使用标识或命令,验证是否为已知标识和命令。
Open database by read-only@b@ @b@ ... Ommit ...@b@ @b@ // Open DB(DB should be created in advance)@b@ SQLiteDatabase db= SQLiteDatabase.openDatabase(SQLiteDatabase.getDatabasePath("Sample.db"), null, OPEN_READONLY);
3. 采取措施保证数据库不被意外重写 - 根据应用的要求,验证数据的有效性。
当向数据库中存储数据的时候,验证数据类型和长度。@b@ 读取数据的时候,验证数据的类型和长度是否正确。
Verify that the Input value is more than 1 (Extract from MainActivity.java)@b@ @b@public class MainActivity extends Activity {@b@ @b@ ... Abbreviation ...@b@ @b@ //Process for adding@b@ private void addUserData(String idno, String name, String info) {@b@ //Check for No@b@ if (!validateNo(idno, CommonData.REQUEST_NEW)) {@b@ return;@b@ }@b@ @b@ //Inserting data process@b@ DataInsertTask task = new DataInsertTask(mSampleDbyhis);@b@ task.execute(idno, name, info);@b@ }@b@... Abbreviation ...@b@ @b@ private boolean validateNo(String idno, int request) {@b@ if (idno == null || idno.length() == 0) {@b@ if (request == CommonData.REQUEST_SEARCH) {@b@ //When search process, unspecified is considered as OK.@b@ return true;@b@ } else {@b@ //Other than search process, null and blank are error.@b@ Toast.makeText(this, R.string.IDNO_EMPTY_MESSAGE,Toast.LENGTH_LONG).show(); return false;@b@ }@b@ }@b@ @b@ //Verify that it's numeric character@b@ try {@b@ // Value which is more than 1@b@ if (!idno.matches("[1-9][0-9]*")) {@b@ //In case of not numeric character, error@b@ Toast.makeText(this, R.string.IDNO_NOT_NUMERIC_MESSAGE, Toast.LENGTH_LONG).show();@b@ return false;@b@ }@b@ } catch (NullPointerException e) {@b@ @b@ //It never happen in this case@b@ return false;@b@ }@b@ @b@ return true;@b@ }@b@ ... Abbreviation...@b@}
4、使用SQLCipher进行数据加密
使用sqllite,存储数据的db文件 是以纯文本的形式存储的,当删除数据的时候,数据并没有真的从数据库删除,只是标记删除,当更新数据的时候,之前的数据并没有被删除,而是继续存在db中,SQLCipher是SQLite的扩展,为数据库提供256位的AES加密。
Sqlciph使用步骤:
1、加载sqlcipher.jar, libdatabase_sqlcipher.so, libsqlcipher_android.so and libstlport_shared.so。@b@2、在代码文件中,Regarding all source files, change all android.database.sqlite.* which is specified by import, to info.guardianproject.database.sqlite.*. In addition, android.database.Cursor can be used as it is.@b@3、初始化数据并设置数据库密码。
Easy code example@b@ @b@SQLiteDatabase.loadLibs(this); // First, Initialize library by using context. SQLiteOpenHelper.getWritableDatabase(passwoed): // Parameter is password(Suppose that it's string type and It's got@b@in a secure way.)