1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

SQLCipherとは、SQLiteのオープンソースの拡張機能で、データベースを暗号化して保存できる便利なツールとなっています。

今回は、このSQLCipherを使ったAndroidアプリをSQLiteOpenHelperからRoomに移行した際にハマったポイントを紹介しようと思います。

SQLiteOpenHelperを利用したコード

SQLiteOpenHelperを利用したコードは以下のようにimportするSQLiteOpenHelperをsqlcipherのものにするだけで簡単に利用できます。

import net.sqlcipher.Cursor;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteOpenHelper;

public class PasswordMemoOpenHelper extends SQLiteOpenHelper {
    public PasswordMemoOpenHelper(Context context) {
        super(context, "PasswordMemoDB", null, 3);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table passworddata("
                + "id long not null,"
                + "title text default '',"
                + "account text default '',"
                + "password text default '',"
                + "url text default '',"
                + "group_id long default 1,"
                + "memo text default '',"
                + "inputdate text default ''"
                + ");");
    }
}

上記コードをRoomを利用したコードに変換していく際に、ハマったポイントを後述していきます。

ハマりポイント1:RoomではSQLCipher4を利用する必要がある

Roomを利用してSQLCipherを使うには、net.sqlcipher.database.SupportFactoryを利用する必要があり、そのために、SQLCipher3系からSQLCipher4系にアップグレードする必要がありました。
なので、以下のようにgradleを修正。

dependencies {
    implementation 'net.zetetic:android-database-sqlcipher:3.5.9@aar'
}

dependencies {
    implementation 'net.zetetic:android-database-sqlcipher:4.4.2'
}

RoomDatabaseを利用したDB生成処理は、SupportFactoryを利用するように以下のように修正。

@Database(entities = [PasswordEntity::class, GroupEntity::class], version = 4, exportSchema = false)
abstract class PasswordMemoRoomDatabase : RoomDatabase() {
    companion object {
        @Volatile
        private var INSTANCE: PasswordMemoRoomDatabase? = null

        fun getDatabase(
            context: Context,
        ): PasswordMemoRoomDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    PasswordMemoRoomDatabase::class.java,
                    "PasswordMemoDB"
                ).allowMainThreadQueries()
                    .fallbackToDestructiveMigration()
                    .openHelperFactory(SupportFactory(SQLiteDatabase.getBytes(context.getString(R.string.db_secret_key).toCharArray())))
                    .build()
                INSTANCE = instance
                instance
            }
        }
    }
}

ハマりポイント2:SQLCipher3系のDB読み込みエラー

上記のように修正後、SQLCipher3系で作られたDBを読み込むと以下のようなExceptionが発生。

net.sqlcipher.database.SQLiteException: file is not a database: , while compiling: select count(*) from sqlite_master;

どうやら、3系から4系に移行する場合PRAGMA cipher_migrateを設定する必要があるらしく、公式サイトでは

このような案内がされていました。
で、実装方法について色々調べたところ、こちらの記事に出会しました。

SQLiteDatabaseHookを利用してPRAGMA cipher_migrateを設定することは分かったのですが、SupportFactoryを利用している場合の実装方法が分からんって思っていたら、

こちらの記事にある通り、SupportFactoryはオーバーロードされており、第二引数でSQLiteDatabaseHookを渡せばいいことが分かりました。

Roomを利用したコード

上記のハマりポイントを解決した、Roomを利用したコードは以下となります。

import androidx.room.Room
import androidx.room.RoomDatabase
import net.sqlcipher.database.SQLiteDatabase
import net.sqlcipher.database.SQLiteDatabaseHook
import net.sqlcipher.database.SupportFactory

@Database(entities = [PasswordEntity::class, GroupEntity::class], version = 4, exportSchema = false)
abstract class PasswordMemoRoomDatabase : RoomDatabase() {
    companion object {
        @Volatile
        private var INSTANCE: PasswordMemoRoomDatabase? = null

        fun getDatabase(
            context: Context,
        ): PasswordMemoRoomDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    PasswordMemoRoomDatabase::class.java,
                    "PasswordMemoDB"
                ).allowMainThreadQueries()
                    .fallbackToDestructiveMigration()
                    .openHelperFactory(SupportFactory(SQLiteDatabase.getBytes(context.getString(R.string.db_secret_key).toCharArray()), object : SQLiteDatabaseHook {
                        override fun preKey(database: SQLiteDatabase?) {}

                        override fun postKey(database: SQLiteDatabase?) {
                            val cursor = database?.rawQuery("PRAGMA cipher_migrate", null)
                            var migrationOccurred = false
                            if (cursor?.count == 1) {
                                cursor.moveToFirst()
                                val selection: String = cursor.getString(0)
                                migrationOccurred = selection == "0"
                                Log.d("selection", selection)
                            }
                            cursor?.close()
                            Log.d("migrationOccurred:", migrationOccurred.toString())
                        }

                    }))
                    .build()
                INSTANCE = instance
                instance
            }
        }
    }
}

さいごに

まだアップデートに向けて開発途中ですが、GitHubでコードを公開していますのでぜひ参考にしてみて下さい。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?