Jetpack Security 是什么?
Jetpack Security 是 Google I/O 2019 发布的安全组件库。Security构成简单,主要包含EncryptedFile
和EncryptedSharedPreferences
两个类,分别用来对File
和SharedPreferences
的读写进行加密解密处理。Security要求min SDK version 23
。
- 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。
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
EncryptedSharedPreferences
普通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中
使用EncryptedSharedPreferences
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val 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被加密保存
性能对比
SharedPreference TestCase
@RunWith(AndroidJUnit4::class)
class SharedPreferenceTest {
private lateinit var data: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
@Before
fun setup() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
data = appContext.getSharedPreferences("Sample", Context.MODE_PRIVATE)
editor = data.edit()
}
@Test
fun sharedPreference() {
for (i in 1..10000) {
editor.putInt("IntSave", i)
editor.apply()
val intSaved = data.getInt("IntSave", 1)
assertEquals(intSaved, i)
}
}
}
EncryptedSharedPreference TestCase
@RunWith(AndroidJUnit4::class)
class EncryptedSharedPreferenceTest {
private lateinit var data: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
@Before
fun setup() {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
data = EncryptedSharedPreferences
.create(
"Sample",
masterKeyAlias,
appContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
editor = data.edit()
}
@Test
fun encryptedSharedPreference() {
for (i in 1..10000) {
editor.putInt("IntSave", i)
editor.apply()
val intSaved = data.getInt("IntSave", 1)
Assert.assertEquals(intSaved, i)
}
}
}
性能比较 (ms) | EncryptedSharedPreferences | SharedPreferences |
---|---|---|
最慢 | 6212 | 567 |
平均 | 6119.3 | 514.3 |
最快 | 6028 | 426 |
使用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: ByteArrayOutputStream
val 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 INFORMATION
Log.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.
}