首页

SQLite安全开发注意点及数据有效性验证代码示例说明

标签:SQLite,数据库,android,安全,敏感数据     发布时间:2017-11-04   

一、注意事项

1、对数据库访问进行控制。@b@2、反注入。@b@3、对敏感数据进行保护。

二、操作数据库

1.正确的设置数据库文件的位置和访问权限。 @b@2.当与其他应用共享数据库的时候,使用content provider进行访问控制。 @b@3.在进行数据库操作的时候,使用占位符进行变量操作。

SQLite安全开发注意点及数据有效性验证代码示例说明

三、反注入

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.)