Android开发者对SharedPreferences一定不陌生——这个从API Level 1就存在的轻量级存储方案,凭借其简单的键值对接口和自动持久化特性,成为无数App存储配置信息的首选。但2023年Google I/O大会上,Android团队正式宣布将SharedPreferences标记为弃用状态(deprecated),这记警钟让开发者不得不重新审视本地数据存储的安全架构。
我在金融类App开发中深刻体会到:传统的SharedPreferences存在三个致命缺陷:
而DataStore+Android Keystore的组合拳恰好能解决这些问题:
DataStore分为两种实现方式:
以Preferences DataStore为例,其核心改进在于:
kotlin复制// 传统SharedPreferences写入
sharedPref.edit().putString("api_key", "12345").apply()
// DataStore写入
context.dataStore.edit { prefs ->
prefs[stringPreferencesKey("api_key")] = "12345"
}
表面看只是API变化,实则暗藏玄机:
Android 6.0引入的KeyGenParameterSpec是关键所在。我们来看一个典型的AES密钥生成配置:
kotlin复制KeyGenParameterSpec.Builder(
"my_key_alias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
setBlockModes(KeyProperties.BLOCK_MODE_GCM)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
setUserAuthenticationRequired(true)
setUserAuthenticationValidityDurationSeconds(30)
setKeySize(256)
// 关键设置!声明密钥不可导出
setIsStrongBoxBacked(true) // 优先使用StrongBox安全芯片
}.build()
这段配置意味着:
在build.gradle中需要添加:
gradle复制// DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0"
// 安全加密
implementation "androidx.security:security-crypto:1.1.0-alpha06"
implementation "androidx.security:security-identity-credential:1.0.0-alpha03"
注意:security-crypto库需要Android 6.0+(API 23),但实际生产环境建议最低版本设为Android 9.0(API 28),因为:
setUnlockedDeviceRequired可防止锁屏状态下访问密钥建议采用分层密钥架构:
这种设计的好处是:
具体实现代码:
kotlin复制class SecureDataStore(private val context: Context) {
private val dataStore = context.createDataStore(name = "secure_prefs")
// 主密钥别名,建议按业务模块划分
private val masterKeyAlias = "user_auth_master_key"
private suspend fun getOrCreateMasterKey(): SecretKey {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
return if (keyStore.containsAlias(masterKeyAlias)) {
(keyStore.getEntry(masterKeyAlias, null) as KeyStore.SecretKeyEntry).secretKey
} else {
createMasterKey()
}
}
private fun createMasterKey(): SecretKey {
return KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
).apply {
init(
KeyGenParameterSpec.Builder(
masterKeyAlias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setKeySize(256)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(30)
.setIsStrongBoxBacked(true)
.build()
)
}.generateKey()
}
}
完整的数据存储流程应包含:
kotlin复制suspend fun <T> securePut(
key: Preferences.Key<T>,
value: T,
serializer: (T) -> ByteArray
) {
val masterKey = getOrCreateMasterKey()
val dek = generateAesKey() // 随机生成256位AES密钥
// 加密DEK得到KEK
val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply {
init(Cipher.ENCRYPT_MODE, masterKey)
}
val kek = cipher.doFinal(dek.encoded)
// 加密实际数据
val dataCipher = Cipher.getInstance("AES/GCM/NoPadding").apply {
init(Cipher.ENCRYPT_MODE, dek, GCMParameterSpec(128, cipher.iv))
}
val encryptedData = dataCipher.doFinal(serializer(value))
// 存储组合数据:IV + KEK + 加密数据
dataStore.edit { prefs ->
prefs[bytesPreferencesKey("${key.name}_iv")] = cipher.iv
prefs[bytesPreferencesKey("${key.name}_kek")] = kek
prefs[bytesPreferencesKey(key.name)] = encryptedData
}
}
在Pixel 6 Pro(Android 13)上的测试数据:
| 操作类型 | SharedPreferences | DataStore(无加密) | DataStore+Keystore |
|---|---|---|---|
| 写入100次 | 48ms | 62ms | 380ms |
| 读取100次 | 12ms | 18ms | 210ms |
| 首次加载 | 8ms | 15ms | 650ms |
优化建议:
dataStore.edit { }一次性提交多个修改对于API 23以下的设备,推荐降级方案:
kotlin复制fun getEncryptedSharedPreferences(context: Context): SharedPreferences {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
EncryptedSharedPreferences.create(
"secure_prefs",
MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
} else {
// 注意:此方案安全性较低,仅作兼容使用
context.getSharedPreferences("fallback_prefs", Context.MODE_PRIVATE)
}
}
即使使用Keystore,运行时内存中仍可能暴露密钥。建议:
kotlin复制fun wipeBytes(bytes: ByteArray) {
bytes.fill(0)
System.gc()
}
结合BiometricPrompt实现双重保护:
kotlin复制private suspend fun <T> withBiometricAuth(action: suspend () -> T): T {
return suspendCoroutine { cont ->
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("需要验证")
.setSubtitle("请进行生物识别以访问安全数据")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
.build()
val biometricPrompt = BiometricPrompt(
context as FragmentActivity,
ContextCompat.getMainExecutor(context),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
lifecycleScope.launch {
cont.resumeWith(runCatching { action() })
}
}
}
)
biometricPrompt.authenticate(promptInfo)
}
}
建议的安全策略组合:
实现示例:
kotlin复制suspend fun rotateMasterKey() {
val oldKey = getMasterKey()
val newKey = createMasterKey("new_master_key_${System.currentTimeMillis()}")
// 重新加密所有KEK
dataStore.data.first().asMap().forEach { (key, _) ->
if (key.name.endsWith("_kek")) {
val dek = decryptWithKey(oldKey, getKek(key))
val newKek = encryptWithKey(newKey, dek)
updateKek(key, newKek)
}
}
// 更新密钥别名
KeyStore.getInstance("AndroidKeyStore").apply {
deleteEntry(oldKey.alias)
}
}
现象:KeyPermanentlyInvalidatedException
原因:
解决方案:
kotlin复制try {
// 尝试使用现有密钥
} catch (e: KeyPermanentlyInvalidatedException) {
// 1. 删除旧密钥
KeyStore.getInstance("AndroidKeyStore").apply {
deleteEntry(masterKeyAlias)
}
// 2. 创建新密钥
val newKey = createMasterKey()
// 3. 迁移数据(需要备份方案)
migrateData(oldKey, newKey)
}
现象:StrongBoxUnavailableException
排查步骤:
kotlin复制val hasStrongBox = context.packageManager.hasSystemFeature(
PackageManager.FEATURE_STRONGBOX_KEYSTORE
)
需求场景:ContentProvider需要访问加密数据
安全方案:
关键代码:
kotlin复制// 主进程
val parcelableDek = ParcelableSecretKey(dek).apply {
encryptWithProcessKey()
}
// ContentProvider进程
val receivedDek = parcelableDek.decryptWithProcessKey()
val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply {
init(Cipher.DECRYPT_MODE, receivedDek, GCMParameterSpec(128, iv))
}
建议记录的关键事件:
示例日志结构:
json复制{
"timestamp": "2023-07-20T14:30:00Z",
"event_type": "key_rotation",
"key_alias": "user_auth_master_key_v2",
"device_secure": true,
"strongbox_used": true,
"execution_time_ms": 342
}
结合Crashlytics等工具的关键配置:
kotlin复制private val crashlytics = FirebaseCrashlytics.getInstance()
suspend fun <T> withSecureLogging(block: suspend () -> T): T {
try {
return block()
} catch (e: SecurityException) {
crashlytics.log("Security operation failed: ${e.message}")
crashlytics.recordException(
FingerprintException(
"SECURE_STORAGE_FAILURE",
e.stackTraceToString()
)
)
throw e
}
}
使用AndroidX Test进行安全测试:
kotlin复制@RunWith(AndroidJUnit4::class)
class SecureDataStoreTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
@Test
fun testKeyRotation() = runBlocking {
val store = SecureDataStore(context)
val testData = "敏感数据".toByteArray()
// 初始存储
store.securePut("test_key", testData)
// 模拟密钥失效
KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
deleteEntry("user_auth_master_key")
}
// 验证自动恢复
val retrieved = store.secureGet("test_key")
assertEquals(testData.decodeToString(), retrieved.decodeToString())
}
}
使用MobSF进行安全扫描时,重点关注:
从SharedPreferences迁移的建议步骤:
并行运行阶段(1-2个版本周期)
数据迁移阶段
kotlin复制suspend fun migrateLegacyData() {
val oldPrefs = context.getSharedPreferences("legacy", Context.MODE_PRIVATE)
oldPrefs.all.forEach { (key, value) ->
when (value) {
is String -> dataStore.edit { it[stringPreferencesKey(key)] = value }
is Int -> dataStore.edit { it[intPreferencesKey(key)] = value }
// 其他类型处理...
}
}
}
清理阶段
针对企业级应用的实现:
kotlin复制fun getProfileSpecificStore(userId: String): DataStore<Preferences> {
val masterKeyAlias = "profile_${userId}_master_key"
// 每个用户独立的密钥和数据存储
}
安全同步架构设计:
存储助记词的特殊处理:
kotlin复制fun storeMnemonic(phrase: List<String>) {
// 1. 将助记词转换为BIP-39种子
val seed = MnemonicCode.toSeed(phrase, "")
// 2. 使用分段加密
val chunkSize = 32 // 匹配AES块大小
seed.toList().chunked(chunkSize).forEachIndexed { i, chunk ->
securePut("mnemonic_chunk_$i", chunk.toByteArray())
}
}