SQLCipher for Android getReadableDatabase()Overherad

时间:2014-03-04 15:42:40

标签: android performance sqlite sqlcipher

我修改了我的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秒的开销)并解锁了数据库。

有关为何会发生这种情况的任何想法吗?

2 个答案:

答案 0 :(得分:3)

您看到的性能问题很可能是由于SQLCipher密钥派生所致。 SQLCipher打开数据库的性能故意慢,使用PBKDF2执行密钥派生(即数千个SHA1操作)来抵御暴力破解和字典攻击(您可以在http://sqlcipher.net/design阅读更多相关信息)。此活动将推迟到第一次使用数据库时,这恰好发生在setLocale中,这就是您在分析时看到性能问题的原因。

最好的选择是缓存数据库连接,以便可以多次使用它,而无需重复打开和键入数据库。如果可以,在启动期间打开数据库一次是首选的操作过程。对同一数据库句柄的后续访问不会触发密钥派生,因此性能会更快。

如果不可能,则另一个选项是禁用或削弱密钥派生。这将导致SQLCipher在导出密钥时使用更少轮的PBKDF2。虽然这会使数据库打开得更快,但从安全角度来看,它显着变弱。因此,除特殊情况外,不建议使用。也就是说,这里有关于如何减少KDF迭代的信息:

http://sqlcipher.net/sqlcipher-api/#kdf_iter

答案 1 :(得分:2)

  

当我调用getReadableDatabase()?

时,它是否解密整个数据库文件

没有。它可以根据需要动态解密页面(4KB ??)。

  

似乎开销是在getReadableDatabase(密码)和getWritableDatabase(密码)中,如果我的上述假设为真,这是有意义的

仅在流程的生命周期内调用一次。其他任何东西都是不安全的,因为它要求你保持密码,超出任何开销问题。

当然,您似乎对密码进行了硬编码,在这种情况下,所有这些加密都毫无意义,浪费时间。

  

请分享您有关如何解决此问题的知识。

使用Traceview确定完全您花费的时间。

在我执行的一个基准测试中 - 将SQLite基准测试转换为SQLCipher - 我无法检测到任何材料开销。正如我所知,磁盘I / O淹没了加密开销。

如果一个写得很好的Android应用程序的SQLCipher增加了开销,它将使糟糕的操作变得更糟。因此,例如,需要进行表扫描的查询已经很糟糕; SQLCipher会让它更难以吸收。解决方案是根据需要添加适当的索引(或FTS3)以避免表扫描。