先吐槽一下國內不少IT教程的文章,老舊不說太多不負責任的照抄。因此各位看客若是想在android上面使用sqlcipher不妨先去看看github和官方博客,特別是後者,建議在集成以前,先看看官方的博客講解和說明。linux
介紹一下選擇sqlcipher的背景;因項目須要,用到一個三方處理模塊,須要對引用的資源(sqlite)進行數據加密,加密的方案其實很簡單,要麼直接對數據庫自己加密,要麼對數據加密後再寫入,取出後解密。單從實現對效率來講後者確定就不如前者。做爲有追求的新時代coder確定不會選用後者。那剩下的就只有數據庫自己加密,查了一下資料,sqlite自己也有加密方案,可是須要付費,這個確定也不用了,那麼剩下的就是開源方案了,github你值得擁有。android
目前來看sqlcipher 是github上fork 和 star 最多的開源方案,分社區版和付費版。做爲有追求的coder固然是選擇社區版!廢話到此結束,下面開始姿式講解。git
仍是建議先去看看官方的介紹文檔官方博客;sqlcipher 的使用分紅兩個部分,1、生成sqlcipher支持的加密數據庫,2、代碼內引入sqlcipher依賴,修改不多的代碼,正常的業務流程內的數據庫操做。github
如何生成sqlcipher支持的加密數據庫,此處不得再也不次吐槽不少不負責任的教程和文檔,各類要求你寫代碼去生成加密數據庫的文章,大家真的有去實踐嗎?sql
其實,對於上面操做,官方的文檔也說的很清楚了,下面是摘要:數據庫
基於已有的明文數據庫生成一個全新加密數據庫:windows
$ ./sqlcipher plaintext.db sqlite> ATTACH DATABASE 'encrypted.db' AS encrypted KEY 'testkey'; sqlite> SELECT sqlcipher_export('encrypted'); sqlite> DETACH DATABASE encrypted;
看到這裏是否是存在一個疑問, sqlcipher的可執行文件從哪裏來?api
我是這樣作的:ide
brew install sqlcipher
linux 應該能夠這個姿式:(未實踐,看客們本身試試)工具
sudo apt-get install sqlcipher
windows自行搜索把。
成功安裝以後應該是這個樣子的:
Hehr-2:assets hehr$ sqlcipher SQLCipher version 3.20.1 2017-08-24 16:21:36 Enter ".help" for instructions Enter SQL statements terminated with a ";" Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database.
因此上面的第一行命令:
./sqlcipher plaintext.db -> sqlcipher plaintext.db
若是有耐心看到這裏,估計各位已經能順利生成大家的encrypted.db,生成完畢以後可使用sqlite的圖形化瀏覽工具(推薦db browser),訪問數據庫試試,應該會提示你輸入密碼,輸入大家上面設置的testkey應該就能看到明文的數據庫內容了。
一、代碼內引入sqlcipher 依賴,以下:
compile 'net.zetetic:android-database-sqlcipher:3.5.9'
肯定版本以前,請必定本身先去github上看看如今最新release的版本是多少,在填寫版本。畢竟coder解決問題都是在下一個版本,你我都懂。
二、導入sqlcipher倉庫下的SQLiteOpenHelper、SQLiteDatabase等等以下:
import net.sqlcipher.DatabaseErrorHandler; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteOpenHelper; import net.sqlcipher.Cursor;
這裏強調一下,Cursor 必須更換成sqlcipher 包下的,如使用原生的sqlite cursor會有各類莫名奇妙的問題等待你。不要相信網上其它不負責任的教程的文章。
三、在數據庫實際操做以前加入以下代碼:
SQLiteDatabase.loadLibs(context);
我是在實例的DBHelper的構造方法內引入的,各位看客大家任意。
四、 getReadableDatabase() 、getWritableDatabase() 方法內填入以前設置的數據庫訪問密鑰,比方說這個姿式的代碼
sqLiteDatabase = dbHelper.getWritableDatabase(Conf.DB.PWD);`
到此,代碼內的集成工做已經所有完成,總結一下。導了4個包,複製了一句代碼,改了一個錯誤提示。
爲了知足喜歡拿來主義的coder,我把我這裏的數據庫操做的代碼,貼上來。
public class DBHelper extends SQLiteOpenHelper { //數據庫版本號 private static final int DATABASE_VERSION=1; public DBHelper (Context context , String dbName) { this(context,dbName,null,DATABASE_VERSION); SQLiteDatabase.loadLibs(context);//加載數據庫SO } public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) { } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
/** * COPY FROM https://blog.csdn.net/qq_16624353/article/details/53728373 * @param */ public class DatabaseManger { private static DBHelper dbHelper ; private SQLiteDatabase sqLiteDatabase; private static DatabaseManger instance = null; /** * * 構造方法上下文 * * @param context * @return */ private DatabaseManger(Context context , String dbName) { dbHelper = new DBHelper(context,dbName); sqLiteDatabase = dbHelper.getWritableDatabase(Conf.DB.PWD); } /** * * 獲取本類對象的實例 * @param context * @return */ public static synchronized DatabaseManger getInstance(Context context,String dbName) { if (instance == null) { if(context == null) { throw new RuntimeException("NullContextException"); } instance = new DatabaseManger(context , dbName ); } return instance; } /** * 關閉數據庫 */ public synchronized void close() { if(sqLiteDatabase.isOpen()) { sqLiteDatabase.close(); sqLiteDatabase=null; } if(dbHelper!=null) { dbHelper.close(); dbHelper=null; } if(instance != null) { instance = null; } } /** * 執行一條sql語句 * */ public void execSql(String sql) { if(sqLiteDatabase.isOpen()) { sqLiteDatabase.execSQL(sql); } else { throw new RuntimeException("The DataBase has already closed"); } } /** * sql執行查詢操做的sql語句 * selectionargs查詢條件 * 返回查詢的遊標,可對數據進行操做,可是須要本身關閉遊標 */ public Cursor queryData2Cursor(String sql, String[] selectionArgs)throws Exception { Cursor cursor = null; if(sqLiteDatabase.isOpen()) { cursor = sqLiteDatabase.rawQuery(sql,selectionArgs); }else { throw new RuntimeException("The DataBase has already closed"); } return cursor; } /** * 查詢表中數據總條數 * 返回表中數據條數 * */ public int getDataCounts(String table)throws Exception { Cursor cursor = null; int counts = 0; if(sqLiteDatabase.isOpen()) { cursor = queryData2Cursor("select * from "+ table,null); if(cursor != null && cursor.moveToFirst()) { counts = cursor.getCount(); } }else { throw new RuntimeException("The DataBase has already closed"); } return counts; } /** * * 消除表中全部數據 * @param table * @throws Exception */ public void clearAllData(String table)throws Exception { if(sqLiteDatabase.isOpen()) { execSql("delete from "+ table); }else { throw new RuntimeException("The DataBase has already closed"); } } /** * * 插入數據 * @param sql 執行操做的sql語句 * @param bindArgs sql中的參數,參數的位置對於佔位符的順序 * @return 返回插入對應的額ID,返回0,則插入無效 * @throws Exception */ public long insertDataBySql(String sql,String[] bindArgs)throws Exception { long id = 0; if(sqLiteDatabase.isOpen()) { SQLiteStatement sqLiteStatement = sqLiteDatabase.compileStatement(sql); if(bindArgs != null) { int size = bindArgs.length; for (int i=0; i < size;i++) { sqLiteStatement.bindString(i+1,bindArgs[i]); } id=sqLiteStatement.executeInsert(); sqLiteStatement.close(); } }else { throw new RuntimeException("The DataBase has already closed"); } return id; } /** * * 插入數據 * @param table 表名 * @param values 數據 * @return 返回插入的ID,返回0,則插入失敗 * @throws Exception */ public long insetData(String table, ContentValues values)throws Exception { long id=0; if(sqLiteDatabase.isOpen()) { id=sqLiteDatabase.insertOrThrow(table,null,values); }else { throw new RuntimeException("The DataBase has already closed"); } return id; } /** * * 批量插入數據 * @param table 表名 * @param list 數據源 * @param args 數據鍵名 key * @return * @throws Exception */ public long insertBatchData(String table, List<Map<String,Object>> list, String[] args)throws Exception { long insertNum =0; sqLiteDatabase.beginTransaction(); ContentValues contentValues = new ContentValues(); for(int i=0; i <list.size();i++) { for(int j=0;j<args.length;j++) { contentValues.put(args[j],list.get(i).get(args[j]).toString()); } long id = insetData(table,contentValues); if(id >0) { insertNum++; } } sqLiteDatabase.setTransactionSuccessful(); sqLiteDatabase.endTransaction(); return insertNum; } /** * * 更新數據 * @param table 表名 * @param values 須要更新的數據 * @param whereClaause 表示sql語句中條件部分的語句 * @param whereArgs 表示佔位符的值 * @return * @throws Exception */ public int updateData(String table,ContentValues values,String whereClaause,String[] whereArgs)throws Exception { int rowsNum = 0; if(sqLiteDatabase.isOpen()) { rowsNum = sqLiteDatabase.update(table,values,whereClaause,whereArgs); }else { throw new RuntimeException("The DataBase has already closed"); } return rowsNum; } /** * * 刪除數據 * @param sql 待執行的sql語句 * @param bindArgs sql語句中的參數,參數的順序對應占位符的順序 */ public void deleteDataBySql(String sql,String[] bindArgs)throws Exception { if(sqLiteDatabase.isOpen()) { SQLiteStatement statement = sqLiteDatabase.compileStatement(sql); if(bindArgs != null) { int size = bindArgs.length; for(int i= 0;i<size;i++) { statement.bindString(i+1,bindArgs[i]); } statement.execute(); statement.close(); } }else { throw new RuntimeException("The DataBase has already closed"); } } /** * * 刪除數據 * @param table 表名 * @param whereClause sql中的條件語句部分 * @param whereArgs 佔位符的值 * @return */ public long deleteData(String table,String whereClause,String[] whereArgs)throws Exception { long rowsNum =0; if(sqLiteDatabase.isOpen()) { rowsNum=sqLiteDatabase.delete(table,whereClause,whereArgs); }else { throw new RuntimeException("The DataBase has already closed"); } return rowsNum; } /** * * @param table 表名 * @param columns 查詢須要返回的列的字段 * @param selection SQL語句中的條件語句 * @param selectionArgs 佔位符的值 * @param groupBy 表示分組,能夠爲NULL * @param having SQL語句中的having,能夠爲null * @param orderBy 表示結果排序,能夠爲null * @return * @throws Exception */ public Cursor queryData(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy)throws Exception { return queryData(table,columns,selection,selectionArgs,groupBy,having,orderBy,null); } /** * * @param table 表名 * @param columns 查詢須要返回的列的字段 * @param selection SQL語句中的條件語句 * @param selectionArgs 佔位符的值 * @param groupBy 表示分組,能夠爲NULL * @param having SQL語句中的having,能夠爲null * @param orderBy 表示結果排序,能夠爲null * @param limit 表示分頁 * @return * @throws Exception */ public Cursor queryData(String table,String[] columns,String selection,String[] selectionArgs, String groupBy,String having,String orderBy,String limit)throws Exception { return queryData(false,table,columns,selection,selectionArgs,groupBy,having,orderBy,limit); } /** * @param distinct true if you want each row to be unique,false otherwise * @param table 表名 * @param columns 查詢須要返回的列的字段 * @param selection SQL語句中的條件語句 * @param selectionArgs 佔位符的值 * @param groupBy 表示分組,能夠爲NULL * @param having SQL語句中的having,能夠爲null * @param orderBy 表示結果排序,能夠爲null * @param limit 表示分頁 * @return * @throws Exception */ public Cursor queryData(boolean distinct,String table,String[] columns,String selection, String[] selectionArgs,String groupBy, String having,String orderBy,String limit)throws Exception { return queryData(null,distinct,table,columns,selection,selectionArgs,groupBy,having,orderBy,limit); } /** * @param cursorFactory 遊標工廠 * @param distinct true if you want each row to be unique,false otherwise * @param table 表名 * @param columns 查詢須要返回的列的字段 * @param selection SQL語句中的條件語句 * @param selectionArgs 佔位符的值 * @param groupBy 表示分組,能夠爲NULL * @param having SQL語句中的having,能夠爲null * @param orderBy 表示結果排序,能夠爲null * @param limit 表示分頁 * @return * @throws Exception */ public Cursor queryData(SQLiteDatabase.CursorFactory cursorFactory,boolean distinct,String table,String[] columns,String selection, String[] selectionArgs,String groupBy, String having,String orderBy,String limit)throws Exception { Cursor cursor = null; if(sqLiteDatabase.isOpen()){ cursor = sqLiteDatabase.queryWithFactory(cursorFactory, distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit); }else{ throw new RuntimeException("The database has already closed!"); } return cursor; } /** * * @param sql 執行查詢造做的SQL語句 * @param selectionArgs 查詢條件 * @param object JAVABEAN對象 * @return 查詢結果 */ public List<Map<String,String >> query2List(String sql,String[] selectionArgs,Object object)throws Exception { List<Map<String,String>> list = new ArrayList<>(); if(sqLiteDatabase.isOpen()) { Cursor cursor = null; cursor = queryData2Cursor(sql,selectionArgs); Field[] fields; HashMap<String,String> map; if(cursor !=null && cursor.getCount()>0) { while (cursor.moveToNext()) { map = new HashMap<>(); fields = object.getClass().getDeclaredFields(); for(int i =0; i< fields.length;i++) { /** * 1經過key,即列名,獲得所在的列索引 * 2經過所在行以及所在列的索引,獲得惟一肯定的隊友值 * 3將值與鍵封裝到MAP集合中,此條數據讀取完畢 */ map.put(fields[i].getName(),cursor.getString(cursor.getColumnIndex(fields[i].getName()))); } list.add(map); } cursor.close(); } }else { throw new RuntimeException("The database has already closed!"); } return list; } }
關於打包和混淆
在這裏提一下,打包尤爲是jar包,建議把依賴讓外部去作,不要把sqlcipher的代碼所有打如你本身的jar包內,緣由以下:一、原本一句話就能夠搞定的事情,爲何要折騰這麼多? 二、若是外部也須要引入sqlcipher呢?
混淆部分我直接貼了:
#KEEP SQLCHIPHER -keep class com.xxx.xxx.utils.db.**{*;} -keep class net.sqlcipher.database.**{*;} -keep class net.sqlcipher.**{*;}
注意,本身的這倆數據庫操做的類不要混淆了,沒啥技術含量,也沒有那個必要。
以上,祝君成功!
hehr 2018.6.23 00:48