正確姿式在Android項目中集成sqlcipher

先吐槽一下國內不少IT教程的文章,老舊不說太多不負責任的照抄。因此各位看客若是想在android上面使用sqlcipher不妨先去看看github官方博客,特別是後者,建議在集成以前,先看看官方的博客講解和說明。linux


介紹一下選擇sqlcipher的背景;因項目須要,用到一個三方處理模塊,須要對引用的資源(sqlite)進行數據加密,加密的方案其實很簡單,要麼直接對數據庫自己加密,要麼對數據加密後再寫入,取出後解密。單從實現對效率來講後者確定就不如前者。做爲有追求的新時代coder確定不會選用後者。那剩下的就只有數據庫自己加密,查了一下資料,sqlite自己也有加密方案,可是須要付費,這個確定也不用了,那麼剩下的就是開源方案了,github你值得擁有。android

目前來看sqlcipher 是github上fork 和 star 最多的開源方案,分社區版和付費版。做爲有追求的coder固然是選擇社區版!廢話到此結束,下面開始姿式講解。git


  • the first

仍是建議先去看看官方的介紹文檔官方博客;sqlcipher 的使用分紅兩個部分,1、生成sqlcipher支持的加密數據庫,2、代碼內引入sqlcipher依賴,修改不多的代碼,正常的業務流程內的數據庫操做。github

  • the second

如何生成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應該就能看到明文的數據庫內容了。

  • the third 剩下就是代碼內集成的工做了,此處若是已經寫好了一套本身的數據庫操做,其實要作的事情很簡單,大概4步,以下:

一、代碼內引入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

相關文章
相關標籤/搜索