Upgrade to Pro — share decks privately, control downloads, hide ads and more …

EncryptedSharedPreferences が deprecated になっちゃった...

Avatar for Yuki Anzai Yuki Anzai
September 12, 2025

EncryptedSharedPreferences が deprecated になっちゃった!どうしよう! / Oh no! EncryptedSharedPreferences has been deprecated! What should I do?

DroidKaigi 2025
https://2025.droidkaigi.jp/timetable/939336/

EncryptedSharedPreferences など androidx.security:security-crypto ライブラリの全ての API が version 1.1.0-alpha07 で Deprecated になりました。
このセッションでは androidx.security:security-crypto がそもそもどういうライブラリなのか、どのような機能を提供しているのかを紹介します。
次に代替となるプラットフォーム API および Android Keystore などについて解説します。最後に androidx.security:security-crypto から代替方法へのマイグレーションを解説します。

Avatar for Yuki Anzai

Yuki Anzai

September 12, 2025
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. :VLJ"O[BJ w "OESPJE&OHJOFFS w (PPHMF%FWFMPQFS&YQFSUGPS"OESPJE w 9 UXJUUFS !ZBO[N w

    CMPHZBO[NCMPHTQPUDPN w גࣜձࣾ΢ϑΟΧ୅දऔక໾ w ࡳຈɾؔ౦ڌ఺ੜ׆ 2
  2. +FUQBDL4FDVSJUZ +FU4FD w "OESPJE9ϥΠϒϥϦ܈ͷҰͭ w BOESPJEYTFDVSJUZάϧʔϓ w TFDVSJUZDSZQUPEFQSFDBUFE w TFDVSJUZBQQBVUIFOUJDBUPS

    w TFDVSJUZBQQBVUIFOUJDBUPSUFTUJOH w TFDVSJUZJEFOUJUZDSFEFOUJBM 5 https://developer.android.com/jetpack/androidx/releases/security https://developer.android.com/privacy-and-security/cryptography#security-crypto-jetpack-deprecated
  3. TFDVSJUZDSZQUP w (PPHMF*0Ͱൃද w TUBCMF͸೥݄ w ηΩϡϦςΟͷϕετϓϥΫςΟεΛ؆ૉԽ͢ΔͨΊʹઃܭ͞ΕͨϥΠϒϥϦ w +$"ͷΑ͏ͳ௿Ϩϕϧ"1*͸ඇৗʹڧྗ͕ͩෳࡶͰޡ༻͠΍͍͢ w

    ઐ໳ՈʹΑͬͯݕূ͞Εͨ҆શͳσϑΥϧτઃఆΛఏڙ w NJO4EL7FSTJPOʢ"OESPJE.BSTINBMMPXʣ w ,FZ(FO1BSBNFUFS4QFDΛར༻͢ΔͨΊ 6 https://android-developers.googleblog.com/2019/05/whats-new-with-android-jetpack.html
  4. &ODSZQUFE4IBSFE1SFGFSFODFT w 4IBSFE1SFGFSFODFTΠϯλʔϑΣʔεΛ࣮૷͠ɺΩʔͱ஋ͷ྆ํΛࣗಈతʹ ҉߸Խ͢ΔΫϥε 8 val masterKey = MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)

    .build() val pref: SharedPreferences = EncryptedSharedPreferences.create( context, "secret_shared_prefs", masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) pref.edit { … }
  5. &ODSZQUFE'JMF w ϑΝΠϧͷಡΈॻ͖Λ҉߸Խ͢ΔϢʔςΟϦςΟ 9 val masterKey = MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build()

    val encryptedFile = EncryptedFile.Builder( context, File(context.filesDir, "secret_data"), masterKey, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB ).build() val encryptedOutputStream = encryptedFile.openFileOutput() val encryptedInputStream = encryptedFile.openFileInput()
  6. &ODSZQUFE4IBSFE1SFGFSFODFTͷத਎ 13 <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="__androidx_security_crypto_encrypted_prefs_key_keyset__">12a90104ce…</string>

    <string name="ARLtwLTPSwFHnBUu54Fx9pjOOKgg7HZnjv4E6WW8rxRxloI=">AYMKS+BZSF6B6Mmxan…</string> <string name="__androidx_security_crypto_encrypted_prefs_value_keyset__">1288015e…</string> </map> <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="__androidx_security_crypto_encrypted_prefs_key_keyset__">12a90104ce…</string> <string name=“ARLtwLTPSwFHnBUu54Fx9pjOOKgg7HZnjv4E6WW8rxRxloI=">AYMKS+C1JGpFULdDV…</string> <string name="__androidx_security_crypto_encrypted_prefs_value_keyset__">1288015e…</string> </map> ಉ͡ฏจΛ҉߸Խͯ͠΋ ҟͳΔ҉߸จʹͳΔ ৗʹಉ͡҉߸จʹͳΔ
  7. "OESPJE,FZTUPSF w ҉߸伴Λίϯςφ಺ʹ֨ೲ͠ɺσόΠε͔Βͷநग़Λࠔ೉ʹ͢Δ͜ͱΛ໨తͱ ͨ͠γεςϜϨϕϧͷαʔϏε w 伴ϚςϦΞϧͷඇΤΫεϙʔτੑ w ϋʔυ΢ΣΞϨϕϧͷอޢ w 5&&

    5SVTUFE&YFDVUJPO&OWJSPONFOU 04ຊମ͔Β΋ִ཭͞Εͨɺϓϩ ηοα಺ͷ҆શͳ࣮ߦ؀ڥ w 4USPOH#PYઐ༻ͷ଱λϯύʔੑΛ࣋ͭϋʔυ΢ΣΞνοϓ 20
  8. +BWB$SZQUPHSBQIZ"SDIJUFDUVSF +$" w +$"ͷΫϥεੜ੒࣌ʹϓϩόΠμͱͯ͠l"OESPJE,FZ4UPSFzΛࢦఆ͢Δ 22 val keyStore = KeyStore.getInstance("AndroidKeyStore") val

    keyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore" ) val keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore" ) https://developer.android.com/privacy-and-security/keystore#UsingAndroidKeyStore
  9. 4FDSFU,FZͷੜ੒ 23 private const val ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore" private const

    val KEY_STORE_ALIAS = "my_data_key_store_alias" private fun getOrCreateSecretKey(): SecretKey { val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER).apply { load(null) } if (keyStore.containsAlias(KEY_STORE_ALIAS)) { val entry = keyStore.getEntry(KEY_STORE_ALIAS, null) return (entry as KeyStore.SecretKeyEntry).secretKey } // 伴Λੜ੒ …
  10. 25 private const val ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore" private const val

    KEY_STORE_ALIAS = "my_data_key_store_alias" private fun getOrCreateSecretKey(): SecretKey { … // 伴Λੜ੒ val params = KeyGenParameterSpec.Builder( KEY_STORE_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setKeySize(256) .build() val keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE_PROVIDER ) keyGenerator.init(params) return keyGenerator.generateKey() }
  11. 29 private const val TRANSFORMATION = "AES/GCM/NoPadding" private const val

    IV_SIZE_BYTES = 12 private const val TAG_SIZE_BITS = 128 fun encrypt(plaintext: String): String { val cipher = Cipher.getInstance(TRANSFORMATION) cipher.init(Cipher.ENCRYPT_MODE, getOrCreateSecretKey()) val ciphertext = cipher.doFinal(plaintext.toByteArray(Charsets.UTF_8)) val ivAndCiphertext = cipher.iv + ciphertext // IV ͱ҉߸จΛ݁߹ return Base64.getEncoder().encodeToString(ivAndCiphertext) }
  12. 30 private const val TRANSFORMATION = "AES/GCM/NoPadding" private const val

    IV_SIZE_BYTES = 12 private const val TAG_SIZE_BITS = 128 fun decrypt(encryptedString: String): String { val cipher = Cipher.getInstance(TRANSFORMATION) val ivAndCiphertext = Base64.getDecoder().decode(encryptedString) // อଘ͓͍ͯͨ͠ IV Λ࢖༻ val spec = GCMParameterSpec(TAG_SIZE_BITS, ivAndCiphertext, 0, IV_SIZE_BYTES) cipher.init(Cipher.DECRYPT_MODE, getOrCreateSecretKey(), spec) val plaintextBytes = cipher.doFinal( /* input = */ ivAndCiphertext, /* inputOffset = */ IV_SIZE_BYTES, /* inputLen = */ ivAndCiphertext.size - IV_SIZE_BYTES ) return String(plaintextBytes, Charsets.UTF_8) }
  13. 35

  14. ΩʔηοτػೳΛ࢖Θͳ͍৔߹ 36 class TinkHelper { private val aead: Aead init

    { AeadConfig.register() if(!AndroidKeystore.hasKey(KEY_STORE_ALIAS)) { AndroidKeystore.generateNewAes256GcmKey(KEY_STORE_ALIAS) } aead = AndroidKeystore.getAead(KEY_STORE_ALIAS) } … }
  15. 37 class TinkHelper { private val aead: Aead … fun

    encrypt(plaintext: String): String { val plaintextBytes = plaintext.toByteArray(Charsets.UTF_8) val encrypted = aead.encrypt(plaintextBytes, ByteArray(0)) return Base64.getEncoder().encodeToString(encrypted) } fun decrypt(encryptedString: String): String { val encrypted = Base64.getDecoder().decode(encryptedString) val plaintextBytes = aead.decrypt(encrypted, ByteArray(0)) return String(plaintextBytes, Charsets.UTF_8) } }
  16. 41 class TinkHelperWithKeyset(context: Context) { private val aead: Aead private

    val keysetPref: SharedPreferences by lazy { … } init { … val masterKeyAead = AndroidKeystore.getAead(KEY_STORE_MASTER_KEY_ALIAS) val keysetHex = keysetPref.getString("keyset_name", null) val handle = if (keysetHex == null) { KeysetHandle.generateNew(KeyTemplates.get("AES256_GCM").toParameters()).also { val encryptedKeyset = TinkProtoKeysetFormat.serializeEncryptedKeyset(it, masterKeyAead, ByteArray(0)) keysetPref.edit { putString("keyset_name", Hex.encode(encryptedKeyset)) } } } else { LegacyKeysetSerialization.parseEncryptedKeyset( BinaryKeysetReader.withBytes(Hex.decode(keysetHex)), masterKeyAead, ByteArray(0) ) } aead = handle.getPrimitive<Aead>(RegistryConfiguration.get(), Aead::class.java) }
  17. ϚΠάϨʔγϣϯͷεςοϓ  ϚΠάϨʔγϣϯࡁΈ͔Ͳ͏͔νΣοΫ͢Δ  &ODSZQUFE4IBSFE1SFGFSFODFʹอଘ͍ͯ͠ΔσʔλΛϝϞϦ্ʹऔΓग़͢  ৽͘͠࡞੒ͨ͠"OESPJE,FZTUPSF $JQIFS ΋͘͠͸5JOL Ͱ҉߸Խ͢Δ

     ৽͘͠࡞੒ͨ͠%BUB4UPSF4IBSFE1SFGFSFODFTʹอଘ͢Δ  TFDVSJUZDSZQUPͷΩʔηοτ͕อଘ͞Ε͍ͯͨ4IBSFE1SFGFSFODFTϑΝ ΠϧΛ࡟আ͢Δ  "OESPJE,FZTUPSF಺ͷTFDVSJUZDSZQUPͷϚελʔΩʔΛ࡟আ͢Δ 44
  18. ϚελʔΩʔͱΩʔηοτͷ࡟আ 45 // delete SharedPreferences files deleteSharedPreferences("secret_shared_prefs") // delete MasterKey

    val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } if (keyStore.containsAlias(MasterKey.DEFAULT_MASTER_KEY_ALIAS)) { keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS) } EncryptedSharedPreferences.create( applicationContext, "secret_shared_prefs", … )
  19. όοΫΞοϓઃఆͷෆ଍ 50 όοΫΞοϓ Android Keystore SharedPreferences ͷ XML σόΠε A

    σόΠε B Android Keystore ❌ SharedPreferences ͷ XML ෳ߹ԽͰ͖ͳ͍
  20. όοΫΞοϓઃఆͷෆ଍ 51 όοΫΞοϓ Android Keystore SharedPreferences ͷ XML σόΠε A

    σόΠε B Android Keystore ❌ SharedPreferences ͷ XML ෳ߹ԽͰ͖ͳ͍ ❌
  21. 53 <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <!-- security-crypto ͷ৔߹ -->

    <exclude domain="sharedpref" path="secret_shared_prefs.xml"/> <exclude domain="sharedpref" path="__androidx_security_crypto_encrypted_file_pref__.xml"/> <!-- Tink + DataStore/SharedPreferences ͷ৔߹ --> <exclude domain="sharedpref" path="tink_pref_file.xml"/> <exclude domain="file" path="datastore/secret_data_store.preferences_pb" /> <exclude domain="sharedpref" path="my_secret_prefs.xml"/> <!-- Android Keystore + Cipher + DataStore/SharedPreferences ͷ৔߹ --> <exclude domain="file" path="datastore/secret_data_store.preferences_pb" /> <exclude domain="sharedpref" path="my_secret_prefs.xml"/> </cloud-backup> <device-transfer> … <cloud-backup> ಺ͱಉ͡ … </device-transfer> </data-extraction-rules> https://developer.android.com/identity/data/autobackup?utm_source=chatgpt.com#IncludingFiles data_extraction_rules.xml EncryptedSharedPreferences.create() Ͱࢦఆ໊ͨ͠લ EncryptedFile.Builder Ͱ࢖ΘΕ͍ͯΔσϑΥϧτ໊ withSharedPref()Ͱࢦఆͨ͠ ໊લʢΩʔηοτͷอଘઌʣ ҉߸Խͨ͠σʔλͷอଘઌ