LoginSignup
5

More than 3 years have passed since last update.

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

Posted at

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を使ってみる

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

 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の中身を見てみましょう。
筆者の環境では以下のようになっていました。暗号化された文字列の値は環境により異なります。

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)を使ってデータを保存した場合は、以下のようになります。

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の設定を追加

AndroidManifes.xml
<!--以下のコードを追加-->
 <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に移行すべきかどうかは、アプリの特性やプロジェクトの状況に応じて様々なので、それぞれで考えよう

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
5