SharedPreferencesを自前で難読化するのはもう古い?これからはEncryptedSharedPrefenrecesを使おう

## EncryptedSharedPreferencesとは

Android Developersのサイトにある通り、「SharedPreferencesのKeyとValueを暗号化する実装」を提供するものです。

（[Android Developers](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences)公式サイトより引用）
>An implementation of SharedPreferences that encrypts keys and values.


今までのgetSharedPreferences()などで使用していたAPIの使い勝手はそのままに、暗号化する機能を提供してくれるので、手軽にセキュリティを高めることができます。


## 動作条件
・プロジェクトのminSdkVersionが23以上であること

これはandroidx.securityのライブラリのminSdkVersionが「23」に指定されているためです。
(APIレベル23未満では、EncryptedSharedPreferencesを使用するためのマスターキー情報がないので使用できない）

## ライブラリの組み込み
build.gradleに以下のコードを追加します。
ライブラリのバージョンはその時の状況に応じて、適宜変更してください。

```gradle
dependencies {
    implementation "androidx.security:security-crypto:1.1.0-alpha03"
 }
```

## EncryptedSharedPreferencesを使ってみる

```kotlin:MainActivity.kt
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に格納されるため安全に使用することができます。
以下のコードで、デフォルト設定のマスターキーを取得します。

```kotlin
 val mainKey = MasterKey.Builder(applicationContext)
                //master keyに使用するKeySchemeを指定. 本記事執筆時、AES256_GCMのみ定義されている
                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                .build()
```

※開発者自身で、鍵生成のパラメータを指定することができますが、上記の（デフォルト）設定の使用が推奨されているようです。

### EncryptedSharedPreferences.create
以下のコードで、EncryptedSharedPreferencesインスタンスを生成することができます。

```kotlin
 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:encrypted_prefs.xml(EncryptedSharedPreferencesを使用した場合）
<?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:encrypted_prefs.xml(getSharedPreferencesを使用した場合）
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="foo">bar</string>
</map>
```


## 補足
### プロジェクトのminSdkVersionが23未満の場合
プロジェクトのminSdkVersionが23未満の時は、以下の修正を加えればビルド自体はできます。
　・AndroidManifest.xmlにoverrideLibraryの設定を追加

```xml:AndroidManifes.xml
<!--以下のコードを追加-->
 <uses-sdk tools:overrideLibrary="androidx.security"/>
```

　・端末のOSバージョンによって処理を分岐

```kotlin
if (Build.VERSION.SDK_INT >= 23) {
  APIレベルが２３以上の時のみ、処理を実施
}
```

※ただし、後述のように、このような実装を行うメリットはあまりないでしょう。


## 最後に
- EncryptedSharedPreferencesは、今までのgetSharedPreferences()による共有設定の編集と同じようなAPIで、KeyValuePairデータを端末に安全に保存できる

- プロジェクトのminSdkVersionが23以上の新規開発では、getSharedPreferences()ではなく、EncryptedSharedPreferencesの使用を積極的に検討しよう

- プロジェクトのminSdkVersionを23以上にあげられない場合、素直に既存のSharedPreferencesを使い回す（必要に応じて、KeyやValueを自前で暗号化）なり、他の暗号化ライブラリの使用を検討した方が良いでしょう。 
 => SharedPreferencesを使って保存される情報の暗号化有無がOSバージョンによって変わるのは好ましくない。暗号化するなら、全ての端末を対象とするのが基本。また、OSバージョンにより暗号化の実装を変えるのは無駄に複雑。

- すでにリリースされているアプリで、EncryptedSharedPreferences以外によるデータ保存処理を行なっている場合、EncryptedSharedPreferencesに移行すべきかどうかは、アプリの特性やプロジェクトの状況に応じて様々なので、それぞれで考えよう