Android Biometric Security

Android-加密生物辨識

陳冠勳 2025/12/08 09:48:17
11

前言

在 OSWAP(OWASP)資安十大風險中,身份驗證與授權問題一直都是嚴重的威脅,尤其在金融交易、股票下單等需要高等級身份辨識的應用中特別重要。早期,Android 9 以前的版本安全性較低,有些資安滲透工具可繞過指紋驗證,帶來資安疑慮。然而,自 Android 9 起,原生系統導入了 BiometricManager 與 BiometricPrompt,不僅可支援更多生物辨識方式(指紋、臉部、虹膜等),配合裝置端硬體密鑰系統(如 Keystore)整合,讓敏感功能更加安全。本文將完整說明如何在 Android 應用中正確、安全地實現 BiometricPrompt 生物辨識時,同時示範如何利用Android Keystore與生物辨識共同強化本地端資料加密。

 

Android Keystore 運作原理

Android Keystore System 是一個安全加密金鑰管理系統,能將加密金鑰儲存在安全硬體(如 Trusted Execution Environment, TEE 或 Secure Element, SE)中,並藉由系統強制執行存取權限與用途限制,防止金鑰被未經授權使用。應用程式無法直接存取金鑰的原始資料,只能透過 Keystore API 呼叫金鑰執行加解密或簽章等操作。

Keystore 提供:

• 金鑰生成與儲存:只能透過系統API建立,且儲存在硬體保護區,無法匯出金鑰。

• 存取控制:可限制金鑰只能用於特定加密演算法、操作模式,甚至設定需用戶生物辨識或密碼驗證才能使用。

• 安全硬體加持:只在硬體環境執行敏感加解密運算,提升整體安全性。

 

這大幅降低了金鑰被竊取的風險,即使應用程式或裝置被攻擊,攻擊者也無法取得或濫用保護中的金鑰。

 

Cipher 運作原理

Cipher 是進行加密及解密運算的演算法實體。例如在Android中,常使用AES-GCM模式進行加密,其運作過程如下:

• 初始化:指定加密(ENCRYPT_MODE)或解密(DECRYPT_MODE)模式,並載入指定金鑰與參數(如初始化向量 IV)。

• 加密:將明文資料丟入 Cipher物件,經由金鑰演算輸出安全的密文。

• 初始化向量(IV):GCM模式會用隨機產生IV,提高加密結果隨機性,IV非機密但解密時需使用相同IV。

• 解密:利用相同金鑰與IV,Cipher物件對密文解密還原明文。

 

當Cipher與Keystore結合時,金鑰本身由Keystore硬體保護,只有通過授權(如生物辨識)後,Cipher才被允許執行加解密,確保敏感數據安全且需驗證後才能操作。

 

與 Keystore 互動流程為:

1. App 先向 Keystore 要求生成並持有金鑰。

2. Cipher 指定此金鑰進行初始化(ENCRYPT/DECRYPT)。

3. 若金鑰被設限(如需通過生物識別),則在進行 Cipher 運算時,操作系統先彈出生物驗證 UI,驗證通過後才真正允許 Cipher 進行 decryption/encryption。

4. Cipher 處理完畢,將 output(密文或明文)回傳給 App 而不暴露金鑰詳細內容。

加入 Keystore 與 AES 加密示範

Android Keystore System可讓你產生硬體保護的金鑰,藉此提升本地敏感資料的安全性。你可以搭配BiometricPrompt要求使用者生物驗證後,才允許進行解密操作。

一、環境建置與元件安裝

1. 首先,進入專案中 app 資料夾下的 build.gradle 檔案,於 dependencies 區段新增 Biometric KTX 函式庫,版本可用 1.4.0-alpha02(實際以官方最新版為準):

dependencies {
    implementation "androidx.biometric:biometric-ktx:1.4.0-alpha02"
}

2. 權限設定
一般情況下只需要在Manifest中加入USE_BIOMETRIC權限:

<uses-permission android:name="android.permission.USE_BIOMETRIC"/>

若需相容早期指紋辨識,也建議加入USE_FINGERPRINT,但新裝置建議統一採用Biometric API。

二、BiometricPrompt 基本原理與設定

BiometricPrompt 的設計不僅僅支援指紋,還能針對具備臉部、虹膜等感測器的裝置實現更多生物辨識方式;辨識過程可藉由裝置端的 Keystore 輔助,高度保障用戶私密資訊。

 

1. 建立加密金鑰

