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将数据保存在应用程序目录中的XML文件中(/data/data/package/shared_prefs/)。文件未加密 — 通过root权限访问设备或分析备份时,所有数据都可以作为普通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使用两级加密方案:主密钥(Master Key)存储在Android Keystore中,数据加密使用派生密钥。这是Keystore保护和对称加密性能的结合。
对于值,使用AES-256 GCM(Galois/Counter模式)— 认证加密模式(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接受五个参数:上下文、文件名、主密钥、密钥加密方案和值加密方案。方案的选择影响性能和保护级别。
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的初始化只进行一次,通常在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之间的选择取决于存储数据的类型。对于界面设置(主题、语言、排序),普通SharedPreferences就足够了。对于敏感信息(令牌、密码、密钥),EncryptedSharedPreferences是必需的。
EncryptedSharedPreferences由于加密操作比普通版本慢。写入一个字符串值大约需要5-15毫秒(取决于数据大小和硬件AES加速)。读取 — 2-5毫秒。对于大多数应用程序来说,这是不可察觉的,但在批处理操作(迁移、恢复)中,最好使用apply()而不是commit()。
普通SharedPreferences不提供任何加密保护:XML文件可以被任何具有root权限的进程或通过adb backup读取。EncryptedSharedPreferences在应用程序级别加密数据,主密钥存储在Android Keystore中,具有硬件保护(StrongBox)的可能性。
| 特性 | 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-200 KB)。对于大数据,使用带有SQLCipher的Room或通过来自同一AndroidX Security库的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+)。
是的,刷新令牌是主要使用场景之一。AES-256 GCM加密、Keystore中的主密钥、生物识别保护 — 对OAuth令牌来说足够。对于生命周期短的访问令牌也适用,尽管有些团队更喜欢将其存储在内存中。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。