Encrypt/Decrypt existing database using SQLCipher in Android - android

I'm using the below piece of code to encrypt and decrypt the database i'm able to encrypt but when i'm trying to decrypt i'm getting the below exception.I referred this documentation and TestCases too still facing the same problem.
Exception:
sqlite returned: error code = 26, msg = file is encrypted or is not a database
CREATE TABLE android_metadata failed
Failed to setLocale() when constructing, closing the database
net.sqlcipher.database.SQLiteException: file is encrypted or is not a database
Encrypt:
private static void ConvertNormalToSQLCipheredDB(Context context,
String startingFileName, String endingFileName, String filePassword)
throws IOException {
File mStartingFile = context.getDatabasePath(startingFileName);
if (!mStartingFile.exists()) {
return;
}
File mEndingFile = context.getDatabasePath(endingFileName);
mEndingFile.delete();
SQLiteDatabase database = null;
try {
database = SQLiteDatabase.openOrCreateDatabase(MainApp.mainDBPath,
"", null);
database.rawExecSQL(String.format(
"ATTACH DATABASE '%s' AS encrypted KEY '%s'",
mEndingFile.getAbsolutePath(), filePassword));
database.rawExecSQL("select sqlcipher_export('encrypted')");
database.rawExecSQL("DETACH DATABASE encrypted");
database.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (database.isOpen())
database.close();
mStartingFile.delete();
}
}
Decrypt:
private void decryptDatabase() {
File unencryptedFile = getDatabasePath(PhoneNumbersDatabase.DATABASE_NAME);
unencryptedFile.delete();
File databaseFile = getDatabasePath("encrypt.db");
SQLiteDatabaseHook hook = new SQLiteDatabaseHook() {
public void preKey(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase
.rawExecSQL("PRAGMA cipher_default_use_hmac = off;");
}
public void postKey(SQLiteDatabase sqLiteDatabase) {
}
};
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
databaseFile, "test123", null, hook); // Exception
if (database.isOpen()) {
database.rawExecSQL(String.format(
"ATTACH DATABASE '%s' as plaintext KEY '';",
unencryptedFile.getAbsolutePath()));
database.rawExecSQL("SELECT sqlcipher_export('plaintext');");
database.rawExecSQL("DETACH DATABASE plaintext;");
android.database.sqlite.SQLiteDatabase sqlDB = android.database.sqlite.SQLiteDatabase
.openOrCreateDatabase(unencryptedFile, null);
sqlDB.close();
database.close();
}
databaseFile.delete();
}

You do not need to set cipher_default_use_hmac to off in the preKey event when you attempt to decrypt the database. It is not being disabled when you encrypt the database so an HMAC is being included for every page of the database. Try removing your SQLiteDatabaseHook from the decryption function. Also, consider joining the SQLCipher Mailing List for these type of discussions.

Related

Cause by: android.database.sqlite.SQLiteException no such table: Category (code 1 SQLITE_ERROR): , while compiling: SELECT *FROM Category WHERE tag =?

