net.sqlcipher.database.SQLiteException: код ошибки 100: доступна другая строка
Я использую 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 ответ
Я считаю, что единственное решение - использовать специально созданный метод rawExecSQL.
не могу использовать. raw или rawExecSQL из-за проблем безопасности
Нет проблем с безопасностью при использовании этого, так как нет шансов для SQL-инъекций, так как нет ввода пользователя. возможно увидеть SQL инъекцию.
Я считаю, что проблема заключается в том, что в общем случае exec / execute разрешает ограниченные результаты, rawQuery / query возвращает Cursor. Я полагаю, что преобразование, вероятно, генерирует SQL, модифицируя его путем шифрования данных и последующего выполнения этого результирующего SQL в виде потока операторов (отсюда код ошибки 100 при попытке использовать execute). Требуется специальный метод (следовательно, rawExecSQL), так как большинство встроенных методов допускают выполнение только одного оператора.
Рабочий пример
Вот рабочий пример с другими попытками, закомментированными с результатом (включая код ошибки 100 при попытке использовать SQLiteStatement).
В этом примере создается обычная база данных, загружаются некоторые данные, извлекаются и выгружаются данные (для сравнения / проверки), которые закрываются с использованием стандартных методов Androiditeite для SQLiteDatabase.
Зашифрованная база данных затем создается с использованием метода openorcreate SQlCipher и затем немедленно закрывается (таким образом, создавая файл).
Затем открывается обычная база данных с помощью методов SQLCipher, затем присоединяется вновь созданная пустая зашифрованная база данных, затем выполняется преобразование и отсоединяется зашифрованная база данных. Нормаль тогда закрывается.
Наконец новая зашифрованная база данных открыта, данные извлечены и сброшены (для сравнения / доказательства).
Код:-
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: <<<<<