wcdb使用笔记

本地数据加密

由于项目涉及到一些用户隐私数据的存储,所以需要对保存在客户端本地的数据进行加密,以防止用户隐私数据在设备被root的情况下出现泄漏。目前android的本地数据存储基本分为file,sharepreference和database,所以对数据的加密操作分为了两种:文件加密和文件内的数据加密。文件加密就是在打开该文件的时候需要获得正确的加密秘钥才能从该文件中读取数据或者写入数据到该文件中,这种方式相对简单。文件内数据加密就是打开文件时不需要解密,但是从文件中读取出来的数据是加密过的密文,需要对其进行解密才能识别和使用,同样在写入数据到文件的时候,也需要先将数据进行加密然后再写入到文件,这种方式相比第一种复杂一些,但是安全性更高,对数据保护的粒度更细。file和sharepreference使用上述两种方式实现的成本差异并不明显,database使用文件内数据比如列字段加密相比文件加密要更加复杂,所以database通常使用文件加密的方式来实现,比如有名的sqlcipher就是采用的这种方式,今天我们将要提到的wcdb也是采用的这种方式来保护数据的。

WCDB

wcdb是微信团队贡献的一个开源项目,是一个高效易用的数据库框架,并且支持多个平台。关于wcdb的更多介绍以及如何集成和使用WCDB请参考Tencent/wcdb,这里不再赘述。下面主要介绍一下我在将WCDB集成到原有项目(Android客户端)的过程中遇到的一些问题以及解决方案。因为我也是在使用WCDB的过程中不断查找资料,发现了一些别人没有遇到或者别人遇到了自己也遇到了但是没有清楚答案的问题,所以才想记录下来,以作备忘。

混淆

通常我们在引入一些知名的第三方库的时候,都需要在proguard中加入一些规则来屏蔽对该库的混淆,因为混淆可能会导致该库的部分功能异常,比如glide的项目介绍上就有如下说明:

《wcdb使用笔记》 image

但是我们在wcdb的项目上没有看到关于混淆这一块的介绍,刚开始我以为不需要,后来打了一个release包后发现wcdb运行报错,才知道这里还是有必要加一下的。内容如下:

-keep class com.tencent.wcdb.** {*;}

关于在proguard中如何加入第三方库的放混淆配置,可以使用以下方法。在android studio的project视图下的External Libraries中找到对应的库名字,比如wcdb,然后就可以看到这个库的完整包路径了。如下图:

《wcdb使用笔记》 image

加密已有数据

由于我的项目以前是使用android原生的sqlite储存数据,现在要迁移到wcdb上,就必须考虑到版本兼容的问题,当老版本升级到新版本后确保老版本上保存在本地的数据能无缝迁移到新版本上面来。那么对于已有数据的加密,wcdb的项目里面也提供了一个例子,我将核心代码贴出来:

File oldDbFile = mContext.getDatabasePath(OLD_DATABASE_NAME);
if (oldDbFile.exists()) {
    // SQLiteOpenHelper begins a transaction before calling onCreate().
    // We have to end the transaction before we can attach a new database.
    db.endTransaction();

    // Attach old database to the newly created, encrypted database.
    String sql = String.format("ATTACH DATABASE %s AS old KEY '';",
            DatabaseUtils.sqlEscapeString(oldDbFile.getPath()));
    db.execSQL(sql);

    // Export old database.
    db.beginTransaction();
    DatabaseUtils.stringForQuery(db, "SELECT sqlcipher_export('main', 'old');", null);
    db.setTransactionSuccessful();
    db.endTransaction();

    // Get old database version for later upgrading.
    int oldVersion = (int) DatabaseUtils.longForQuery(db, "PRAGMA old.user_version;", null);

    // Detach old database and enter a new transaction.
    db.execSQL("DETACH DATABASE old;");

    // Old database can be deleted now.
    oldDbFile.delete();
}

这里很多第一次使用wcdb的童鞋,如果没有细看这个例子中的代码会比较容易犯一个错误,就是将

DatabaseUtils.stringForQuery(db, "SELECT sqlcipher_export('main', 'old');", null);

不小心写成了

db.execSQL("SELECT sqlcipher_export('encrypted')");