In my app i used sqlite db file for getting some data. It works fine for most of the users but some of them getting crash Cause by: android.database.sqlite.SQLiteException: no such table: getCtgList (code 1): ,
while compiling: "SELECT *FROM Category WHERE tag =?", new String[]{string}"
Below is my SQLite DBHelper class to create and copy database. in asster/font.db has a Category table . after copying this table some of device missing this table. I searched a lot to fix this error but didn't find any solution. and I'm getting trending issues for app crashing on google play store. Please help me to fix this error.
Code:
package com.softtechbd.nickname_finder.Database;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
public class DBHelper extends SQLiteOpenHelper {
public static final String location = "/data/data/com.com.softtechbd.stylishnicknamegenerator/databases";
private static final String DB_Name = "font_data.db";
private static final int DB_Ver = 2;
private Context mContext;
private SQLiteDatabase database;
private Object String;
public DBHelper(#Nullable Context context) {
super(context, DB_Name, null, DB_Ver);
this.mContext= context;
initialize();
}
private void initialize() {
if (databaseExists() ){
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(mContext);
int dbVersion = prefs.getInt("db_ver", 1);
if (DB_Ver != dbVersion) {
File dbFile = mContext.getDatabasePath(DB_Name);
if (!dbFile.delete()) {
Log.w("dbexists", "Unable to update database");
}
}
}
if (!databaseExists()){
this.getReadableDatabase();
try {
copyDatabase();
}catch (Exception e){
e.printStackTrace();
}
copyDatabase();
//By calling this method and empty database will be created into the default system path
//of your application so we are gonna be able to overwrite that database with our database.
}
}
private void copyDatabase(){
try {
InputStream inputStream = mContext.getAssets().open(DB_Name);
FileOutputStream fileOutputStream = new FileOutputStream(
"/data/data/"+mContext.getPackageName()+"/databases/"+DB_Name);
byte[]arrby = new byte[1024];
do {
int n;
if((n=inputStream.read(arrby)) <=0 ){
fileOutputStream.flush();
fileOutputStream.close();
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences((mContext)).edit();
editor.putInt("db_ver",DB_Ver);
editor.apply();
// Toast.makeText(mContext, "SQLite DB copied", Toast.LENGTH_SHORT).show();
return;
}
fileOutputStream.write(arrby,0,n);
}while (true);
}catch (Exception e){
e.printStackTrace();
}
}
private void closeDatabase(){
SQLiteDatabase sqLiteDatabase = this.database;
if(sqLiteDatabase!= null){
sqLiteDatabase.close();
}
}
private boolean databaseExists() {
File dbFile = mContext.getDatabasePath(DB_Name);
return dbFile.exists();
}
#Override
public void onCreate(SQLiteDatabase db) {
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public void openDatabase(){
String string = this.mContext.getDatabasePath(DB_Name).getPath();
SQLiteDatabase sqLiteDatabase = this.database;
if(sqLiteDatabase!=null && sqLiteDatabase.isOpen()){
return;
}
this.database=SQLiteDatabase.openDatabase(string,null,0);
}
public ArrayList<String> getSymbols() {
ArrayList<String> arrayList = new ArrayList<>();
openDatabase();
Cursor cursor = database.rawQuery("SELECT *FROM symbols ", null);
cursor.moveToFirst();
while (!cursor.isAfterLast()){
arrayList.add(cursor.getString( cursor.getColumnIndex("content")));
cursor.moveToNext();
}
cursor.close();
closeDatabase();
return arrayList;
}
public ArrayList<String> getHotName (){
ArrayList<String> arrayList = new ArrayList<>();
openDatabase();
Cursor cursor = database.rawQuery("SELECT *FROM hot_names", null);
cursor.moveToFirst();
while (!cursor.isAfterLast()){
arrayList.add(cursor.getString( cursor.getColumnIndex("contents")));
cursor.moveToNext();
}
cursor.close();
closeDatabase();
return arrayList;
}
public ArrayList<String> getList(String string){
ArrayList<String> arrayList = new ArrayList<>();
this.openDatabase();
Cursor cursor = this.database.rawQuery("SELECT * FROM emotication_detail WHERE parent_id =?",new String[]{string});
cursor.moveToFirst();
while (!cursor.isAfterLast()){
arrayList.add( cursor.getString( cursor.getColumnIndex("content")) );
// item sample = new item(cursor.getInt(0),cursor.getString(1));
cursor.moveToNext();
}
cursor.close();
this.closeDatabase();
return arrayList;
}
public ArrayList<String> getCtgList (String string){
ArrayList<String> arrayList = new ArrayList<>();
openDatabase();
Cursor cursor = database.rawQuery("SELECT *FROM Category WHERE tag =?", new String[]{string});
cursor.moveToFirst();
while (!cursor.isAfterLast()){
arrayList.add(cursor.getString( cursor.getColumnIndex("content")));
cursor.moveToNext();
}
cursor.close();
closeDatabase();
return arrayList;
}
}
Error log:
Fatal Exception: android.database.sqlite.SQLiteException: no such table: Category (code 1 SQLITE_ERROR): , while compiling: SELECT *FROM Category WHERE tag =?
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(SQLiteConnection.java)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:903)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:514)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
at android.database.sqlite.SQLiteQuery.<init>(SQLiteQuery.java:37)
at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:46)
at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1408)
at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1347)
at com.softtechbd.nickname_finder.Database.DBHelper.getCtgList(DBHelper.java:18)
at com.softtechbd.nickname_finder.Fragments.pick_category_list.onCreateView(pick_category_list.java:4)
at androidx.fragment.app.Fragment.R(Fragment.java:15)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:27)
at androidx.fragment.app.FragmentManagerImpl.b0(FragmentManagerImpl.java:49)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:42)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:6)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:7)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:88)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:4)
at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:2)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6758)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:497)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:912)
I would suggest changing :-
private boolean databaseExists() {
File dbFile = mContext.getDatabasePath(DB_Name);
return dbFile.exists();
}
to :-
private boolean databaseExists() {
File dbFile = mContext.getDatabasePath(DB_Name);
if (dbFile.exists()) {
return true;
}
dbFile.mkdirs(); //<<<<<<<<<< creates the databases folder
return false;
}
along with removing the line this.getReadableDatabase();
The use of getReadableDatabase (or getWritableDatabase as they are effectively the same in most siutations) hides the real underlying issue which is that the databases folder not existing results in the copy failing. That is getReadableDatabase will create the missing databases folder.
Although this works for older devices, newer devices (Android 9+) default to using write-ahead logging (WAL). If a database is created using WAL then two additional files, the WAL file (database file name suffixed with -wal) and the shared memory file (database file name suffixed with -shm) are created.
If just the database file is overwritten (as per the comments) then the -wal file is incompatible with the new database and the attempt to open the database fails, the failure is captured and instead an empty database file is created and returned and hence the loss of any tables. Hence the issue that you are facing.
Creating the databases folder if the database file does not exist, eliminates the need to use getReadable database to create the databases folder and thus the creation of -wal and -shm files. Thus why getReadableDatabase should not be used prior to the copy of the assets file.
Alternately you could delete the -wal and -shm files if they exist, prior to the copy.
i.e. the comments
WAL instead of recording changes in the log allowing them to be rolled back (journal mode) writes the changes to the WAL file and they are applied to the actual database when committed, thus the WAL file is effectively part of the database (roll back is effectively deleting WAL file). You may wish to refer to https://sqlite.org/wal.html
I would also suggest NOT hard coding the database path in the line
FileOutputStream fileOutputStream = new FileOutputStream("/data/data/"+mContext.getPackageName()+"/databases/"+DB_Name);
but instead use mContext.getDatabasePath(DB_NAME) to get the path.
e.g.
FileOutputSream fileOutputStrean = new FileOutputStream(mContext.getDatabasePath(DB_NAME));
Note the above code has not been tested so it may contain some errors, rather it is the principle that is being communicated.
An alternative approach could be to force journal mode using the journal_mode pragma or the SQliteDatabase disableWriteAheadLogging method. https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#disableWriteAheadLogging()

