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