先吐槽一下国内很多IT教程的文章,老旧不说太多不负责任的照抄。所以各位看客如果想在android上面使用sqlcipher不妨先去看看github和官方博客,特别是后者,建议在集成之前,先看看官方的博客讲解和说明。
介绍一下选择sqlcipher的背景;因项目需要,用到一个三方处理模块,需要对引用的资源(sqlite)进行数据加密,加密的方案其实很简单,要么直接对数据库本身加密,要么对数据加密后再写入,取出后解密。单从实现对效率来说后者肯定就不如前者。作为有追求的新时代coder肯定不会选用后者。那剩下的就只有数据库本身加密,查了一下资料,sqlite本身也有加密方案,但是需要付费,这个肯定也不用了,那么剩下的就是开源方案了,github你值得拥有。
目前来看sqlcipher 是github上fork 和 star 最多的开源方案,分社区版和付费版。作为有追求的coder当然是选择社区版!废话到此结束,下面开始姿势讲解。
- the first
还是建议先去看看官方的介绍文档官方博客;sqlcipher 的使用分成两个部分,一、生成sqlcipher支持的加密数据库,二、代码内引入sqlcipher依赖,修改很少的代码,正常的业务流程内的数据库操作。
- the second
如何生成sqlcipher支持的加密数据库,此处不得不再次吐槽很多不负责任的教程和文档,各种要求你写代码去生成加密数据库的文章,你们真的有去实践吗?
其实,对于上面操作,官方的文档也说的很清楚了,下面是摘要:
基于已有的明文数据库生成一个全新加密数据库:
$ ./sqlcipher plaintext.db
sqlite> ATTACH DATABASE 'encrypted.db' AS encrypted KEY 'testkey';
sqlite> SELECT sqlcipher_export('encrypted');
sqlite> DETACH DATABASE encrypted;
看到这里是不是存在一个疑问, sqlcipher的可执行文件从哪里来?
我是这样做的:
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步,如下:
1、代码内引入sqlcipher 依赖,如下:
compile 'net.zetetic:android-database-sqlcipher:3.5.9'
确定版本之前,请一定自己先去github上看看现在最新release的版本是多少,在填写版本。毕竟coder解决问题都是在下一个版本,你我都懂。
2、导入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会有各种莫名奇妙的问题等待你。不要相信网上其它不负责任的教程的文章。
3、在数据库实际操作之前加入如下代码:
SQLiteDatabase.loadLibs(context);
我是在实例的DBHelper的构造方法内引入的,各位看客你们任意。
4、 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
关于打包和混淆
在这里提一下,打包尤其是jar包,建议把依赖让外部去做,不要把sqlcipher的代码全部打如你自己的jar包内,原因如下:1、本来一句话就可以搞定的事情,为什么要折腾这么多? 2、如果外部也需要引入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