Android官方推荐的安全组件:使用Jetpack Security为数据加密
作者:fundroid_方卓
链接:https://blog.csdn.net/vitaviva/article/details/104828195
Jetpack Security 是什么?
Jetpack Security 是 Google I/O 2019 发布的安全组件库。Security构成简单,主要包含EncryptedFile和EncryptedSharedPreferences两个类,分别用来对File和SharedPreferences的读写进行加密解密处理。Security要求min SDK version 23。

图片源:Google开发者
EncryptedFile 封装了Google的加密库tink的逻辑,提供FileInputStream和FileOutputStream,可以更安全的进行流的读写。
EncryptedSharedPreferences 是SharedPreferences包装类,通过两种方式自动加密键/值:
Key加密使用的是确定性的加密算法,使得秘钥可以被加密
Value加密使用AES-256 GCM加密,不确定加密
Security库秘钥管理分为两个部分:
秘钥集合(Key set)
包含一个或多个秘钥来加密文件或SharedPreferences数据,存储在SharedPreferences中。
主密钥(Master Key)
用来加密所有秘钥集合,存储在Android Keystore系统中
使用Android Keystore的包装类MasterKeys只用两行就可以制作Master Key。
= MasterKeys.AES256_GCM_SPECval masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
1,普通SharedPreferences
val data = getSharedPreferences("Sample", Context.MODE_PRIVATE)val editor = data.edit()editor.putInt("IntSave", 10)editor.apply()val intSaved = data.getInt("IntSave", 1)Log.d("IntSave", intSaved.toString())
key和value都被明文保存在xml中


2,使用EncryptedSharedPreferences
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPECval masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)val sharedPreferences = EncryptedSharedPreferences.create("Sample",masterKeyAlias,context,EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM)val editor = sharedPreferences.edit()editor.putInt("IntSave", 10)editor.apply()val intSaved = sharedPreferences.getInt("IntSave", 1)Log.d("IntSave", intSaved.toString())
key和value被加密保存


3,性能对比
3.1 SharedPreference TestCase
@RunWith(AndroidJUnit4::class)class SharedPreferenceTest {private lateinit var data: SharedPreferencesprivate lateinit var editor: SharedPreferences.Editor@Beforefun setup() {// Context of the app under test.val appContext = InstrumentationRegistry.getInstrumentation().targetContextdata = appContext.getSharedPreferences("Sample", Context.MODE_PRIVATE)editor = data.edit()}@Testfun sharedPreference() {for (i in 1..10000) {editor.putInt("IntSave", i)editor.apply()val intSaved = data.getInt("IntSave", 1)assertEquals(intSaved, i)}}}
3.2 EncryptedSharedPreference TestCase
@RunWith(AndroidJUnit4::class)class EncryptedSharedPreferenceTest {private lateinit var data: SharedPreferencesprivate lateinit var editor: SharedPreferences.Editorfun setup() {val appContext = InstrumentationRegistry.getInstrumentation().targetContextval keyGenParameterSpec = MasterKeys.AES256_GCM_SPECval masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)data = EncryptedSharedPreferences.create("Sample",masterKeyAlias,appContext,EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM)editor = data.edit()}fun encryptedSharedPreference() {for (i in 1..10000) {editor.putInt("IntSave", i)editor.apply()val intSaved = data.getInt("IntSave", 1)Assert.assertEquals(intSaved, i)}}}

使用pixel3的测试结果如上,性能上有10倍以上的劣化,但是作为加密库来说已经不错了。
EncryptedFile
Write File
例如向text文件中中写入 ”MY SUPER SECRET INFORMATION“字符串
val fileToWrite = "my_other_sensitive_data.txt"val encryptedFile = EncryptedFile.Builder(File(context.getFilesDir(), fileToWrite),context,masterKeyAlias,EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build()// Write to a file.try {val outputStream: FileOutputStream? = encryptedFile.openFileOutput()outputStream?.apply {write("MY SUPER SECRET INFORMATION".toByteArray(Charset.forName("UTF-8")))flush()close()}} catch (ex: IOException) {// Error occurred opening file for writing.}
Read File
通过EncryptedFile可以输出明文”MY SUPER SECRET INFORMATION“;
仅使用BufferedReader则会输出不可读的密文(�]�}�Wr<������q1Bv����B��|)��j_��>��uBLN#���Y�w���;�̴?�w��M���;�K�M�Ƕ�
val fileToRead = "my_sensitive_data.txt"lateinit var byteStream: ByteArrayOutputStreamval encryptedFile = EncryptedFile.Builder(File(context.getFilesDir(), fileToRead),context,masterKeyAlias,EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build()try {encryptedFile.openFileInput().use { fileInputStream ->try {val sb = StringBuilder()val br = BufferedReader(InputStreamReader(fileInputStream) as Reader?)br.readLine().forEach {sb.append(it)}br.close()// 输出 MY SUPER SECRET INFORMATIONLog.d("fileContents", sb.toString())} catch (ex: Exception) {// Error occurred opening raw file for reading.} finally {fileInputStream.close()}}} catch (ex: IOException) {// Error occurred opening encrypted file for reading.}
