LoginSignup
8
5

More than 1 year has passed since last update.

既存アプリにおけるEncryptedSharedPreferencesへの移行方法

Last updated at Posted at 2021-12-16

本記事は、Android Advent Calendar 2021 16日目の記事です。

はじめに:EncryptedSharedPreferencesとは

Android Developers - データをより安全に取り扱う

SharedPreferences クラスをラップし、2 つの方式を使用してキーと値を自動的に暗号化します。
- キーは、暗号化された状態でも検索できるように、決定性暗号化アルゴリズムを使用して暗号化されます。
- 値は、AES-256 GCM を使用して暗号化されます。これは非決定性です。

2021年4月にStable版ver1.0.0がリリースされた、Jetpackのライブラリです。
このライブラリを用いると、煩雑なコードなしで暗号化したSharedPreferencesのデータを読み書きできます。

リリースしているアプリでこのライブラリを使うには、既に暗号化して保存しているデータを復号&再度暗号化して保存する処理が必要になります。
この記事では、その具体的な処理について解説します。

1. 導入

Android Developersのページを参考に、ライブラリの依存関係を追加します。

app/build.gradle
dependencies {
    implementation "androidx.security:security-crypto:1.0.0"
}

2. 初期化処理

SharedPreferencesの操作を行うクラスで、暗号化用のeditorを取得します。
アプリのローンチ時等であれば、encryptedPreferencesNameはそのまま context.packageName を用いて問題ありませんが、既に同じ名前のファイルにデータが保存されている場合は、適当なsuffixを付ける必要があります。

    private val encryptedPreferencesName = context.packageName + "_encrypted_preferences"
    private val prefs: SharedPreferences = EncryptedSharedPreferences.create(
        encryptedPreferencesName,
        MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
    private val editor = prefs.edit()

また、ライブラリ導入前の既存のSharedPreferencesアクセス用のeditorも取得しておきます。

    private val unEncryptedPrefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
    private val unEncryptedEditor = unEncryptedPrefs.edit()

3. データ移管処理

データの移管処理はアプリの起動時に行う必要があるため、ApplicationクラスのonCreate()のタイミングで以下の処理を実行します。
ポイントごとに解説します。

    fun initialize(keys: List<Pair<String, Boolean>>) { // ポイント①:移管対象の項目を指定する
        val unEncryptedKeys: List<String> = unEncryptedPrefs.all.keys.map { it.toString() }

        keys.forEach {
            val key = it.first
            val isEncrypted = it.second

            unEncryptedKeys.find { it == key }?.let {
                unEncryptedPrefs.all.entries.find { it.key == key }?.let { entry ->
                    if (isEncrypted) {
                        putString(key, getStringWithDecryption(key))
                    } else {
                        when (val value = entry.value) {
                            is String -> putString(key, value)
                            is Int -> putInt(key, value)
                            is Long -> putLong(key, value)
                            is Boolean -> putBoolean(key, value)
                        }
                    }
                    unEncryptedEditor.remove(key).apply() // ポイント②:移管した項目を削除する
                }
            }
        }
    }

ポイント①:移管対象の項目を指定する

※この処理は必ずしも必要ではありません
自分の担当しているアプリの場合、使用しているSDKがデフォルトのSharedPreferencesを使ってデータの読み書きをしています。
このため、ここで明示的に「(SDKではなく)アプリ側で管理している項目」を移管対象として指定する必要がありました。
SDKの管理する項目まで一括でEncryptedSharedPreferencesで管理すると、SDK側で復号できずエラーが発生するためです。
こういった場合は、移管対象をリスト化してやるのがベターです。

    private val keys = listOf(
        Pair("testKey1", false), // キーと暗号化済みの項目か?をPairでまとめる
        Pair("testKey2", true)
    )

ポイント②:移管した項目を削除する

既存のSharedPreferencesからEncryptedSharedPreferencesへ移管し終わった項目については、既存のSharedPreferencesからデータを消しておきます。
こうすることで、次回アプリ起動時にはこの移管処理が走らなくなります。

まとめ

EncryptedSharedPreferencesを用いると、アプリ側でAndroidKeyStoreManagerを用いることなく、キーと値を暗号化することができます。
これからローンチするアプリにおいては、最初からこの方式で読み書きを行えば良いですが、既存のアプリの場合は旧Verとの整合性を取る必要があるため、本記事に記載したような移管処理が必要になります。
実務においてはこのようなケースが大半と思いますので、どなたかのお役に立てれば幸いです :tada:

参考記事

8
5
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
8
5