[Android]SQLCipherをSQLiteOpenHelperからRoomに移行する

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

https://www.zetetic.net/sqlcipher/

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

# SQLiteOpenHelperを利用したコード
SQLiteOpenHelperを利用したコードは以下のようにimportするSQLiteOpenHelperをsqlcipherのものにするだけで簡単に利用できます。
```kotlin
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を利用したコードに変換していく際に、ハマったポイントを後述していきます。
# ハマりポイント１：RoomではSQLCipher4を利用する必要がある
Roomを利用してSQLCipherを使うには、`net.sqlcipher.database.SupportFactory`を利用する必要があり、そのために、SQLCipher3系からSQLCipher4系にアップグレードする必要がありました。
なので、以下のようにgradleを修正。
```gradle
dependencies {
    implementation 'net.zetetic:android-database-sqlcipher:3.5.9@aar'
}
```
↓
```gradle
dependencies {
    implementation 'net.zetetic:android-database-sqlcipher:4.4.2'
}
```
RoomDatabaseを利用したDB生成処理は、SupportFactoryを利用するように以下のように修正。
```kotlin
@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
            }
        }
    }
}
```
# ハマりポイント２：SQLCipher3系のDB読み込みエラー
上記のように修正後、SQLCipher3系で作られたDBを読み込むと以下のようなExceptionが発生。
```console
net.sqlcipher.database.SQLiteException: file is not a database: , while compiling: select count(*) from sqlite_master;
```

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

https://discuss.zetetic.net/t/upgrading-to-sqlcipher-4/3283

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

https://qiita.com/devWill/items/722462856c641c4557f8

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

https://discuss.zetetic.net/t/room-database-not-getting-encrypted/6187

こちらの記事にある通り、`SupportFactory`はオーバーロードされており、第二引数で`SQLiteDatabaseHook`を渡せばいいことが分かりました。
# Roomを利用したコード
上記のハマりポイントを解決した、Roomを利用したコードは以下となります。
```kotlin
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でコードを公開していますのでぜひ参考にしてみて下さい。

https://github.com/highcom/PasswordMemoPublic