然后发现怎么运行都会报异常。针对这个问题在项目里面还有一个issue:集成WCDB后希望能实现解密数据库 #36。我也遇到了,后面找了好久才发现是这里的问题,原因是wcdb的execSQL不支持select,但是原生的sqlite以及sqlcipher都是支持,所以在第一次用的时候就会觉得很奇怪,这条语句语法看上去完全没问题,运行就是行不通。

数据解密

有时为了方便定位问题,需要查看database的数据,如果是debug包可以直接联调查看,但如果是release包那么就必须手动导出db文件然后进行解密查看了。这里有两种方式解密:

  1. db文件拷贝到电脑上面,然后安装sqlcipher工具进行解密
  2. 在手机上使用wcdb sdk来解密

电脑端使用sqlcipher解密

首先在电脑端安装sqlcipher工具(链接:https://pan.baidu.com/s/1_yCOoqZJTersQ6KmkZb13w
提取码:jorr ),这里以windows为例,下载该工具后进入sqlcipher-3.0.1\bin\目录下,打开命令行工具,输入以下命令,如下图:

《wcdb使用笔记》 image
《wcdb使用笔记》 image

其中123456为加密秘钥,encrypt.db为加密的数据库文件,这里有几个地方需要注意:

  1. wcdb默认加密后的db文件的pagesize为4096,所以这里如果不设置cipher_page_size或者设置的值与你在使用wcdb加密时设置的pagesize一致的话,就会报错,这一点我网上找了很久都没发现有人提到,有的文章上设置的是1024,但是我试过就是不行,后来找了一个未加密的db文件看了下pagesize的值才发现不对。
  2. 在进行任何操作之前需要先使用pragma key=…来解密数据库,否则可能会报错“Error: file is encrypted or is not a database”,这里网上也有很多人跟我一样遇到。
  3. wcdb使用了sqlcipher来加密的,在加解密的时候必须使用一致的版本,比如我们使用sqlcipher3.x加密的,那么在解密的时候也必须使用3.x版本,否则就会解密失败。

有时在命令行里面解密和查看数据不太方便,我们可以将加密db中的数据导出到一个未加密的db中,首先我们在命令行工具中使用sqlcipher打开encrypt.db文件,然后输入如下命令:

pragma key='123456';
pragma cipher_page_size=4096;
attach database 'plaintext.db' as plaintext key '';
select sqlcipher_export('plaintext');
detach database plaintext;

其中123456为加密秘钥,palintext.db为解密后的db文件。执行完上述命令后,我们就会在当前目录下看到一个解密后的plaintext.db文件了,然后使用其他的数据库工具如sqlite expert等就可以正常打开查看里面的数据了。

wcdb sdk解密

使用wcdb sdk解密数据与之前提到的加密数据的过程是相识的,这里结合代码来详细说明。

SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(getDataBase("encrypt.db"), "123456".getBytes(), null, null);
//将要生成的未加密db文件,这里可以根据自己的需要放在sd目录中方便导出查看
File plainDbFile = mContext.getDatabasePath("plaintext.db");
// Attach database 
String sql = String.format("ATTACH DATABASE %s AS plaintext KEY ';",
        DatabaseUtils.sqlEscapeString(plainDbFile.getPath()));
db.execSQL(sql);

//导出加密数据库到未加密数据库中,sqlcipher_export这个方法有两个参数,第一个参数plaintext代表新生成的未加密数据库,第二个参数main代表已加密的数据库,也可以不使用第二个参数,那么默认将使用db关联的数据库导出到plaintext中。详细使用可以查看wcdb开源项目的android demo
db.beginTransaction();
DatabaseUtils.stringForQuery(db, "SELECT sqlcipher_export('plaintext', 'main');", null);
db.setTransactionSuccessful();
db.endTransaction();

// Detach plaintext database
db.execSQL("DETACH DATABASE plaintext;");

这样我们就将一个加密数据库导出到了plaintext.db中。

总结

本文算是自己在项目中使用WCDB过程中一些使用心得和问题总结,WCDB在使用这一块其实还有更多高级的用法,这里并没有提到,后面有时间了再做详述。

    原文作者:codeKeeper
    原文地址: https://www.jianshu.com/p/8fab9eba909d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