Android 官方推荐使用 Jetpack Security 进行数据加密
作为一名开发人员,您希望确保数据的安全,并使其掌握在打算使用的一方手中。但是,如果你像大多数Android开发者一样,你没有专门的安全团队来帮助正确加密你的应用程序的数据。通过搜索Web来了解如何加密数据,您可能会得到一些已经过时好几年的答案,并提供错误的示例。
Jetpack Security(JetSec)加密库对Files和SharedPreferences读写进行加密解密处理。该库促进了AndroidKeyStore的使用,同时使用了安全且众所周知的加密原语。使用EncryptedFile和EncryptedSharedPreferences可让您本地保护可能包含敏感数据,API密钥,OAuth令牌和其他类型机密的文件
Jetpack Security基于Tink,它是Google的一个开源,跨平台安全项目。如果您需要常规加密、混合加密或类似的加密,Tink可能是合适的。Jetpack Security数据结构与Tink完全兼容。
密钥生成
Jetpack Security使用一个主密钥,该主密钥对用于每个加密操作的所有子密钥进行加密。JetSec在MasterKeys类中提供了推荐的默认主密钥。此类使用基本的AES256-GCM密钥,该密钥已生成并存储在AndroidKeyStore中。AndroidKeyStore是一个在TEE或StrongBox中存储加密密钥的容器,这使得它们很难提取。子项存储在可配置的SharedPreferences对象中。
首先,我们使用Jetpack Security中的AES256_GCM_SPEC规范,推荐用于一般用例。AES256-GCM是对称的,在现代设备上通常速度很快。
val keyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
对于需要更多配置或处理非常敏感数据的应用,建议构建您的KeyGenParameterSpec
,选择适合您使用的选项。带有BiometricPrompt的时限密钥可以提供更高级别的保护,以防止设备被盗用。
重要选项:
userAuthenticationRequired()
和userAuthenticationValiditySeconds()
用于创建有时间限制的密钥。限时密钥需要BiometricPrompt
用于对称密钥的加密和解密的授权。unlockedDeviceRequired()
设置一个标志,该标志有助于确保在设备未解锁的情况下不会发生密钥访问。此标志在Android Pie及更高版本中可用。- 使用
setIsStrongBoxBacked()
,可以在功能更强大的单独芯片上运行加密操作。这对性能有轻微影响,但更安全。它可以在一些运行Android Pie或更高版本的设备上使用。
注意:如果您的应用需要在后台加密数据,你不应该使用有时间限制的密钥或要求设备解锁,因为如果没有用户在场,你将无法完成这一任务。
// Custom Advanced Master Key
val advancedSpec = KeyGenParameterSpec.Builder(
"master_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
setBlockModes(KeyProperties.BLOCK_MODE_GCM)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
setKeySize(256)
setUserAuthenticationRequired(true)
setUserAuthenticationValidityDurationSeconds(15) // must be larger than 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setUnlockedDeviceRequired(true)
setIsStrongBoxBacked(true)
}
}.build()
val advancedKeyAlias = MasterKeys.getOrCreate(advancedSpec)
解锁有时间限制的密钥
如果您的密钥是使用以下选项创建的,则必须使用BiometricPrompt对设备进行授权:
- userAuthenticationRequired is true
- userAuthenticationValiditySeconds > 0
用户验证后,将在有效秒字段中设置的时间量内解锁密钥。AndroidKeystore没有用于查询键设置的API,因此您的应用必须跟踪这些设置。您应该使用onCreate()
将对话框呈现给用户的活动方法来构建BiometricPrompt实例。
用于解锁时间限制密钥的BiometricPrompt代码:
// Activity.onCreate
val promptInfo = PromptInfo.Builder()
.setTitle("Unlock?")
.setDescription("Would you like to unlock this key?")
.setDeviceCredentialAllowed(true)
.build()
val biometricPrompt = BiometricPrompt(
this, // Activity
ContextCompat.getMainExecutor(this),
authenticationCallback
)
private val authenticationCallback = object : AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
// Unlocked -- do work here.
}
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
// Handle error.
}
}
To use:
biometricPrompt.authenticate(promptInfo)
加密文件
Jetpack Security包含一个EncryptedFile类,它消除了加密文件数据的挑战。与File类似,EncryptedFile提供一个FileInputStream对象用于读取,一个FileOutputStream对象用于写入。使用流式AEAD(遵循OAE2定义)对文件进行加密。数据被分为多个块,并使用AES256-GCM进行加密,从而无法重新排序。
val secretFile = File(filesDir, "super_secret")
val encryptedFile = EncryptedFile.Builder(
secretFile,
applicationContext,
advancedKeyAlias,
FileEncryptionScheme.AES256_GCM_HKDF_4KB)
.setKeysetAlias("file_key") // optional
.setKeysetPrefName("secret_shared_prefs") // optional
.build()
encryptedFile.openFileOutput().use { outputStream ->
// Write data to your encrypted file
}
encryptedFile.openFileInput().use { inputStream ->
// Read data from your encrypted file
}
加密SharedPreferences
如果您的应用程序需要保存Key-value(例如 API 密钥),JetSec提供EncryptedSharedPreferences类,它使用与您习惯的SharedPreferences接口相同的接口。
Key和value均被加密。密钥使用提供确定性密文的AES256-SIV-CMAC加密。值使用AES256-GCM加密,并绑定到加密密钥。此方案允许对密钥数据进行安全加密,同时仍然允许查找。
EncryptedSharedPreferences.create(
"my_secret_prefs",
advancedKeyAlias,
applicationContext,
PrefKeyEncryptionScheme.AES256_SIV,
PrefValueEncryptionScheme.AES256_GCM
).edit {
// Update secret values
}
