SQLCipher для Android getReadableDatabase() Overherad

Я изменил свой класс DatabaseHelper, чтобы использовать библиотеку SQLCipher.

Для этого я:

  • Скопировал активы в папку с моими активами и библиотеки (armeabi, x86, commons-codec, guava-r09, sqlcipher) в папку с моими библиотеками.

  • Изменил импорт в моем классе 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()?), но во время отладки кажется, что накладные расходы находятся в getReadableDatabase(password) и getWritableDatabase(password), что имеет смысл, если мое предположение выше правда.

Будет ли плохой практикой перемещение этих вызовов в методы DatabaseHelper.open() и DatabaseHelper.close(), которые будут вызываться Activity всякий раз, когда они создают экземпляр DatabaseHelper, вместо того, чтобы вызывать их для каждого отдельного метода? Поделитесь, пожалуйста, своими знаниями о том, как решить эту проблему.

ИЗМЕНИТЬ:

Я использовал DDMS для отслеживания одного из методов и вижу, что накладные расходы действительно составляют SQLiteOpenHelper.getReadableDatabase() (каждый раз это занимает ~4 секунды). Запросы, кажется, работают быстро, и я не думаю, что мне нужно беспокоиться о них.

Если я разверну вызовы, каждый раз следуя за вызовом с наибольшей продолжительностью, я получаю:

SQLiteDatabase.OpenOrCreateDatabase --> SqLiteDatabase.openDatabase --> SQLiteDatabase.openDatabase --> SQLiteDatabase.setLocale

Таким образом, SQLiteDatabase.setLocale(java.util.Locale) кажется виновником, поскольку каждый раз, когда вызывается getReadableDatabase(), требуется ~ 4 секунды. Я просмотрел источник для SQLiteDatabase, и он просто блокирует БД, вызывает native_setLocale(locale.toString(), mFlags) (здесь имеют место 4-секундные накладные расходы) и разблокирует БД.

Любая идея о том, почему это происходит?


person user1987392    schedule 04.03.2014    source источник


Ответы (2)


Проблема с производительностью, которую вы видите, скорее всего, связана с производным ключом SQLCipher. Производительность SQLCipher при открытии базы данных намеренно низкая, поскольку PBKDF2 использует PBKDF2 для получения ключей (т. net/design" rel="nofollow">http://sqlcipher.net/design). Это действие откладывается до первого использования базы данных, которое происходит в setLocale, поэтому вы видите проблему с производительностью при профилировании.

Наилучший вариант — кэшировать соединение с базой данных, чтобы его можно было использовать несколько раз без необходимости многократно открывать и вводить базу данных. Если это возможно, предпочтительнее открыть базу данных один раз во время запуска. Последующий доступ к тому же дескриптору базы данных не приведет к созданию ключа, поэтому производительность будет намного выше.

Если это невозможно, другой вариант — отключить или ослабить создание ключа. Это заставит SQLCipher использовать меньше раундов PBKDF2 при получении ключа. Хотя это ускорит открытие базы данных, с точки зрения безопасности она будет значительно слабее. Таким образом, это не рекомендуется, кроме как в исключительных случаях. Тем не менее, вот информация о том, как уменьшить итерации KDF:

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

person Stephen Lombardo    schedule 10.03.2014
comment
Можете ли вы сказать мне, как кешировать соединение с базой данных ?? Должен ли я создать еще одну базу данных простого_текста с sqlcipher_export и удалить ее по завершении работы приложения? или просто оставить соединение с БД открытым?? Спасибо - person Saeed.re; 05.09.2014
comment
Я создал свою базу данных с помощью AsyncTask в MainActivity, а затем использовал эту статью, чтобы создать подключение к базе данных и оставить его открытым в моем приложении. - person Saeed.re; 07.09.2014

он расшифровывает весь файл БД, когда я вызываю getReadableDatabase()?

Нет. Он расшифровывает страницы (4 КБ ??) по мере необходимости, на лету.

кажется, что накладные расходы находятся в getReadableDatabase (пароль) и getWritableDatabase (пароль), что имеет смысл, если мое предположение выше верно

Вызывайте их только один раз в течение всего срока службы вашего процесса. Все остальное небезопасно, так как требует, чтобы вы хранили пароль, помимо любых накладных расходов.

Конечно, вы, похоже, жестко кодируете пароль, и в этом случае все это шифрование бессмысленно и является пустой тратой времени.

Поделитесь, пожалуйста, своими знаниями о том, как решить эту проблему.

Используйте Traceview, чтобы определить, точно на что тратится ваше время.

В одном бенчмарке, который я выполнил — преобразовании бенчмарка SQLite в SQLCipher — я не смог обнаружить каких-либо существенных накладных расходов. Насколько я могу судить, дисковый ввод-вывод перекрыл накладные расходы на шифрование.

В той мере, в какой хорошо написанное приложение SQLCipher для Android добавляет накладные расходы, оно усугубляет плохие операции. Так, например, запрос, который должен выполнить сканирование таблицы, уже отстой; SQLCipher будет постепенно усложнять задачу. Решение состоит в том, чтобы добавить соответствующие индексы (или FTS3) по мере необходимости, чтобы избежать сканирования таблицы.

person CommonsWare    schedule 04.03.2014
comment
Эй, спасибо за ваш вклад. Я проследил это, и это действительно «getReadableDatabase()», который вызывает накладные расходы. Пожалуйста, взгляните на мой РЕДАКТИРОВАТЬ. - person user1987392; 05.03.2014
comment
@ user1987392: Это немного странно. setLocale() просто погружается в собственный код C, поэтому я не совсем уверен, что он делает и почему у вас это займет так много времени. Я не вижу, чтобы это занимало столько времени в моей работе с SQLCipher. - person CommonsWare; 05.03.2014
comment
А что касается жестко запрограммированного pw, я знаю об этом, но я бы не назвал это бессмысленным. Это по-прежнему отвлекает от «отслеживания» пользователей, у которых есть рутированный телефон, но они не знают, как реконструировать. Помимо этого, я не думаю, что вы можете многое сделать, если только вы не хотите использовать что-то вроде PBKDF2, но это означало бы запросить у пользователя еще один пароль. - person user1987392; 05.03.2014
comment
Это странно, так как все остальное занимает всего миллисекунды. SQLCipher, похоже, не дает возможности открыть БД с помощью SQLiteDatabase.NO_LOCALIZED_COLLATORS. Я рассмотрел другие вопросы, но решения пока не видно: stackoverflow.com/questions/5230769/ - person user1987392; 05.03.2014
comment
@ user1987392: Я знаю об этом, но я бы не назвал это бессмысленным. Это по-прежнему отвлекает от «слежки» пользователей, у которых есть рутированный телефон, но они не знают, как реконструировать — это не большая аудитория. Что касается первоначальной проблемы, вы можете попробовать sqlcipher-users группу Google, так как там вы, скорее всего, получите поддержку от авторов SQLCipher. - person CommonsWare; 05.03.2014