本記事は、Android Advent Calendar 2021 16日目の記事です。
はじめに:EncryptedSharedPreferencesとは
Android Developers - データをより安全に取り扱う
SharedPreferences クラスをラップし、2 つの方式を使用してキーと値を自動的に暗号化します。
- キーは、暗号化された状態でも検索できるように、決定性暗号化アルゴリズムを使用して暗号化されます。
- 値は、AES-256 GCM を使用して暗号化されます。これは非決定性です。
2021年4月にStable版ver1.0.0がリリースされた、Jetpackのライブラリです。
このライブラリを用いると、煩雑なコードなしで暗号化したSharedPreferencesのデータを読み書きできます。
リリースしているアプリでこのライブラリを使うには、既に暗号化して保存しているデータを復号&再度暗号化して保存する処理が必要になります。
この記事では、その具体的な処理について解説します。
1. 導入
Android Developersのページを参考に、ライブラリの依存関係を追加します。
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との整合性を取る必要があるため、本記事に記載したような移管処理が必要になります。
実務においてはこのようなケースが大半と思いますので、どなたかのお役に立てれば幸いです