Author: fundroid_Fang Zhuo
Link: https://blog.csdn.net/vitaviva/article/details/104828195
What is Jetpack Security?
Jetpack Security is the security component library released at Google I/O 2019. The Security library is simple and mainly includes two classes: EncryptedFile and EncryptedSharedPreferences, which are used to encrypt and decrypt the reading and writing of File and SharedPreferences respectively. Security requires a minimum SDK version of 23.
Image Source: Google Developers
EncryptedFile encapsulates the logic of Google’s encryption library tink, providing FileInputStream and FileOutputStream for safer streaming reading and writing.
EncryptedSharedPreferences is a wrapper class for SharedPreferences that automatically encrypts key/value pairs in two ways:
-
Key encryption uses a deterministic encryption algorithm, allowing the key to be encrypted
-
Value encryption uses AES-256 GCM encryption, which is non-deterministic
The Security library key management is divided into two parts:
-
Key set
Contains one or more keys to encrypt file or SharedPreferences data, stored in SharedPreferences.
-
Master Key
Used to encrypt all key sets, stored in the Android Keystore system.
Using the wrapper class MasterKeys of Android Keystore, you can create a Master Key with just two lines.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPECval masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
1. Ordinary 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())
The key and value are stored in plain text in xml
2. Using 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())
The key and value are stored encrypted
3. Performance Comparison
3.1 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) } }}
3.2 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) } }}
The test results using pixel3 as shown above, the performance degrades by more than 10 times, but it is still good for an encryption library.
EncryptedFile
Write File
For example, write the string “MY SUPER SECRET INFORMATION” to a text file
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
Through EncryptedFile, you can output the plaintext “MY SUPER SECRET INFORMATION”;
Using only BufferedReader will output unreadable ciphertext (�]�}�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() // Output 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.}