EncryptedSharedPreferences はAndroidX Securityライブラリのコンポーネントで、SharedPreferences APIを介して保存されたデータの透過的な暗号化を提供します。通常のSharedPreferencesではデータがプレーンなXMLファイルに保存されるのに対し、EncryptedSharedPreferencesはディスクに書き込む前にキーと値を自動的に暗号化します。Android Developersによると、このライブラリは値にAES-256 GCM、キーにAES-256 SIV(RFC 5297)を使用し、データの機密性と整合性を保証します。
重要なポイント
EncryptedSharedPreferences はandroidx.security.cryptoパッケージのクラスで、AndroidX Security 1.0.0(2019)で導入されました。SharedPreferencesインターフェースを実装していますが、すべての書き込み操作(putString、putInt、putBooleanなど)は事前にデータを暗号化し、読み取り操作はデータを復号化してから返します。
標準のSharedPreferencesは、アプリのディレクトリ(/data/data/package/shared_prefs/)にあるXMLファイルにデータを保存します。ファイルは暗号化されておらず、デバイスへのルートアクセスやバックアップ解析により、すべてのデータがプレーンなXMLとして読み取られます。認証トークン、APIキー、ユーザーの個人データが攻撃者にアクセス可能になります。
EncryptedSharedPreferencesはライブラリレベルでこの問題を解決します。データはディスクに書き込む前に暗号化され、読み取り時に復号化されます。開発者が手動で暗号化関数を呼び出す必要はなく、APIは通常のSharedPreferencesと同じです。
AndroidX Securityライブラリv1.0.0は2019年12月にリリースされました。EncryptedSharedPreferencesは、Cipher + SharedPreferencesによる手動暗号化の旧来の手法を置き換えました。現在の安定版は1.1.0-alpha06(2024)で、API 19+をサポートしています。このライブラリはJetpackの一部であり、追加の権限は必要ありません。
Google Security Blog(2024)によると、EncryptedSharedPreferencesはクラウド同期を必要としない機密性の高いアプリ設定を保存する推奨方法です。より複雑なシナリオでは、SQLCipher暗号化を使用したRoomが推奨されています。
EncryptedSharedPreferencesは2層の暗号化スキームを使用します。Master KeyはAndroid Keystoreに保存され、派生キーがデータ暗号化に使用されます。これにより、Keystoreの保護と対称暗号化のパフォーマンスを組み合わせています。
値にはAES-256 GCM(Galois/Counter Mode)が使用されます。これはデータの機密性と整合性を保証する認証暗号化モード(AEAD)です。キー(パラメータ名)にはAES-256 SIV(RFC 5297)が適用されます。これは内容を公開せずにキーを検索するために必要な決定論的暗号化です。
EncryptedSharedPreferencesの各ファイルには、暗号化されたキーと値のペアが含まれています。ファイル構造は、最初にメタデータ(バージョン、キー識別子)を含むヘッダー、次に暗号化されたエントリのリストです。ファイルは有効なXMLではなく、テキストエディタでは読み取れません。
MasterKeyクラスは、Android Keystoreに保存される256ビットのマスターキーの作成と管理を担当します。MasterKey.Builderを使用すると、ストレージタイプ(Keystoreまたはソフトウェア)、生体認証保護、キーの有効期間を設定できます。デフォルトでは、マスターキーはAndroid KeystoreでAES/GCM/NoPaddingアルゴリズムを使用して生成されます。
import androidx.security.crypto.MasterKey
import androidx.security.crypto.EncryptedSharedPreferences
fun getEncryptedPrefs() {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
EncryptedSharedPreferences.createは5つのパラメータを受け取ります。コンテキスト、ファイル名、マスターキー、キー暗号化スキーム、値暗号化スキームです。スキームの選択はパフォーマンスとセキュリティレベルに影響します。
AES256_SIV — 決定論的暗号化: 同じキーは常に同じ暗号文を生成します。これはキー検索(SharedPreferences.getX(key))に必要です。欠点: 攻撃者は繰り返される暗号文を照合して、どのキーが使用されているかを特定できます。AES256_SIV2 — 追加のランダム化を備えた改良版。
値にはAES256_GCMが使用されます。GCMは各値に12バイトのIV(初期化ベクトル)と16バイトの認証タグを追加します。これにより、機密性(誰も値を読めない)と認証(誰も検出されずに値を改ざんできない)が提供されます。
MasterKey.BuilderのsetUserAuthenticationRequired(true)メソッドは、Keystoreからマスターキーを取得する前に生体認証を要求します。これにより追加のレイヤーが追加されます。アプリがロック解除されたデバイスで実行されている場合でも、攻撃者はFace IDやTouch IDなしではEncryptedSharedPreferencesを読み取れません。
重要: setUserAuthenticationRequiredを使用する場合、ユーザーが生体認証を変更または削除すると、マスターキーは使用できなくなります。KeyPermanentlyInvalidatedExceptionを処理し、データ移行を伴う新しいマスターキーを作成する必要があります。
fun createBiometricKey(): MasterKey {
return MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.setUserAuthenticationRequired(true)
.setRequestStrongBoxBacked(true)
.build()
}
fun writeSecureToken(token: String) {
try {
prefs.edit().putString("auth_token", token).apply()
} catch (e: KeyPermanentlyInvalidatedException) {
// 生体認証が変更されました — キーを再作成する必要があります
}
}
Kotlinを使用したAndroidアプリでのEncryptedSharedPreferences統合の完全な例を見てみましょう。androidx.security:security-cryptoライブラリはGradle経由で追加されます。
build.gradle(app)ファイルに追加: implementation "androidx.security:security-crypto:1.1.0-alpha06"。Kotlinプロジェクトの場合は、kotlin-stdlibも必要です。MasterKeyの初期化は1回だけ行われ、通常はApplication.onCreateまたはDIコンテナを介して行われます。
EncryptedSharedPreferencesインスタンスを作成した後、APIは通常のSharedPreferencesと変わりません。edit()はEditorを返し、すべてのメソッド(putString、getString、putBoolean、getBoolean)は同様に動作します。唯一の違いは内部にあります。データは書き込み時に暗号化され、読み取り時に復号化されます。
class AuthRepository(context: Context) {
private val prefs = createEncryptedPrefs(context)
fun saveCredentials(login: String, password: String) {
prefs.edit()
.putString("login", login)
.putString("password", password)
.apply()
}
fun getToken(): String? {
return prefs.getString("auth_token", null)
}
fun clearAll() {
prefs.edit().clear().apply()
}
}
暗号化されていないSharedPreferencesからEncryptedSharedPreferencesに既存のデータを移行するには: 古いファイルからすべてのデータを読み取り、新しいEncryptedSharedPreferencesを作成し、すべてのデータを書き込み、古いファイルを削除します。Googleは組み込みのマイグレーターを提供していないため、開発者が手動で実装します。
SharedPreferencesとEncryptedSharedPreferencesの選択は、保存するデータの種類によって異なります。UI設定(テーマ、言語、並び順)には通常のSharedPreferencesで十分です。機密情報(トークン、パスワード、キー)にはEncryptedSharedPreferencesが必須です。
EncryptedSharedPreferencesは暗号化操作のため、通常よりも低速です。1つの文字列値の書き込みには約5〜15ミリ秒かかります(データサイズとAESハードウェアアクセラレーションに依存)。読み取りは2〜5ミリ秒です。ほとんどのアプリではこれは目立ちませんが、バッチ操作(移行、復元)ではcommit()ではなくapply()を使用してください。
通常のSharedPreferencesは暗号化による保護を一切提供しません。XMLファイルはルートアクセスを持つプロセスやadbバックアップを介して読み取られます。EncryptedSharedPreferencesはアプリケーションレベルでデータを暗号化し、マスターキーはオプションのハードウェア保護(StrongBox)付きでAndroid Keystoreに保存されます。
| 特性 | SharedPreferences | EncryptedSharedPreferences |
|---|---|---|
| 保存 | プレーンなXML | 暗号化されたバイナリファイル |
| 暗号化 | なし | AES-256 GCM + SIV |
| キー保護 | なし | Android Keystore + StrongBox |
| パフォーマンス | 0.1〜1ミリ秒 | 2〜15ミリ秒 |
| 推奨 | UI設定 | トークン、キー、PII |
EncryptedSharedPreferencesを使用して保存: OAuthリフレッシュトークン、外部サービス用のAPIキー、ユーザーのメールアドレスや電話番号、機密性の高いアプリ設定(PIN、認証フラグ)。EncryptedSharedPreferencesは生体認証データや大きなドキュメントの保存には適していません。代わりにEncryptedFileまたはSQLCipherを使用したRoomを使用してください。
一般的なルール: データ漏洩がユーザーやビジネスに害を及ぼす場合はEncryptedSharedPreferencesを使用します。データが外観的なもの(テーマ、言語、並び順)のみの場合は通常のSharedPreferences。EncryptedSharedPreferencesは最初から実装するのが理にかなっており、リファクタリングは不要です。既存のプロジェクトでの置き換えには移行と古い暗号化されていないデータの処理が必要です。
EncryptedSharedPreferencesはアプリ実行中のデータは保護せず、ディスク上のみであることに注意してください。攻撃者がプロセスメモリにアクセスできる場合、復号化されたデータが傍受される可能性があります。追加の保護として、難読化のためにProGuard/DexGuardを使用してください。
よくある質問
Jetpack DataStoreはSharedPreferencesのより現代的な代替手段で、FlowとKotlinコルーチンに基づいています。DataStoreはデフォルトではデータを暗号化しませんが、EncryptedSharedPreferencesと組み合わせたり、暗号化プロトコルを使用してProto DataStoreによる手動暗号化と併用したりできます。
推奨されません。EncryptedSharedPreferencesは少量のデータ(最大100〜200KB)向けに設計されています。より大きなデータには、同じAndroidX SecurityライブラリのSQLCipherを使用したRoomまたはEncryptedFileによるファイル暗号化を使用してください。
いいえ、自動的なスキーマ移行はありません。データ構造を変更する場合、開発者は古いKeyGenを介して古いデータを手動で読み取り、新しいKeyGenを介して書き込む必要があります。スキーマバージョンは別のパラメーターに保存することをお勧めします。
AndroidX Security 1.0.0はAPI 19+(Android KitKat)をサポートしています。バージョン1.1.0-alpha06もAPI 19+をサポートしています。StrongBoxにはAPI 28+とハードウェアサポートのあるデバイス(Google Pixel 3+、Samsung Galaxy S9+)が必要です。
はい、リフレッシュトークンは主要なユースケースの1つです。AES-256 GCM暗号化、Keystoreのマスターキー、生体認証保護はOAuthトークンに十分なレベルです。短命のアクセストークンにも適していますが、一部のチームはメモリに保存することを好みます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。