+2

Mã hoá dữ liệu trên Android với Jetpack Security

Jetpack Security (JetSec) là thư viện được xây dựng từ Tink - dự án mã nguồn mở, bảo mật đa nền tảng của Google. Jetpack Security được sử dụng cho việc mã hoá File và SharedPreferences.

Sử dụng EncryptedFileEncryptedSharedPreferences cho phép chúng ta bảo vệ các tệp local chứa nhưngx thông tin nhạy cảm, API key, OAuth token và các thông tin bí mật khác.

Tạo Key

Trước khi chúng ta tiến hành mã hoá dữ liệu, ta cần hiểu cách mà các Key mã hóa sẽ được giữ an toàn. Jetpack Security sử dụng một khóa chính, nó mã hóa tất cả các khóa con được sử dụng cho mỗi hoạt động mã hoá riêng. JetSec cung cấp một khóa chính mặc định được đề xuất trong lớp MasterKeys. Lớp này sử dụng khóa AES256-GCM cơ bản, khoá này được tạo và lưu trữ trong AndroidKeyStore. AndroidKeyStore là một container lưu trữ các khóa mật mã trong TEE hoặc StrongBox, rất khó có thể giải nén chúng. Các khóa con được lưu trữ trong một SharedPreferences object mà có thể config được.

val keyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

Đối với các ứng dụng yêu cầu thêm config đặc biệt hoặc xử lý dữ liệu rất nhạy cảm, ta sẽ dùng đến KeyGenParameterSpec, chọn các option phù hợp với mỗi trường hợp sử dụng. Các khóa có giới hạn thời gian với BiometricPrompt giúp tăng thêm cấp độ bảo vệ chống lại các thiết bị đã bị root hoặc bị xâm phạm.

Các option:

  • userAuthenticationRequired()userAuthenticationValiditySeconds() có thể được sử dụng để tạo khóa giới hạn thời gian. Các khóa có giới hạn thời gian sẽ yêu cầu ủy quyền bằng BiometricPrompt cho cả mã hóa và giải mã.
  • unlockDeviceRequired() gắn thêm cờ này sẽ giúp đảm bảo việc truy cập khóa không thể xảy ra nếu thiết bị không được mở khóa (cờ này chỉ khả dụng từ Android Pie trở lên) .
  • setIsStrongBoxBacked(), chạy mã hoá trên một chip riêng biệt mạnh hơn. Nó sẽ có một chút ảnh hưởng đến hiệu suất, nhưng an toàn hơn (khả dụng từ Android Pie trở lên) .
// 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)

Unlock Key giới hạn thời gian

Ta sẽ phải sử dụng BiometricPrompt để cấp quyền cho thiết bị nếu khóa được tạo với các option:

  • userAuthenticationRequired giá trịtrue
  • userAuthenticationValiditySeconds > 0

Sau khi người dùng xác thực, khóa sẽ được mở khóa trong khoảng thời gian được đặt trong trường giây hợp lệ.

// Create BiometricPrompt instance in onCreate

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
val promptInfo = PromptInfo.Builder()
    .setTitle("Unlock?")
    .setDescription("Would you like to unlock this key?")
    .setDeviceCredentialAllowed(true)
    .build()
biometricPrompt.authenticate(promptInfo)

Mã hoá File

Jetpack Security cung cấp lớp EncryptedFile loại bỏ những thách thức trong việc mã hoá file trước đây. Giống như File, EncryptedFile cung cấp một đối tượng FileInputStream để đọc và một đối tượng FileOutputStream để ghi. Các tệp được mã hóa bằng Streaming AEAD, tuân theo định nghĩa OAE2. Dữ liệu được chia thành nhiều phần và được mã hóa bằng AES256-GCM theo cách không thể sắp xếp lại.

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
}

Mã hoá SharedPreferences

Nếu ta cần lưu các cặp Key-Value, chẳng hạn như API key, thì JetSec cung cấp lớp EncryptedSharedPreferences, lớp này sử dụng cùng interface giống như SharedPreferences mà ta thường sử dụng.

Cả Key và Value đều được mã hóa. Các Key được mã hóa bằng AES256-SIV-CMAC, cung cấp một văn bản mật mã xác định; các Value được mã hóa bằng AES256-GCM và được liên kết với Key được mã hóa. Việc sử dụng phương pháp mã hoá này cho phép dữ liệu quan trọng được mã hóa một cách an toàn, trong khi vẫn cho phép tra cứu.

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

Nguồn tham khảo

https://medium.com/androiddevelopers/data-encryption-on-android-with-jetpack-security-e4cb0b2d2a9

https://github.com/android/security-samples/tree/master/FileLocker


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.