Android 中指紋識別的使用

前言

    最近,在查看  [SampleProject](https://github.com/WangJie0822/SampleProject) 這個項目的時候,就覺得吧,這個登錄有點麻煩,總是要輸密碼,現(xiàn)在很多 **APP** 都是可以指紋登錄的呀,這個必須支持一波;而且開發(fā)這么多年還沒嘗試過指紋識別,這可不行,學(xué)到老活到老嘛。

指紋登錄流程

    **指紋登錄** 不就是簡單的調(diào)用 **指紋識別** 的 `API` 然后登錄賬號嗎,這有什么可說的?可能有人會問了。然而并非如此,一開始我也很天真的以為就這么簡單,但是在網(wǎng)上找了很多文章之后發(fā)現(xiàn),大多數(shù)的文章都只是詳細(xì)的說明了怎么去調(diào)用 **指紋識別** 的 `API`,至于怎么用于登錄,怎么去實現(xiàn) **指紋登錄** 的業(yè)務(wù)確是很少提及,所以,這里我會先給大家講清楚實現(xiàn) **指紋登錄** 的流程再去實現(xiàn)。

指紋識別在指紋登錄中的作用

    登錄是我們應(yīng)用中的邏輯,我們把 **指紋識別** 穿插在其中是需要他做什么?

    實際上,我們可以把 **指紋登錄** 簡單的理解成 **不用密碼登錄**,那么為了能過 **不用密碼登錄**,我們肯定需要把用戶登錄需要的信息 `保存到本地`,那么我們首先考慮到的就是 **本地數(shù)據(jù)的安全問題**,在這里,**指紋識別** 就為我們本地數(shù)據(jù)提供了 `加密、解密` 的功能。

指紋登錄相關(guān)流程

指紋登錄流程
    如上圖所示,在使用 **指紋登錄** 功能前,我們需要先 **開啟指紋登錄**,這個步驟是為了獲取登錄所需要的數(shù)據(jù),并進行加密存儲,之后再 **指紋登錄** 時,獲取存儲加密的數(shù)據(jù),進行解密,然后進行登錄。

指紋識別面臨的問題

    在實現(xiàn)功能之前,我們需要知道,**Google** 從 `Android 6.0` 才開始支持 **指紋識別**,所以,如果你想兼顧 **6.0** 以下的機型,那么你可能需要自己去集成不同 **手機廠商** 的 `SDK`,當(dāng)然了,現(xiàn)在 `6.0` 以下的手機已經(jīng)很少了,而且我的項目只是一個自用的 **DEMO**,就不去做那些復(fù)雜的東西了,有需要的可以自行了解;

    其次,從 `Android 6.0` **Google** 新增了 `FingerprintManager` 用于指紋識別,后續(xù)又新增了 `FingerprintManagerCompat` 提供了一些兼容性操作,再到 `Android 9.0` 新增了 `BiometricPrompt` `API` 用于生物識別,并將 `FingerprintManager` 添加了 `@Deprecated` 標(biāo)記,所以在實現(xiàn)時我們也需要考慮版本兼容問題。

指紋登錄實現(xiàn)

    從上面,我們了解了 **指紋登錄** 的流程以及 **指紋識別** 的發(fā)展及要注意的問題,接下來我們開始實現(xiàn)。

指紋識別的集成

    首先,我們將 **指紋識別** 功能集成進來。

    雖然 `Android 6.0` 就有了 **指紋識別** 的 `API`,但顯然并不是所有手機都會支持,所以,我們首先來判斷當(dāng)前手機是否支持 **指紋識別**。
/** [Build.VERSION_CODES.M] 以上指紋管理對象 */
private val fingerprintManager: FingerprintManagerCompat by lazy {
    FingerprintManagerCompat.from(activity)
}

/** 檢查指紋識別支持狀態(tài) */
fun checkBiometric(): Int {
    // 獲取鎖屏管理
    val km = context.getSystemService(KeyguardManager::class.java)
    return when {
        !fingerprintManager.isHardwareDetected -> {
            // 不支持指紋
            BiometricInterface.ERROR_HW_UNAVAILABLE
        }
        !km.isKeyguardSecure -> {
            // 未設(shè)置鎖屏
            BiometricInterface.ERROR_NO_DEVICE_CREDENTIAL
        }
        !fingerprintManager.hasEnrolledFingerprints() -> {
            // 未注冊有效指紋
            BiometricInterface.ERROR_NO_BIOMETRICS
        }
        else -> {
            // 支持指紋識別
            BiometricInterface.HW_AVAILABLE
        }
    }
}
    在確定手機支持 **指紋識別** 后,我們就可以調(diào)用 `API` 拉起指紋識別功能了。
/** [Build.VERSION_CODES.M] 以上指紋管理對象 */
private val fingerprintManager: FingerprintManagerCompat by lazy {
    FingerprintManagerCompat.from(activity)
}

/** [Build.VERSION_CODES.M] 以上拉起指紋認(rèn)證 */
fun authenticateM() {
    fingerprintManager.authenticate(
        crypto, // 包裝了 Cipher 對象的 FingerprintManagerCompat.CryptoObject 對象,用于加解密
        flags, // 可選 flag,建議為 0
        cancel, // CancellationSignal 對象,用于取消指紋認(rèn)證,可空,但不建議為 null
        callback, // 認(rèn)證回調(diào)接口
        handler // 回調(diào)所在 Handler,一般為 null
    )
}
    上面只是 `Android 6.0` 以上的簡單的 **指紋認(rèn)證** 代碼,但是 `FingerprintManagerCompat` 并沒有提供相關(guān)提示彈窗,所以,在這基礎(chǔ)上,我們還需要加上相關(guān)彈窗邏輯。
/** [Build.VERSION_CODES.M] 以上拉起指紋認(rèn)證 */
fun authenticateM() {
    val cancellationSignal = CancellationSignal()
    cancellationSignal.setOnCancelListener {
        // 取消回調(diào)
    }
    val dialog = BiometricDialog.create()
    dialog.setOnCancelListener {
        // 取消指紋認(rèn)證
        cancellationSignal.cancel()
    }
    dialog.show()
    fingerprintManager.authenticate(
        crypto, // 包裝了 Cipher 對象的 FingerprintManagerCompat.CryptoObject 對象,用于加解密
        flags, // 可選 flag,建議為 0
        cancellationSignal, // CancellationSignal 對象,用于取消指紋認(rèn)證,可空,但不建議為 null
        object: FingerprintManagerCompat.AuthenticationCallback() {
            override fun onAuthenticationSucceeded(result: FingerprintManagerCompat.AuthenticationResult?) {
                // 認(rèn)證成功
                dialog.dismiss()
            }
            override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) {
                // 認(rèn)證提示
                dialog.setHint(helpString)
            }
            override fun onAuthenticationFailed() {
                // 認(rèn)證失敗
                dialog.dismiss()
            }
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
                // 認(rèn)證異常
                dialog.dismiss()
            }
        }, // 認(rèn)證回調(diào)接口
        null // 回調(diào)所在 Handler,一般為 null
    )
}
    這樣,我們的認(rèn)證功能就完成了,而在 `Android 9.0` 新增的 `BiometricPrompt` `API` 中已經(jīng)提供相關(guān)提示彈窗,所以不需要我們自己手動實現(xiàn)彈窗。