// 建立 AES 金鑰,並限制需通過生物辨識才能使用
private fun createSecretKey() {
    val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
    if (!keyStore.containsAlias(KEY_ALIAS)) {
        val keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
        val parameterSpec = KeyGenParameterSpec.Builder(
            KEY_ALIAS,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setUserAuthenticationRequired(true) // 需要生物認證
            .build()
        keyGen.init(parameterSpec)
        keyGen.generateKey()
    }
}
// KEY_ALIAS可自訂
private const val KEY_ALIAS = "biometricKey"

2. 建立 Cipher 物件

fun getCipher(): Cipher {
    return Cipher.getInstance("${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_GCM}/${KeyProperties.ENCRYPTION_PADDING_NONE}")
}

3. 使用 BiometricPrompt 結合 CryptoObject

// 建立 BiometricPrompt.CryptoObject
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val secretKey = (keyStore.getEntry(KEY_ALIAS, null) as KeyStore.SecretKeyEntry).secretKey
val cipher = getCipher()
cipher.init(Cipher.ENCRYPT_MODE, secretKey)

val biometricPrompt = BiometricPrompt(
    this, 
    ContextCompat.getMainExecutor(this), 
    object : BiometricPrompt.AuthenticationCallback() {
        override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
            // 生物驗證通過,可用 result.cryptoObject.cipher 進行加密
            val encrypted = encryptString("SENSITIVE_DATA", result.cryptoObject!!.cipher!!)
            // encrypted 可安全儲存
        }
    })

val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("登入")
    .setSubtitle("請完成生物特徵驗證")
    .setNegativeButtonText("取消")
    .build()

biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))

4. 加密與解密實作

fun encryptString(plainText: String, cipher: Cipher): ByteArray {
    return cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))
}

fun decryptString(encrypted: ByteArray, cipher: Cipher): String {
    val decryptedBytes = cipher.doFinal(encrypted)
    return String(decryptedBytes, Charsets.UTF_8)
}

注意:使用GCM模式時,iv(Initialization Vector)需要記錄下來,解密時要一併還原成Cipher才能正確解密。

其中PromptInfo 的設定是讓畫面彈出視窗顯示資訊(如標題、按鈕文字),必要時可設定”不需確認”選項避免重複驗證:

val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("登入驗證")
    .setConfirmationRequired(false)
    .setNegativeButtonText("取消")
    .build()

 

5. 裝置支援性確認

利用 BiometricManager.from(context).canAuthenticate(…) 判斷裝置硬體及設定現況,依據不同狀態取得不同代碼:

• BIOMETRIC_SUCCESS:可安全進行生物特徵驗證

• BIOMETRIC_ERROR_NO_HARDWARE:無生物辨識硬體

• BIOMETRIC_ERROR_HW_UNAVAILABLE:硬體暫時不可用

• BIOMETRIC_ERROR_NONE_ENROLLED:尚未註冊任何生物辨識資料,可引導用戶前往設定註冊

• BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED發現一個或多個硬體傳感器存在安全漏洞。

• BIOMETRIC_ERROR_UNSUPPORTED:當前的 Android 版本不兼容

• 其餘還有如安全性異常、版本不符等細緻狀態,有助提升UX與導流

 

進一步處理可引導使用者進入生物辨識設定流程:

when (BiometricManager.from(this)
        .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
    BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
        val intent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
            putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
                     BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
        }
        activityLauncher.launch(intent)
        showToast("尚未註冊生物辨識,請前往設定")
    }
    // 其他 case 處理同理
    BiometricManager.BIOMETRIC_SUCCESS -> {
        biometricPrompt.authenticate(promptInfo)
    }
}

 

三、生物辨識事件接收與處理

建立 BiometricPrompt,註冊驗證結果的 callback:

• onAuthenticationSucceeded:驗證成功

• onAuthenticationError:發生錯誤(如按下取消鍵、超過錄入次數被鎖定)

• onAuthenticationFailed:指紋不正確等失敗,但非致命錯誤

private fun createBiometricPrompt(): BiometricPrompt {
    val executor = ContextCompat.getMainExecutor(this)
    val callback = object : BiometricPrompt.AuthenticationCallback() {
        override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
            if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
                showToast("取消")
            }
            if (errorCode == BiometricPrompt.ERROR_LOCKOUT) {
                showToast("驗證已鎖定,請稍後再試")
            }
        }
        override fun onAuthenticationFailed() {
            showToast("辨識失敗,請再試一次")
        }
        override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
            showToast("生物辨識成功")
        }
    }
    return BiometricPrompt(this, executor, callback)
}

應用時只要呼叫 `biometricPrompt.authenticate(promptInfo)` 即可啟動驗證流程。

四、安全實踐與注意事項

• 密碼/生物辨識雙重認證:為高度敏感功能,可設計要求用戶通過生物識別+設備密碼雙重驗證。

• 憑證存取:建議將敏感資料儲存於 Android Keystore,搭配硬體生物辨識增加攻擊門檻。

• 用戶教育:建議在 APP 中簡要說明生物辨識用途與安全性,提升用戶信任。

 

• 多元硬體支援:需妥善處理低階裝置無法使用生物辨識的例外情境。

 

結語

BiometricPrompt 與 BiometricManager 的導入,讓 Android 應用的身份驗證流程更彈性也更安全,比起早期單一指紋辨識方式,現已支援多種生物辨識設備整合與更多狀態判別。無論是在流程設計還是安全性管理上,開發者都能藉此提供使用者更良好的資安體驗,使重要交易與敏感資訊保護再升級。

陳冠勳