使用 Jetpack Security 在 Android 上進行數據加密

做者 / Jon Markoff, Staff Developer Advocate, Android Securityhtml

您是否嘗試過對應用中的數據進行加密?做爲開發者,您想要保護數據安全,並確保數據掌握在其合理使用者的手中。可是,大多數 Android 開發者沒有專門的安全團隊來幫助他們正確地加密應用數據。就算經過網絡來搜索如何加密數據,您獲得的答案也可能已通過時好幾年了,找到的示例也難以保證準確性。android

Jetpack Security (JetSec) 加密庫爲 Files 和 SharedPreferences 對象的加密操做提供了抽象支持。該庫使用了安全且運用普遍的 密碼學原語 (cryptographic primitives),強化了 AndroidKeyStore 的使用。使用 EncryptedFile 和 EncryptedSharedPreferences 可讓您在本地保護可能包含敏感數據、API 密鑰、OAuth 令牌和其餘類型機密信息的文件。git

從 5.0 開始,Android 會默認 對用戶數據分區的內容進行加密,那您爲何還須要加密應用中的數據呢?這是由於在某些場合中,您可能須要額外的保護。若是您的應用使用 共享存儲 (shared storage),則應該對數據進行加密。若是您的應用處理敏感信息,包括但不限於我的身份可識別信息 (Personally Identifiable Information, PII)、健康記錄、財務信息或企業數據,那麼您的應用應該對其主目錄中的數據進行加密。若是可能,咱們建議您將此類信息與生物驗證操做綁定,以提供額外的保護。github

Jetpack Security 基於 Tink,而 Tink 是 Google 的一個開源並支持跨平臺的安全項目。若是您須要常規加密、混合加密或相似的安全措施,那麼 Tink 可能適用於您的項目。Jetpack Security 的數據結構與 Tink 徹底兼容。安全

密鑰生成

在開始加密數據以前,首先要了解您的加密密鑰是如何被保護的。Jetpack Security 使用一個主密鑰 (master key) 對全部的子密鑰 (subkey) 進行加密,子密鑰則被用於每一個加密操做。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,選擇適合您需求的選項。針對設備被 root 或遭到篡改的狀況,帶有 BiometricPrompt 生物驗證步驟的限時密鑰能夠提供更高級別的保護。app

重要選項:ide

  • userAuthenticationRequired()userAuthenticationValiditySeconds() 能夠用來建立限時密鑰。限時密鑰須要經過 BiometricPrompt 得到受權,才能對對稱密鑰進行加密和解密。
  • unlockedDeviceRequired() 能夠設置一個標誌,用於確保在設備未解鎖時不會發生密鑰訪問。該開關值在 Android 9 及更高版本上可用。
  • 使用 setIsStrongBoxBacked(),便可在更強大的獨立芯片上運行加密操做。這會對性能帶來輕微的影響,但更加安全。此功能在運行 Android 9 或更高版本的某些設備上可用。

注意: 若是您的應用須要在後臺加密數據,則不該使用限時密鑰或要求設備處於解鎖狀態,由於若是沒有用戶在場,您的操做將沒法完成。性能

// 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 值爲 true
  • userAuthenticationValiditySeconds > 0

在用戶進行驗證後,將基於有效秒數字段中給出時長解鎖密鑰。AndroidKeystore 沒有用於查詢密鑰設置的 API,所以您的應用必須本身記錄這些設置。您應該在展現受權界面的 Activity 的 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 對象用於寫入。咱們使用遵循 OAE2 定義的 Streaming AHEAD 對文件進行加密。數據被分爲多個區塊,並使用 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

若是您的應用須要保存鍵值對 (例如 API 密鑰),JetSec 提供了 EncryptedSharedPreferences 類,該類使用的是您所熟知的 SharedPreferences 接口。

鍵和值均會被加密。鍵使用能提供肯定性密文的 AES256-SIV-CMAC 進行加密;值則使用 AES256-GCM 進行加密,並綁定到加密的鍵。該方案容許對機要數據進行安全加密,同時仍然便於查詢。

EncryptedSharedPreferences.create(
    "my_secret_prefs",
    advancedKeyAlias,
    applicationContext,
    PrefKeyEncryptionScheme.AES256_SIV,
    PrefValueEncryptionScheme.AES256_GCM
).edit {
    // Update secret values
}

更多資源

FileLocker 是咱們準備的一個示例應用,您能夠在 Android Security GitHub 示例頁面上找到它。這個應用很好地展現了應該如何使用 Jetpack Security 進行文件加密。

祝你們加密愉快!

相關文章
相關標籤/搜索