/** [Build.VERSION_CODES.Q] 以上拉起指紋認(rèn)證 */
fun authenticateQ() {
    val cancellationSignal = CancellationSignal()
    cancellationSignal.setOnCancelListener {
        // 取消回調(diào)
    }
    // 生成認(rèn)證對象
    val prompt = with(BiometricPrompt.Builder(activity)) {
        setTitle(title)
        setSubtitle(subTitle)
        setDescription(hint)
        setNegativeButton(negative, activity.mainExecutor, { dialog, _ ->
            // 取消回調(diào)
            dialog?.dismiss()
            cancellationSignal.cancel()
        })
        build()
    }
    prompt.authenticate(
        crypto, // 包裝了 Cipher 對象的 BiometricPrompt.CryptoObject 對象,用于加解密
        cancellationSignal, // CancellationSignal 對象,用于取消指紋認(rèn)證,不能為空
        executor, // 回調(diào) Executor,不能為空,可使用 activity.mainExecutor
        callback // 認(rèn)證回調(diào)
    )
}
    那么 `crypto` 怎么獲取呢?這個就要結(jié)合業(yè)務(wù)場景來說了,因為 `Cipher` 對象在用于 **加密、解密** 時獲取的方式是不同的。

開啟指紋登錄功能

    上文有說到過,**指紋登錄** 功能要先提供 **開啟指紋登錄** 來保存登錄需要的數(shù)據(jù),因為 **玩Android** 沒有單獨提供相關(guān)的 `API`,所以這里我們就使用 **登錄** 接口來驗證密碼的正確性;

    所以,首先彈窗提示,讓用戶輸入密碼,確認(rèn)后調(diào)用 **登錄接口** 驗證密碼正確性,確認(rèn)密碼正確后,將密碼暫時緩存,拉起 **指紋認(rèn)證**;

    指紋認(rèn)證流程在上面已經(jīng)說過了,這里我們重點介紹 `Cipher` 對象的獲取。