UndeliverableException: android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database

I am using DBFlow with SQLCipher. I am trying to encrypt the already existing SQLite Database(using DBFlow) with SQLCipher in Android.
I used the following code to encrypt the DB:
private void encryptDB() {
SQLiteDatabase.loadLibs(this);
String password = "test123";
String LEGACY_DATABASE_NAME = "legacy.db";
String NEW_DATABASE_NAME = "new_crypt.db";
File newDBFile = getDatabasePath(NEW_DATABASE_NAME);
File legacyFile = getDatabasePath(LEGACY_DATABASE_NAME);
if (!newDBFile.exists() && legacyFile.exists()) {
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(legacyFile, "", null);
db.rawExecSQL(String.format("ATTACH DATABASE '%s' AS encrypted KEY '%s';", newDBFile.getAbsolutePath(), password));
db.rawExecSQL("SELECT sqlcipher_export('encrypted')");
db.rawExecSQL("DETACH DATABASE encrypted;");
db.close();
db = SQLiteDatabase.openDatabase(newDBFile.getAbsolutePath(), password, null, SQLiteDatabase.OPEN_READWRITE);
db.close();
legacyFile.delete();
newDBFile.renameTo(legacyFile);
}
}
The DB is encrypted fine but when I am trying to write any operations:
Place place = new Place();
place.setName("Test");
place.save();
DB Model:
#Table(database = DatabaseManager.class)
public class Place extends BaseModel {
#Column
String name;
// set and get methods goes here
}
then getting the following exception:
io.reactivex.exceptions.UndeliverableException: android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032 SQLITE_READONLY_DBMOVED[1032])
I found a similar post here but not found any solution to it.
Also, I found this to encrypt the DBFlow database with SQLCipher and implemented it. Then it is working if I install it as a fresh app but when I install this app on top of the old app which is having not encrypted DB then it is failing.
net.sqlcipher.database.SQLiteException: file is not a database: , while compiling: select count(*) from sqlite_master;
Please suggest how can I fix this?

android sqlite - table data cannot be updated

