EncryptedSharedPreferencesとは
Android Developersのサイトにある通り、「SharedPreferencesのKeyとValueを暗号化する実装」を提供するものです。
(Android Developers公式サイトより引用)
An implementation of SharedPreferences that encrypts keys and values.
今までのgetSharedPreferences()などで使用していたAPIの使い勝手はそのままに、暗号化する機能を提供してくれるので、手軽にセキュリティを高めることができます。
動作条件
・プロジェクトのminSdkVersionが23以上であること
これはandroidx.securityのライブラリのminSdkVersionが「23」に指定されているためです。
(APIレベル23未満では、EncryptedSharedPreferencesを使用するためのマスターキー情報がないので使用できない)
ライブラリの組み込み
build.gradleに以下のコードを追加します。
ライブラリのバージョンはその時の状況に応じて、適宜変更してください。
dependencies {
implementation "androidx.security:security-crypto:1.1.0-alpha03"
}
EncryptedSharedPreferencesを使ってみる
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
class MainActivity : AppCompatActivity() {
companion object {
const val PREF_NAME = "encrypted_prefs"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mainKey = MasterKey.Builder(applicationContext)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val prefs = EncryptedSharedPreferences.create(
applicationContext,
PREF_NAME,
mainKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
with (prefs.edit()) {
putString("foo", "bar")
apply()
}
val savedValue = prefs.getString("foo", "Nothing")
Toast.makeText(this, """the saved value of the key "foo" is $savedValue""", Toast.LENGTH_LONG).show()
}
}
上記のコードでアプリをビルドすると、EncryptedSharedPreferencesを使用して保存したデータ("bar"という文字列)がToastで表示されるはずです。
非常にお手軽に使えますね!
以下、上記のコードについて軽く触れておきます。
MasterKey
MasterKeyはandroidx.securityライブラリで使われるマスターキーをラップするクラスです。
このキー情報を使用して、EncryptedSharedPreferencesのKey/Valueの値を暗号化します。
また、このキーはAndroid KeyStoreに格納されるため安全に使用することができます。
以下のコードで、デフォルト設定のマスターキーを取得します。
val mainKey = MasterKey.Builder(applicationContext)
//master keyに使用するKeySchemeを指定. 本記事執筆時、AES256_GCMのみ定義されている
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
※開発者自身で、鍵生成のパラメータを指定することができますが、上記の(デフォルト)設定の使用が推奨されているようです。
EncryptedSharedPreferences.create
以下のコードで、EncryptedSharedPreferencesインスタンスを生成することができます。
val prefs = EncryptedSharedPreferences.create(
applicationContext,
PREF_NAME,
mainKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
それぞれの引数について。
- 第1引数 … Contextインスタンス。SharedPreferencesインスタンス生成のために使用
- 第2引数 … 保存先のファイル名
- 第3引数 … MasterKeyインスタンス
- 第4引数 … keyを暗号化するために参照されるPrefKeyEncryptionScheme情報。本記事執筆時、AES256_SIVのみが定義されている
- 第5引数 … valueを暗号化するために参照されるPrefValueEncryptionScheme情報。本記事執筆時、AES256_GCMのみが定義されている
保存されたデータを見てみよう
今回は保存するファイル名を「encrypted_prefs」としました。
/data/data/${アプリのパッケージ名}/shared_prefs/encrypted_prefs.xmlの中身を見てみましょう。
筆者の環境では以下のようになっていました。暗号化された文字列の値は環境により異なります。
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="__androidx_security_crypto_encrypted_prefs_key_keyset__">12a9010d7073b7e3c32bd1ce2184835ef4b9582676b80a4a8158f4cff8f8f3f852cd3191216e3cbb97aaa6ade6e49f8f8e58570dcec6dda3144ec2abfbdca143553576c2e60f13f6398f496540421768693f3ea9f1be169983e03c37dec39f1302364b0f710dbdeaea5c495efb15f26e8aab33a0fb882a8ae0f500238206a78776479a08b78f45a766b3fe06c2047c0479c70715bb7e99989a4feef0d74223552b6e5c292a5c06532efd3a661a4408f3bfa1a006123c0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e4165735369764b6579100118f3bfa1a0062001</string>
<string name="__androidx_security_crypto_encrypted_prefs_value_keyset__">128601101a6c6904bfc93b66d78e2f65ad240a53a2d203536980b93d5f85b74e695b94ca27333264d4d42f3c69b4549a773a876c44c7afe99345402ab376dd896501b297f2e84c00fb1c9842e094dc77ac9012fb4e543a4180d991c80b01077e8483cc8e1097b35ec8f1a2e92e9557ccc4f1c60891a1549b0ae42a1bb4f1601346b377dda1087dc56f1a4208e580f910123b0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e41657347636d4b6579100118e580f9102001</string>
<string name="AWQIX/Ot9q4upiyy+U2fP4RhilkKf/vT">AQIeQGVdBmATM9ubBcCayGfwulNZzpZbQJdADMe97l6cNJJ1XrPjoHa4vP0=</string>
</map>
"foo"というKeyが"AWQIX/Ot9q4upiyy+U2fP4RhilkKf/vT"という文字列に、
"bar"というValueが"AQIeQGVdBmATM9ubBcCayGfwulNZzpZbQJdADMe97l6cNJJ1XrPjoHa4vP0="という文字列に暗号化されています。
ユーザーにとって意味不明な文字列になっていますね。アプリの秘匿情報を使うのが楽になりました!
"__androidx_security_crypto_encrypted_prefs_key_keyset__"と"__androidx_security_crypto_encrypted_prefs_value_keyset__"は、それぞれKeyとValueの暗号化に使用するkeyset情報で、自動的に保存される情報のようです。
※ちなみに、EncryptedSharedPreferencesではなく、getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)を使ってデータを保存した場合は、以下のようになります。
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="foo">bar</string>
</map>
補足
プロジェクトのminSdkVersionが23未満の場合
プロジェクトのminSdkVersionが23未満の時は、以下の修正を加えればビルド自体はできます。
・AndroidManifest.xmlにoverrideLibraryの設定を追加
<!--以下のコードを追加-->
<uses-sdk tools:overrideLibrary="androidx.security"/>
・端末のOSバージョンによって処理を分岐
if (Build.VERSION.SDK_INT >= 23) {
APIレベルが23以上の時のみ、処理を実施
}
※ただし、後述のように、このような実装を行うメリットはあまりないでしょう。
最後に
-
EncryptedSharedPreferencesは、今までのgetSharedPreferences()による共有設定の編集と同じようなAPIで、KeyValuePairデータを端末に安全に保存できる
-
プロジェクトのminSdkVersionが23以上の新規開発では、getSharedPreferences()ではなく、EncryptedSharedPreferencesの使用を積極的に検討しよう
-
プロジェクトのminSdkVersionを23以上にあげられない場合、素直に既存のSharedPreferencesを使い回す(必要に応じて、KeyやValueを自前で暗号化)なり、他の暗号化ライブラリの使用を検討した方が良いでしょう。
=> SharedPreferencesを使って保存される情報の暗号化有無がOSバージョンによって変わるのは好ましくない。暗号化するなら、全ての端末を対象とするのが基本。また、OSバージョンにより暗号化の実装を変えるのは無駄に複雑。 -
すでにリリースされているアプリで、EncryptedSharedPreferences以外によるデータ保存処理を行なっている場合、EncryptedSharedPreferencesに移行すべきかどうかは、アプリの特性やプロジェクトの状況に応じて様々なので、それぞれで考えよう