/** 獲取 Cipher 對象 */
fun loadCipher(): Cipher {
    val keyStore = KeyStore.getInstance("AndroidKeyStore")
    keyStore.load(null)
    // keyAlias 為密鑰別名,可自己定義,加密解密要一致
    if (!keyStore.containsAlias(keyAlias)) {
        // 不包含改別名,重新生成
        // 秘鑰生成器
        val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
        val builder = KeyGenParameterSpec.Builder(
            keyAlias,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setUserAuthenticationRequired(false)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        keyGenerator.init(builder.build())
        keyGenerator.generateKey()
    }
    // 根據(jù)別名獲取密鑰
    val key = keyStore.getKey(keyAlias, null)
    val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                                    + KeyProperties.BLOCK_MODE_CBC + "/"
                                    + KeyProperties.ENCRYPTION_PADDING_PKCS7)
    // 開啟登錄時用于加密,使用 Cipher.ENCRYPT_MODE 初始化
    cipher.init(Cipher.ENCRYPT_MODE, key)
    return cipher
}
    獲取到 `Cipher` 對象后,調(diào)用指紋認(rèn)證,不同版本 `CryptoObject` 的對象是不同的,直接新建對應(yīng)對象,將 `Cipher` 對象傳入即可
/** 指紋認(rèn)證回調(diào)成功 */
override fun onAuthenticationSucceeded(result: AuthenticationResult?) {
    // 認(rèn)證成功,獲取 Cipher 對象
    val cipher = result?.cryptoObject?.cipher ?: throw RuntimeException("cipher is null!")
    // 使用 cipher 對登錄信息進行加密并保存
    val encryptInfo = cipher.doFinal(loginInfo.toByteArray()).toHexString()
    // 保存 encryptInfo 到本地
    // 保存加密向量到本地
    save(encryptInfo)
    save(cipher.iv.toHexString())
}
    這樣 **開啟指紋登錄** 就完成了。

需要注意的有三點:

  1. 加密時,Cipher 對象使用 cipher.init(Cipher.ENCRYPT_MODE, key) 進行初始化;
  2. 指紋認(rèn)證成功后,使用回調(diào)返回 Cipher 對象對數(shù)據(jù)進行加密;
  3. 指紋認(rèn)證成功后,要將 Cipher 對象中的加密向量 iv 保存起來。

指紋登錄功能

    通過上面開啟了 **指紋登錄** 之后,我們就可以在登錄頁進行 **指紋登錄** 了。

    進入登錄頁后,可以自動拉起 **指紋登錄** 或者用戶點擊 **指紋登錄** 后拉起。
