net.sqlcipher.database.SQLiteException: код ошибки 100: доступна другая строка - PullRequest
0 голосов
/ 14 мая 2019

Я использую SQLiteCipher для шифрования базы данных. Ранее я использовал оператор db.execsql(), который работал нормально. Теперь я изменил запрос на SQLStatment.

Вот мой код

private static void encrypt(Context ctxt) {
        File originalFile = ctxt.getDatabasePath(DBNAME);

        if (originalFile.exists()) {
            File newFile;
            try {
                newFile = File.createTempFile("sqlcipherutils", "tmp", ctxt.getCacheDir());
                SQLiteDatabase db = SQLiteDatabase.openDatabase(originalFile.getAbsolutePath(), "", null, SQLiteDatabase.OPEN_READWRITE);

                SQLiteStatement preparedStatement = db.compileStatement("ATTACH DATABASE ? AS encrypted KEY ?");
                preparedStatement.bindString(1, newFile.getAbsolutePath());
                preparedStatement.bindString(2, DataControllers.getDbKey());
                preparedStatement.execute();

                SQLiteStatement preparedStatement1= db.compileStatement("SELECT sqlcipher_export('encrypted')");
                preparedStatement1.execute();

                SQLiteStatement preparedStatement2= db.compileStatement("DETACH DATABASE encrypted");
                preparedStatement2.execute();

                int version = db.getVersion();
                db.close();
                db = SQLiteDatabase.openDatabase(newFile.getAbsolutePath(), DataControllers.getDbKey(), null, SQLiteDatabase.OPEN_READWRITE);
                db.setVersion(version);
                db.close();
                originalFile.delete();
                newFile.renameTo(originalFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

Сначала выполняются операторы execute, но 2-й оператор execute выдает исключение.

Вот трассировка стека

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.package/com.package.ui.Dashboard}: net.sqlcipher.database.SQLiteException: error code 100: another row available
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2327)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2392)
        at android.app.ActivityThread.access$800(ActivityThread.java:153)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1305)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5293)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
     Caused by: net.sqlcipher.database.SQLiteException: error code 100: another row available
        at net.sqlcipher.database.SQLiteStatement.native_execute(Native Method)
        at net.sqlcipher.database.SQLiteStatement.execute(SQLiteStatement.java:58)
        at com.package.dbconnections.DatabaseOpenHelper.encrypt(DatabaseOpenHelper.java:172)
        at com.package.dbconnections.DatabaseOpenHelper.isDbEncrypted(DatabaseOpenHelper.java:151)
        at com.package.dbconnections.DatabaseOpenHelper.getInstance(DatabaseOpenHelper.java:136)
        at com.package.dbconnections.DatabaseOpenHelper.getUrls(DatabaseOpenHelper.java:605)

1 Ответ

1 голос
/ 14 мая 2019

Я считаю, что единственным разрешением является использование специально созданного rawExecSQL , метода.

не может использовать.raw или rawExecSQL из-за проблем с безопасностью

Нет проблем с безопасностью при использовании этого, поскольку нет шансов для SQL-инъекций, так как нет ввода пользователя.возможно см. SQL-инъекция .

Я считаю, что проблема заключается в том, что в общем случае exec / execute разрешает ограниченные результаты, rawQuery / query возвращает Cursor.Я полагаю, что преобразование, вероятно, генерирует SQL, модифицируя его путем шифрования данных и последующего выполнения этого результирующего SQL в виде потока операторов (отсюда код ошибки 100 при попытке использовать execute).Требуется специальный метод (следовательно, rawExecSQL ), поскольку большинство встроенных методов допускают выполнение только одного оператора.

Рабочий пример

Вотрабочий пример, с другими попытками, закомментированными с результатом (включая код ошибки 100 при попытке использовать SQLiteStatement ).

В примере создается база данных normal ,загружает некоторые данные, извлекает и выгружает данные (для сравнения / проверки), закрывает их, используя стандартные методы SQLiteDatabase android.

Затем создается база данных зашифрованная с использованием метода openorcreate SQlCipher, а затем немедленнозакрыто (таким образом создается файл).

Затем открывается база данных normal с помощью методов SQLCipher, затем присоединяется вновь созданная пустая база данных зашифрованная , выполняется преобразованиеготово, и зашифрованная база данных отключена.Затем normal закрывается.

Наконец, открывается новая зашифрованная база данных , данные извлекаются и сбрасываются (для сравнения / проверки).

Код: -

public class MainActivity extends AppCompatActivity {

    String normaldbname = "mydb";
    String encrypteddbname = "myencrypteddb";
    String password = "thepassword";
    String tablename = "mytable";
    String idcolumn = BaseColumns._ID;
    String namecolumn = "name";
    String[] namelist = new String[]{
            "Fred","Anne","Jane","John",
    };

    SQLiteDatabase normaldb;
    net.sqlcipher.database.SQLiteDatabase normal_for_encryption;
    net.sqlcipher.database.SQLiteDatabase encrypteddb;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        net.sqlcipher.database.SQLiteDatabase.loadLibs(this);
        normaldb = SQLiteDatabase.openOrCreateDatabase(this.getDatabasePath(normaldbname).getPath(),null);
        normaldb.execSQL("CREATE TABLE IF NOT EXISTS " + tablename + " (" +
                idcolumn +
                " INTEGER PRIMARY KEY, " +
                namecolumn +
                " TEXT)");
        ContentValues cv = new ContentValues();
        normaldb.beginTransaction();
        //for (int i=0; i < 1000; i++) { for larger test
            for (String name : namelist) {
                cv.clear();
                cv.put(namecolumn, name);
                normaldb.insert(tablename, null, cv);
            }
        //}
        normaldb.setTransactionSuccessful();
        normaldb.endTransaction();
        DatabaseUtils.dumpCursor(
                normaldb.query(tablename,null,null,null,null,null,null)
        );
        normaldb.close();

