我已经修改了我的DatabaseHelper类以使用SQLCipher库。
为此,我:
DatabaseHelper
类中的导入,使它们指向import net.sqlcipher.database.*
。SQLiteDatabase.loadLibs(getApplicationContext());
。getReadableDatabase()
和getWriteableDatabase()
的行,使它们包含一个密码作为参数;当数据被正确读写时,一切似乎都正常。我的问题与性能有关,因为我的应用程序可能以某种频率执行DB操作,导致它变得缓慢(在迁移到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()
时,它会解密整个DB文件吗?)但是,在调试时,开销似乎在getReadableDatabase(password)
和getWritableDatabase(password)
中,如果上面的假设是正确的话,这是有意义的。
将这些调用移动到DatabaseHelper.open()和DatabaseHelper.close()方法(每当它们实例化DatabaseHelper时都会被它们调用),而不是在每个单独的方法上调用它们,这是否是一种糟糕的做法?请分享如何解决这一问题的知识。
编辑:
我已经使用DDMS来跟踪其中一个方法,并且我可以看到开销确实在SQLiteOpenHelper.getReadableDatabase()
上(花费了大约4秒)。每次)。这些查询似乎运行得很快,我不认为我需要担心它们。
如果我向下钻取调用,每次跟踪持续时间最长的调用,我将得到以下结果:
SQLiteDatabase.OpenOrCreateDatabase
-> SqLiteDatabase.openDatabase
--> SQLiteDatabase.openDatabase
--> SQLiteDatabase.setLocale
因此,SQLiteDatabase.setLocale(java.util.Locale)
似乎是罪魁祸首,因为每次调用getReadableDatabase()都要花费大约4秒。我查看了SQLiteDatabase的源代码,它只锁定DB,调用native_setLocale(locale.toString(), mFlags)
(4秒)。(开销发生在这里),并解锁DB。
知道为什么会这样吗?
发布于 2014-03-10 18:56:28
您所看到的性能问题很可能是由于SQLCipher密钥派生造成的。SQLCipher打开数据库的性能故意慢,使用PBKDF2执行密钥派生(即数千个SHA1操作),以抵御蛮力和字典攻击(您可以在http://sqlcipher.net/design上读到更多关于这一点的信息)。这个活动被推迟到数据库的第一次使用,这恰好发生在setLocale中,这就是为什么您在分析时会看到性能问题。
最好的选择是缓存数据库连接,这样就可以多次使用它,而不必重复打开和输入数据库。如果可能的话,在启动期间打开一次数据库是首选的操作方法。对同一个数据库句柄的后续访问不会触发密钥派生,因此性能将更快。
如果这是不可能的,则另一个选项是禁用或削弱密钥派生。这将导致SQLCipher在派生密钥时使用较少的PBKDF2轮。虽然这将使数据库更快地打开,但从安全性的角度来看,它要弱得多。因此,除例外情况外,不建议这样做。尽管如此,以下是关于如何减少KDF迭代的信息:
iter
发布于 2014-03-04 15:55:09
当我调用getReadableDatabase()时,它会解密整个DB文件吗?
不是的。它解密页(4KB?)根据需要,在飞行中。
开销似乎在getReadableDatabase(密码)和getWritableDatabase(密码)中,如果我上面的假设是正确的,这是有意义的。
只在整个过程中调用一次。任何其他问题都是不安全的,因为它要求您在任何开销问题之上保留密码。
当然,您似乎是在对密码进行硬编码,在这种情况下,所有这些加密都是毫无意义的,是浪费时间的。
请分享如何解决这一问题的知识。
使用Traceview确定您的时间是在哪里度过的。
在我执行的一个基准测试中--将一个SQLite基准转换为SQLCipher --我无法检测到任何物质开销。磁盘I/O淹没了加密开销,据我所知。
如果为Android应用程序编写良好的SQLCipher会增加开销,就会使糟糕的操作变得更糟。因此,例如,需要进行表扫描的查询已经很难执行了;SQLCipher会使它变得越来越难。解决方案是根据需要添加适当的索引(或FTS3),以避免表扫描。
https://stackoverflow.com/questions/22176445
复制相似问题