/** 獲取 Cipher 對象 */
fun loadCipher(): Cipher {
    val keyStore = KeyStore.getInstance("AndroidKeyStore")
    keyStore.load(null)
    // keyAlias 為密鑰別名,可自己定義,加密解密要一致
    if (!keyStore.containsAlias(keyAlias)) {
        // 不包含改別名,重新生成
        // 秘鑰生成器
        val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
        val builder = KeyGenParameterSpec.Builder(
            keyAlias,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setUserAuthenticationRequired(false)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        keyGenerator.init(builder.build())
        keyGenerator.generateKey()
    }
    // 根據(jù)別名獲取密鑰
    val key = keyStore.getKey(keyAlias, null)
    val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                                    + KeyProperties.BLOCK_MODE_CBC + "/"
                                    + KeyProperties.ENCRYPTION_PADDING_PKCS7)
    // 獲取開啟指紋登錄時保存的加密向量數(shù)據(jù)
    val ivBytes = get(IV_BYTES).toHexByteArray()
    val iv = IvParameterSpec(ivBytes)
    // 使用指紋登錄,使用 Cipher.DECRYPT_MODE 和 iv 進行初始化
    cipher.init(Cipher.DECRYPT_MODE, key, iv)
    return cipher
}
    和上面開啟一樣,拉起指紋認(rèn)證。
/** 指紋認(rèn)證回調(diào)成功 */
override fun onAuthenticationSucceeded(result: AuthenticationResult?) {
    // 認(rèn)證成功,獲取 Cipher 對象
    val cipher = result?.cryptoObject?.cipher ?: throw RuntimeException("cipher is null!")
    // 使用 cipher 對登錄信息進行解密
    val logintInfo = cipher.doFinal(get(encryptInfo).toHexByteArray()
    // 使用 loginInfo 進行登錄
    login(loginInfo)
}
    這樣就完成了 **指紋登錄**。

需要注意的有兩點:

  1. 解密時,Cipher 對象使用 cipher.init(Cipher.DECRYPT_MODE, key, iv) 進行初始化;
  2. 指紋認(rèn)證成功后,使用回調(diào)返回 Cipher 對象對數(shù)據(jù)進行解密。

總結(jié)

    看完全文,你學(xué)會怎么集成 **指紋登錄** 功能了嗎?我們需要牢記的是,**指紋登錄** 就是一個類似于 **記住密碼** 的功能,在這個過程中使用到了 **指紋認(rèn)證** 來對登錄信息進行加密解密,使用的都是 `Cipher` 對象,而用于加密和解密時 `Cipher` 對象的初始化方式有所不同,解密時需要使用到加密時生成的 `IvParameterSpec` 加密向量,而由我們初始化出來的 `Cipher` 對象是無法直接使用的,需要使用 **指紋認(rèn)證** 處理之后才能用于加密解密。

    想要我的源碼嗎?想要的話可以全部給你,去找吧!我把所有源碼都放在那里!>> [SampleProject](https://github.com/WangJie0822/SampleProject) <<

    感謝大家的耐心觀看,我是 [WangJie0822](http://www.wangjie0822.top) ,一個平平凡凡的程序猿,歡迎關(guān)注。

文章作者: WangJie0822
文章鏈接: http://www.wangjie0822.top/posts/bef2e009
版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 CC BY-NC-SA 4.0 許可協(xié)議。轉(zhuǎn)載請注明來自 WangJie0822!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 指紋識別相關(guān)api FingerprintManagaerCompat 指紋識別的核心包裝類 初始化方法 用于判斷...
    好多個胖子閱讀 2,201評論 1 10
  • 網(wǎng)上有一堆做指紋識別的demo,但是僅限于識別這一步,其實對于一個項目來說,牽扯到的東西可能更多:從無指紋識別版本...
    瑜小賢閱讀 206評論 0 0
  • 指紋識別-Android @(Android進階資料)[Android, 學(xué)習(xí), 讀書筆記, Markdown]指...
    辰曦小雨閱讀 1,676評論 3 6
  • 最近項目需要使用到指紋識別的功能,查閱了相關(guān)資料后,整理成此文。 指紋識別是在Android 6.0之后新增的功能...
    湫水長天閱讀 3,882評論 2 46
  • 一、 指紋識別接口從Android 6.0開始,Android系統(tǒng)加上了對指紋識別的支持。所有指紋識別的接口都在...
    Qi0907閱讀 1,657評論 0 1

友情鏈接更多精彩內(nèi)容