        net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase(this.getDatabasePath(encrypteddbname).getPath(),password,null).close();


        normal_for_encryption = net.sqlcipher.database.SQLiteDatabase.openDatabase(
                this.getDatabasePath(normaldbname).getPath(),
                "",null,
                net.sqlcipher.database.SQLiteDatabase.OPEN_READWRITE
        );
        net.sqlcipher.database.SQLiteStatement stmnt = normal_for_encryption.compileStatement("ATTACH DATABASE ? AS encrypted KEY ?");
        stmnt.bindString(1,this.getDatabasePath(encrypteddbname).getPath());
        stmnt.bindString(2,password);
        stmnt.execute();

        /* Ouch net.sqlcipher.database.SQLiteException: error code 100: another row available
        net.sqlcipher.database.SQLiteStatement stmnt2 = normal_for_encryption.compileStatement("SELECT sqlcipher_export('encrypted')");
        stmnt2.execute();
        */
        //normal_for_encryption.rawQuery("SELECT sqlcipher_export('encrypted')",null); //<<<<<<<<< Ouch no such table: mytable: , while compiling: SELECT * FROM mytable
        //normal_for_encryption.execSQL("SELECT sqlcipher_export('encrypted')"); //<<<<<<<<< Ouch net.sqlcipher.database.SQLiteException: unknown error: Queries cannot be performed using execSQL(), use query() instead.
        normal_for_encryption.rawExecSQL("SELECT sqlcipher_export('encrypted')"); //<<<<<<<<< WORKS >>>>>>>>>>
        normal_for_encryption.execSQL("DETACH DATABASE encrypted");
        normal_for_encryption.close();
        encrypteddb = net.sqlcipher.database.SQLiteDatabase.openDatabase(
                this.getDatabasePath(encrypteddbname).getPath(),
                password,null,
                net.sqlcipher.database.SQLiteDatabase.OPEN_READWRITE
        );
        net.sqlcipher.DatabaseUtils.dumpCursor(
                encrypteddb.query(tablename,null,null,null,null,null,null)
        );
        encrypteddb.close();
    }
}
  • Снова обратите внимание на закомментированные строки, которые не работают.
  • Единственным недостатком безопасности в приведенном выше является пароль, который не был защищен дляудобство.

Результат: -

Часть 1 - до преобразования / шифрования: -

2019-05-14 21:10:54.032 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@c237ffc
2019-05-14 21:10:54.032 I/System.out: 0 {
2019-05-14 21:10:54.032 I/System.out:    _id=1
2019-05-14 21:10:54.032 I/System.out:    name=Fred
2019-05-14 21:10:54.032 I/System.out: }
2019-05-14 21:10:54.032 I/System.out: 1 {
2019-05-14 21:10:54.032 I/System.out:    _id=2
2019-05-14 21:10:54.033 I/System.out:    name=Anne
2019-05-14 21:10:54.033 I/System.out: }
2019-05-14 21:10:54.033 I/System.out: 2 {
2019-05-14 21:10:54.033 I/System.out:    _id=3
2019-05-14 21:10:54.033 I/System.out:    name=Jane
2019-05-14 21:10:54.033 I/System.out: }
2019-05-14 21:10:54.033 I/System.out: 3 {
2019-05-14 21:10:54.034 I/System.out:    _id=4
2019-05-14 21:10:54.034 I/System.out:    name=John
2019-05-14 21:10:54.034 I/System.out: }
2019-05-14 21:10:54.034 I/System.out: <<<<<

Часть 2 после шифрования из зашифрованных данных.

2019-05-14 21:10:54.871 I/System.out: >>>>> Dumping cursor net.sqlcipher.CrossProcessCursorWrapper@1bff13d
2019-05-14 21:10:54.872 I/System.out: 0 {
2019-05-14 21:10:54.872 I/System.out:    _id=1
2019-05-14 21:10:54.872 I/System.out:    name=Fred
2019-05-14 21:10:54.872 I/System.out: }
2019-05-14 21:10:54.872 I/System.out: 1 {
2019-05-14 21:10:54.872 I/System.out:    _id=2
2019-05-14 21:10:54.872 I/System.out:    name=Anne
2019-05-14 21:10:54.872 I/System.out: }
2019-05-14 21:10:54.872 I/System.out: 2 {
2019-05-14 21:10:54.872 I/System.out:    _id=3
2019-05-14 21:10:54.872 I/System.out:    name=Jane
2019-05-14 21:10:54.872 I/System.out: }
2019-05-14 21:10:54.873 I/System.out: 3 {
2019-05-14 21:10:54.873 I/System.out:    _id=4
2019-05-14 21:10:54.873 I/System.out:    name=John
2019-05-14 21:10:54.873 I/System.out: }
2019-05-14 21:10:54.873 I/System.out: <<<<<  
...