我修改了我的DatabaseHelper类以使用SQLCipher库。
要做到这一点,我:
将资产复制到我的资产文件夹和库(armeabi,x86,commons-codec,guava-r09,sqlcipher)到我的libs文件夹中。
更改了我的DatabaseHelper
课程中的导入,以便他们指向import net.sqlcipher.database.*
。
应用启动时致电SQLiteDatabase.loadLibs(getApplicationContext());
。
修改了我调用getReadableDatabase()
和getWriteableDatabase()
的行,以便它们包含密码作为参数;
随着数据的正确读/写,一切似乎都能正常工作。我的问题与性能有关,因为我的应用程序可能会以某种频率执行数据库操作,导致它变慢(在迁移到SQLCipher之后)。
对于我的DatabaseHelper方法,我相信我遵循标准方法,例如:
/*
* Getting all MyObjects
*/
public List<MyObject> getMyObjects() {
List<MyObject> objects = new ArrayList<MyObject>();
String selectQuery = "SELECT * FROM " + TABLE_NAME;
Log.v(LOG, selectQuery);
// Open
SQLiteDatabase db = this.getReadableDatabase("...the password...");
// I know this passphrase can be figured out by decompiling.
// Cursor with query
Cursor c = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (c.moveToFirst()) {
do {
MyObject object = createMyObjectFromCursor(c); // Method that builds MyObject from Cursor data
// adding to list
objects.add(object);
} while (c.moveToNext());
}
c.close();
db.close();
return objects;
}
我不完全熟悉SQLCipher的内部机制(例如,当我调用getReadableDatabase()
时它是否解密整个数据库文件?)但是,在调试时,似乎开销在{{1} }和getReadableDatabase(password)
,如果我的上述假设成立,这是有道理的。
将这些调用移动到DatabaseHelper.open()和DatabaseHelper.close()方法,它们会在实例化DatabaseHelper时调用,而不是在每个单独的方法上调用它们,这是不好的做法?请分享您关于如何解决此问题的知识。
修改
我已经使用DDMS跟踪其中一种方法,我可以看到开销确实在getWritableDatabase(password)
(每次约需4秒)。查询似乎运行得很快,我认为我不需要担心它们。
如果我向下钻取电话,每次跟踪持续时间最长的电话,我最终会:
SQLiteOpenHelper.getReadableDatabase()
- &gt; SQLiteDatabase.OpenOrCreateDatabase
- &gt; SqLiteDatabase.openDatabase
- &gt; SQLiteDatabase.openDatabase
所以SQLiteDatabase.setLocale
似乎是罪魁祸首,因为每次调用getReadableDatabase()需要约4秒。我查看了SQLiteDatabase的源代码,它只是锁定了数据库,调用了SQLiteDatabase.setLocale(java.util.Locale)
(这里发生了4秒的开销)并解锁了数据库。
有关为何会发生这种情况的任何想法吗?
答案 0 :(得分:3)
您看到的性能问题很可能是由于SQLCipher密钥派生所致。 SQLCipher打开数据库的性能故意慢,使用PBKDF2执行密钥派生(即数千个SHA1操作)来抵御暴力破解和字典攻击(您可以在http://sqlcipher.net/design阅读更多相关信息)。此活动将推迟到第一次使用数据库时,这恰好发生在setLocale中,这就是您在分析时看到性能问题的原因。
最好的选择是缓存数据库连接,以便可以多次使用它,而无需重复打开和键入数据库。如果可以,在启动期间打开数据库一次是首选的操作过程。对同一数据库句柄的后续访问不会触发密钥派生,因此性能会更快。
如果不可能,则另一个选项是禁用或削弱密钥派生。这将导致SQLCipher在导出密钥时使用更少轮的PBKDF2。虽然这会使数据库打开得更快,但从安全角度来看,它显着变弱。因此,除特殊情况外,不建议使用。也就是说,这里有关于如何减少KDF迭代的信息:
答案 1 :(得分:2)
当我调用getReadableDatabase()?
时,它是否解密整个数据库文件
没有。它可以根据需要动态解密页面(4KB ??)。
似乎开销是在getReadableDatabase(密码)和getWritableDatabase(密码)中,如果我的上述假设为真,这是有意义的
仅在流程的生命周期内调用一次。其他任何东西都是不安全的,因为它要求你保持密码,超出任何开销问题。
当然,您似乎对密码进行了硬编码,在这种情况下,所有这些加密都毫无意义,浪费时间。
请分享您有关如何解决此问题的知识。
使用Traceview确定完全您花费的时间。
在我执行的一个基准测试中 - 将SQLite基准测试转换为SQLCipher - 我无法检测到任何材料开销。正如我所知,磁盘I / O淹没了加密开销。
如果一个写得很好的Android应用程序的SQLCipher增加了开销,它将使糟糕的操作变得更糟。因此,例如,需要进行表扫描的查询已经很糟糕; SQLCipher会让它更难以吸收。解决方案是根据需要添加适当的索引(或FTS3)以避免表扫描。