the table ( i.e. vaccines) structure is :
id- auto increment primary key
dose1_date - string
dose2_date - string
The DatabaseAccessor class is as follows. The initDB() and setVaccineDates methods are called from another activity. But the database is not updated. The logged message is found in the logcat however. The DatabaseHelper class is not shown here.
public class DatabaseAccessor {
public static DataBaseHelper myDbHelper = null;
public static SQLiteDatabase rdb = null;
public static SQLiteDatabase wdb = null;
public static synchronized final void initDB(Context context) throws Exception {
if (myDbHelper == null) {
myDbHelper = new DataBaseHelper(context);
myDbHelper.openDataBase();
rdb = myDbHelper.getReadableDatabase();
wdb = myDbHelper.getWritableDatabase();
}
}
public static void setVaccineDates(String birthDate) throws SQLException{
try {
String[] selections = null;
String qry = null;
qry = "select * from vaccines order by id";
Cursor cursor = wdb.rawQuery(qry, selections);
Log.d("update qry===== ", qry);
while (cursor.moveToNext()) {
int rowID = Integer.parseInt(cursor.getString(0));
ContentValues values = new ContentValues();
values.put("dose1_date","66666");
values.put("dose2_date","7777");
wdb.update("vaccines", values, "id=?", new String[] {String.valueOf(rowID)});
//wdb.close();
}
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
}// end of method setVaccineDates
}
What to do ?
Edit : If I uncomment the wdb.close() line , I see in logcat
'06-09 04:21:05.387: W/System.err(4144): java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase: /data/data/com.cloudsoft.vaccine/databases/vaccines2.db
'
As a newbie in android it was just a mistake out of ignorance that this situation took place: after update operation I tried to find the changes in the database file (i.e. file with .db extension sitting inside assets folder in Eclipse) through sqlite browser . But what actually happens is the app running in the device (real one or emulator) has its own database which is created from the .db extension file inside assets folder and consequent database operations only affect the app's own database leaving no touch on the database inside the mentioned folder in Eclipse. And there is the way to watch the app's very own database in the running device in Eclipse's 'File Explorer' (in DDMS mode) with the help of Questoid SQlite Manager

Access sqlite database of plugin from native

I am developing an ionic app, and it have several native parts
I am using this plugin, for local databases
https://github.com/litehelpers/Cordova-sqlite-storage
I have no problem in creating or accessing it from the hybrid part, with functions like this:
var db = window.sqlitePlugin.openDatabase({
name: "dbApplication.sqlite",
path: 'documents/',
environment: "SQLITE_ENV_MOBILE",
bgType: 1
});
db.transaction(function (tx) {
tx.executeSql("INSERT INTO main.table ( name, email, sfid ) VALUES (?,?,?)", [user.name, user.email, user.sfid], function () {
deferred.resolve();
}, errorCallback);
}, errorCallback);
But i am trying to access it from the android native part, with this code:
private static String DB_PATH = "documents/";
private static String DB_NAME ="dbApplication.sqlite";// Database name
public boolean openDataBase() throws SQLException
{
String mPath = DB_PATH + DB_NAME;
mDataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.CREATE_IF_NECESSARY);
return mDataBase != null;
}
public HashMap getDataSigner(Integer signerId) {
openDataBase();
SQLiteDatabase db = mDataBase;
//"CREATE TABLE IF NOT EXISTS main.signers (idSigner INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT, idNumber TEXT, rol TEXT)"
Cursor c=db.rawQuery("SELECT name, idNumber FROM signers WHERE idSigner="+signerId+"", null);
HashMap signer = new HashMap();
if(c.moveToFirst())
{
signer.put("name", c.getString(0));
signer.put("idNumber", c.getString(1));
}
return signer;
}
It returns the following error:
Failed to open database 'documents/dbApplication.sqlite'.
android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error(Sqlite code 14): Could not open database,(OS error - 2:No such file or directory)
So my question is: What is the correct path to the databases, in android, for the databases created with the sqlite plugin?
I have also try to dig in the plugin code, but I dont understand it well, as I am not a good android programmer
Also, if someone have the way to access it from IOS native, it would be awesome, as I am going to need it later
Thanks!
I answer to myself
The correct Path, in Android is:
mDataBase = SQLiteDatabase.openDatabase(this.cordova.getActivity().getDatabasePath(DB_NAME).getAbsolutePath(), null, SQLiteDatabase.CREATE_IF_NECESSARY);
Also, remember to close conections before use, if not, you couldn't use it

Inserting data in sqlite database android

I am using a prebuilt database stored in sdcard.I wanted to insert some values in that. while running insert code 0 byte journal file is building in sdcard but data intended to insert is not visible when i am looking into the database,why??? plz help..there is no error or warning.
public void saveRecords(String site_reading, String Demand,String remarks){
SQLiteDatabase mDb = mDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("site_reading", site_reading); // inserting a string
values.put("demand", Demand); // inserting an int
values.put("remark", remarks);
Log.e("in save records", site_reading + remarks + Demand);
mDb.beginTransaction();
mDb.insert("mst_mrt_1", null, values);
mDb.setTransactionSuccessful();
mDb.endTransaction();
}
And I am calling above method in an another Class as
public void add_data(View v){
d_name = veri.dataname;
TestAdapter mDbHelper = new TestAdapter(this,d_name);
mDbHelper.createDatabase();
String reading1 = info_reading.getText().toString();
String Demand1 = info_demand.getText().toString();
String remark1 = list.getSelectedItem().toString();
mDbHelper.saveRecords(reading1,Demand1, remark1);
mDbHelper.close();
}

Categories

Resources