相信大家對(duì)于生物認(rèn)證應(yīng)該不會(huì)陌生,使用指紋登陸或者 FaceId 支付等的需求場(chǎng)景如今已經(jīng)很普遍,所以基本上只要涉及移動(dòng)端開發(fā),不管是 Android 、iOS 或者是 RN 、Flutter 都多多少少會(huì)接觸到這一業(yè)務(wù)場(chǎng)景。
當(dāng)然,不同之處可能在于大家對(duì)于平臺(tái)能力或者接口能力的熟悉程度,所以本篇主要介紹 Android 和 iOS 上使用系統(tǒng)的生物認(rèn)證需要注意什么,具體流程是什么,給需要或者即將需要的大家出一份匯總的資料。
??注意:本篇更傾向于調(diào)研資料的角度,適合需要接入或者在接入過程中出現(xiàn)疑問的方向,而不是 API 使用教程,另外篇幅較長(zhǎng)警告~
首先,先簡(jiǎn)單說一個(gè)大家都知道的概念,那就是不管是 Android 或者 iOS ,不管是指紋還是 FaceId ,只要使用的是系統(tǒng)提供的 API ,作為開發(fā)者是拿不到任何用戶的生物特征數(shù)據(jù),所以簡(jiǎn)單來說你只能調(diào)用系統(tǒng) API ,然后得到成功或者失敗的結(jié)果。
一、Android
Android 上的生物認(rèn)證發(fā)展史可以說是十分崎嶇,目前簡(jiǎn)單來說經(jīng)歷了兩個(gè)階段:
-
FingerprintManager
(API 23) -
BiometricPrompt
(API 28)
所以如下圖所示,你會(huì)看到其實(shí)底層有兩套 Service
在支持生物認(rèn)證的 API 能力,但是值得注意的是, FingerprintManager
在 Api28(Android P)被添加了 @Deprecated
標(biāo)記 ,包括 androidx 里的兼容包 FingerprintManagerCompat
也是被標(biāo)注了 @Deprecated
,因?yàn)楣俜教峁└倒鲜剑_箱即用的 androidx.biometrics.BiometricPrompt
。
1.1、使用 BiometricPrompt
簡(jiǎn)單介紹下接入 BiometricPrompt
,首先第一步是添加權(quán)限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.biometric">
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
</manifest>
接著調(diào)用 BiometricPrompt
構(gòu)建系統(tǒng)彈出框信息,具體內(nèi)容對(duì)應(yīng)可見下圖:
[圖片上傳失敗...(image-a9ca67-1648602602525)]
最用設(shè)置 AuthenticationCallback
和調(diào)用 authenticate
,然后等待授權(quán)結(jié)果進(jìn)入到成功的回調(diào):
biometricPrompt = new BiometricPrompt(activity, uiThreadExecutor, authenticationCallback);
biometricPrompt.authenticate(promptInfo);
當(dāng)然上述代碼還少了很多細(xì)節(jié):
比如需要是
FragmentActivity
;檢測(cè)設(shè)備是否支持生物認(rèn)證(還有不支持的現(xiàn)在?);
判斷支持哪種生物認(rèn)證,當(dāng)然默認(rèn)
BiometricPrompt
會(huì)幫你處理,如果有多種會(huì)彈出選擇;
而認(rèn)證不成功的時(shí)候可以在 onAuthenticationError
里獲取到對(duì)應(yīng)的錯(cuò)誤碼:
onAuthenticationError | Type |
---|---|
BIOMETRIC_ERROR_LOCKOUT | 操作被取消,因?yàn)?API 由于嘗試次數(shù)過多而被鎖定(一般就是在一次 authenticate 里例如多次指紋沒通過,鎖定了, 但是過一會(huì)還可以調(diào)用) |
BIOMETRIC_ERROR_LOCKOUT_PERMANENT | 由于 BIOMETRIC_ERROR_LOCKOUT 發(fā)生太多次,操作被取消,這個(gè)就是真的 LOCK 了。 |
BIOMETRIC_ERROR_NO_SPACE | 剩余存儲(chǔ)空間不足 |
BIOMETRIC_ERROR_TIMEOUT | 超時(shí) |
BIOMETRIC_ERROR_UNABLE_TO_PROCESS | 傳感器異常或者無法處理當(dāng)前信息 |
BIOMETRIC_ERROR_USER_CANCELED | 用戶取消了操作 |
BIOMETRIC_ERROR_NO_BIOMETRIC | 用戶沒有在設(shè)備中注冊(cè)任何生物特征 |
BIOMETRIC_ERROR_CANCELED | 由于生物傳感器不可用,操作被取消 |
BIOMETRIC_ERROR_HW_NOT_PRESENT | 設(shè)備沒有生物識(shí)別傳感器 |
BIOMETRIC_ERROR_HW_UNAVAILABLE | 設(shè)備硬件不可用 |
BIOMETRIC_ERROR_VENDOR | 如果存在不屬于上述之外的情況,Other |
1.2、BiometricPrompt 自定義
簡(jiǎn)單接入完 BiometricPrompt
之后, 你可能會(huì)有個(gè)疑問: BiometricPrompt
是很方便,但是 UI 有點(diǎn)丑了,可以自定義嗎?
抱歉,不可以 ,是的,BiometricPrompt
不能自定義 UI,甚至你想改個(gè)顏色都“費(fèi)勁”, 如果你去看 biometric 的源碼,就會(huì)發(fā)現(xiàn)官方并沒有讓你自定義的打算,除非你 cv 這些代碼自己構(gòu)建一套,至于為什么會(huì)有這樣的設(shè)計(jì),我個(gè)人猜測(cè)其中一條就是屏下指紋。
在官方的 《Migrating from FingerprintManager to BiometricPrompt》里也說了:丟棄指紋的布局文件,因?yàn)槟銓⒉辉傩枰鼈儯珹ndroidX 生物識(shí)別庫帶有標(biāo)準(zhǔn)化的 UI。
什么是標(biāo)準(zhǔn)化的 UI ?如下所示是使用 BiometricPrompt
的三臺(tái)手機(jī),可以看到:
- 第一和第二臺(tái)除了位置有些許不同,其他基本一致;
- 第三胎手機(jī)是屏下指紋,可以看到整個(gè)指紋輸入的 UI 效果完全是廠家自己的另外一種風(fēng)格;
所以使用 BiometricPrompt
你將不需要關(guān)注 UI 問題,因?yàn)槟銢]得選,甚至你也不需要關(guān)注手機(jī)上的生物認(rèn)證類型的安全度問題,因?yàn)椴还苁? CDD 還是 UI ,OEM 廠商的都會(huì)直接實(shí)現(xiàn)好,例如三星的 UI 是如下圖所示:
Android 兼容性定義文檔 (Android CDD)_里描述了生物認(rèn)證傳感器安全度的強(qiáng)弱,而在 framework 層面
BiometricFragment
和FingerprintDialogFragment
都是@hide
,甚至你單純?nèi)シ?androidx.biometric:biometric.aar
的庫,你都看不到BiometricFragment
的布局,只能看到FingerprintDialogFragment
的 layout。
那就沒辦法自定義 UI 了嗎?還是有的,有兩個(gè)選擇:
- 繼續(xù)使用
FingerprintManager
,雖然標(biāo)注了棄用,但是目前還是可以用,在 Android 11 上也可以正常執(zhí)行對(duì)應(yīng)邏輯,下圖是同一臺(tái)手機(jī)在 Android 11 上使用FingerprintManager
和BiometricPrompt
的對(duì)比:
- 使用騰訊的 soter ,這個(gè)我們后面講;
1.3、Login + BiometricPrompt
介紹完調(diào)用和 UI ,那就再結(jié)合 Login 場(chǎng)景聊聊 BiometricPrompt
,官方針對(duì) Login 場(chǎng)景提供了一個(gè) Demo ,這里主要介紹整個(gè)業(yè)務(wù)流程,具體代碼可以看官方的 BiometricLoginKotlin ,前面說過生物認(rèn)證只提供認(rèn)證結(jié)果,那么結(jié)合 Login 業(yè)務(wù),在官方的例子中 BiometricPrompt
主要是用于做認(rèn)證和加密的作用:
如上圖所示,場(chǎng)景是在登陸之后,我們獲取到了用戶的 Token 信息,這個(gè) Token 信息可能是服務(wù)器基于用戶密碼合并后的內(nèi)容,所以它包含了一些敏感隱私,為了安全期間我們不能直接存儲(chǔ),而是利用 BiometricPrompt
去實(shí)現(xiàn)加密后存儲(chǔ):
- 首先通過
KeyStore
,主要是得到一個(gè)包含密碼的SecretKey
,當(dāng)然這里有一個(gè)關(guān)鍵操作,那就是setUserAuthenticationRequired(true)
,后面我們?cè)俳忉專?/li> - 然后利用
SecretKey
創(chuàng)建Clipher
,Clipher
就是 Java 里常用于加解密的對(duì)象; - 利用
BiometricPrompt.CryptoObject(cipher)
去調(diào)用生物認(rèn)證授權(quán); - 授權(quán)成功后會(huì)得到一個(gè)
AuthenticationResult
,Result 里面包含存在密鑰信息的cryptoObject?.cipher
和cipher.iv
加密偏移向量; - 利用授權(quán)成功后的
cryptoObject?.cipher
對(duì) Token 進(jìn)行加密,然后和cipher.iv
一起保存到SharePerferences
,就完成了基于BiometricPrompt
的加密保存;
是不是覺得有點(diǎn)懵? 簡(jiǎn)單說就是:我們通過一個(gè)只有用戶通過身份驗(yàn)證時(shí)才授權(quán)使用的密鑰來加密 Token ,這樣不管這個(gè) Token 是否泄漏,對(duì)于我們來說都是安全的。
然后在 KeyStore
邏輯里這里有個(gè) setUserAuthenticationRequired(true)
操作,這個(gè)操作的意思就是:是否僅在用戶通過身份驗(yàn)證時(shí)才授權(quán)使用此密鑰,也就是當(dāng)設(shè)置為 true
時(shí):
用戶必須通過使用其鎖屏憑據(jù)的子集(例如密碼/PIN/圖案或生物識(shí)別)向此 Android 設(shè)備進(jìn)行身份驗(yàn)證,才能夠而授權(quán)使用密鑰。
也就是只有設(shè)置了安全鎖屏?xí)r才能生成密鑰,而一旦安全鎖屏被禁用(重新配置為無、不驗(yàn)證用戶身份的模式、被強(qiáng)制重置)時(shí),密鑰將不可逆轉(zhuǎn)地失效。
另外可以設(shè)置了
setUserAuthenticationValidityDurationSeconds
來要求密鑰必須至少有一個(gè)生物特征才可用,而一但它設(shè)置為 true,如果用戶注冊(cè)了新的生物特征,它也將不可逆轉(zhuǎn)地失效。
所以可以看到,這個(gè)流程下密鑰會(huì)和系統(tǒng)安全綁定到一起,從而不害怕 Token 等信息的泄漏,也因?yàn)槭跈?quán)成功后的 CryptoObject
和 KeyStore
集成到一起,可以更有效地抵御例如 root 的攻擊。
而反之獲取的流程也是類似,如下圖所示:
- 在
SharePerferences
里獲取加密后的 Token 和 iv 信息; - 同樣是利用
SecretKey
創(chuàng)建Clipher
,不過這次要帶上保存的 iv 信息; - 利用
BiometricPrompt.CryptoObject(cipher)
去調(diào)用生物認(rèn)證授權(quán); - 通過授權(quán)成功后的
cryptoObject?.cipher
對(duì) Token 進(jìn)行加密,得到原始的 Token 信息;
所以可以看到,基本思路就是利用 BiometricPrompt
認(rèn)證后得到 CryptoObject?.Cipher
去加解密,通過系統(tǒng)的安全等級(jí)要保護(hù)我們的隱私信息。
最后補(bǔ)充一個(gè)知識(shí)點(diǎn),雖然一般我們不關(guān)心,但是在 BiometricPrompt
里有 auth-per-use 和 time-bound 這兩個(gè)概念:
-
auth-per-use 密鑰要求每次使用密鑰時(shí),都必須進(jìn)行認(rèn)證 ,前面我們通過
BiometricPrompt.CryptoObject(cipher)
去調(diào)用授權(quán)方法就是這類實(shí)現(xiàn); -
time-bound 密鑰是一種在一定的時(shí)間段內(nèi)有效的密鑰,可以通過
setUserAuthenticationValidityDurationSeconds
設(shè)置有效時(shí)長(zhǎng),如果你設(shè)置為很短,例如 5 秒,那行為上和 auth-per-use 基本類似;
更多資料可以參考官方的 biometric-authentication-on-android
1.4、Tencent soter
前面說到 Android 上還有 soter ,騰訊在微信指紋支付全流程之上,將它的流程抽象為一套完備的生物識(shí)別標(biāo)準(zhǔn):SOTER。
SOTER 會(huì)與手機(jī)廠商合作,在系統(tǒng)原有的接口能力之上提供安全加固,通過業(yè)務(wù)無關(guān)的安全域(TEE,即獨(dú)立于手機(jī)操作系統(tǒng)的安全區(qū)域,root或越獄無法訪問到)應(yīng)用程序(TA)降低開發(fā)難度和適配成本,做到即使外部環(huán)境不可信,依然可以安全授權(quán)。
TEE(Trusted Execution Environment)是獨(dú)立于手機(jī)操作系統(tǒng)的一塊獨(dú)立運(yùn)行的安全區(qū)域,SOTER標(biāo)準(zhǔn)中,所有的密鑰生成、數(shù)據(jù)簽名處理、指紋驗(yàn)證、敏感數(shù)據(jù)傳輸?shù)让舾胁僮骶?TEE 中進(jìn)行,并且 SOTER使用的設(shè)備根密鑰由廠商在產(chǎn)線上燒入,從根本上解決了根密鑰不可信的問題,并以此根密鑰為信任鏈根,派生密鑰,從而完成,與微信合作的所有手機(jī)廠商將均帶有硬件TEE,并且通過騰訊安全平臺(tái)和微信支付安全團(tuán)隊(duì)驗(yàn)收,符合SOTER標(biāo)準(zhǔn)。
簡(jiǎn)而言之,這是一個(gè)支持直通廠商,并且具備后臺(tái)服務(wù)對(duì)接校驗(yàn)的第三方庫,目前最近 5 個(gè)月都還有在更新,那它有什么問題呢?
那就是必須是與微信合作的所有手機(jī)廠商和機(jī)型才能正常使用 ,而且經(jīng)常在一些廠商系統(tǒng)上出現(xiàn)奇奇怪怪的問題,比如:
- MiUI13 綁定服務(wù)異常;
- 鴻蒙系統(tǒng)API層面報(bào)錯(cuò);
- 莫名其妙地出現(xiàn)崩潰;
但是它可以實(shí)現(xiàn)基本類似于微信支付的能力,所以如何取舍就看你的業(yè)務(wù)需求了。
支持機(jī)型可查閱 :#有多少設(shè)備已經(jīng)支持tencent-soter
iOS
相對(duì)來說 iOS 上的生物認(rèn)證就舒適不少,相比較 Android 上需要區(qū)分系統(tǒng)版本和廠商的 fingerprint
、face
和 iris
,iOS 上的 Face ID 和 Touch ID 就十分統(tǒng)一和簡(jiǎn)潔。
簡(jiǎn)單介紹下 iOS 上使用生物認(rèn)證,首先需要在 Info.plist
文件添加描述信息:
<key>NSFaceIDUsageDescription</key>
<string>Why is my app authenticating using face id?</string>
然后導(dǎo)入頭文件 #import <LocalAuthentication/LocalAuthentication.h>
,最后創(chuàng)建 LAContext
去執(zhí)行授權(quán)操作,這里也簡(jiǎn)單展示對(duì)應(yīng)的錯(cuò)誤碼:
Error Code | Type |
---|---|
LAErrorSystemCancel | 系統(tǒng)取消了授權(quán),比如有其他APP切換 |
LAErrorUserCancel | 用戶取消驗(yàn)證 |
LAErrorAuthenticationFailed | 授權(quán)失敗 |
LAErrorPasscodeNotSet | 系統(tǒng)未設(shè)置密碼 |
LAErrorBiometryNotAvailable | ID不可用,例如未打開 |
LAErrorBiometryNotEnrolled | ID不可用,用戶未錄入 |
LAErrorUserFallback | 用戶選擇輸入密碼 |
而同樣關(guān)于自定義 UI 問題上,想必大家都知道了,iOS 生物認(rèn)證沒有自定義 UI 的說法,也不支持自定義 UI ,系統(tǒng)怎么樣就怎么樣,你可以做的只有類似配置‘是否允許使用密碼授權(quán)’這樣的行為 。
在這一點(diǎn)上相信 Android 開發(fā)都十分羨慕 iOS ,有問題也是系統(tǒng)問題,無法修復(fù)。
同樣,簡(jiǎn)單說說在 iOS 上使用生物識(shí)別的 Login 場(chǎng)景流程:
- 獲取到 Token 信息后,驗(yàn)證用戶的 TouchID/FaceID ;
- 驗(yàn)證通過后,將 Token 等信息保存到 keychain (keychain 只是一個(gè)數(shù)據(jù)存儲(chǔ),用于存儲(chǔ)一些敏感數(shù)據(jù)如密碼、證書等);
- 保存成功后,下次再次登錄時(shí)通過驗(yàn)證 TouchID/FaceID 獲取對(duì)應(yīng)信息;
這里主要有兩個(gè)關(guān)鍵點(diǎn):
- 訪問級(jí)別 : 例如是否需要每次都進(jìn)行身份驗(yàn)證時(shí)才可以訪問項(xiàng)目;
- 身份驗(yàn)證級(jí)別: 也就是什么場(chǎng)景下可以訪問到存儲(chǔ)的信息;
舉個(gè)例子,訪問 keychain 首先是需要?jiǎng)?chuàng)建 accessControl ,一般可以通過 SecAccessControlCreateWithFlags
來創(chuàng)建 accessControl ,這里有個(gè)關(guān)鍵參數(shù)用于指定訪問級(jí)別:
- kSecAttrAccessibleAfterFirstUnlock 開機(jī)之后密鑰不可用,需要等用戶輸入開機(jī)密碼
- kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: 開機(jī)之后密鑰不可用,需要等用戶輸入開機(jī)密碼,但是僅限于當(dāng)前設(shè)備
- kSecAttrAccessibleWhenUnlocked: 解鎖過的設(shè)備密鑰會(huì)保持可用狀態(tài)
- kSecAttrAccessibleWhenUnlockedThisDeviceOnly: 解鎖過的設(shè)備密鑰會(huì)保持可用狀態(tài),僅當(dāng)前設(shè)備
- kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly: 解鎖過的設(shè)備密鑰會(huì)保持可用狀態(tài),只有用戶設(shè)置密碼后密鑰才可用
- kSecAttrAccessibleAlways: 始終可用,已經(jīng) Deprecated
- kSecAttrAccessibleAlwaysThisDeviceOnly: 密鑰始終可用,但無法遷移到其他設(shè)備,已經(jīng) Deprecated
類似場(chǎng)景下一般使用 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
,另外還有 SecAccessControlCreateFlags
標(biāo)志,它主要是用于指定希望用戶在訪問鑰匙串時(shí)的約束,一般類似場(chǎng)景會(huì)使用 userPresence
:
- devicePasscode: 限制使用密碼訪問
- biometryAny: 使用任何已注冊(cè) touch 或 face ID 訪問
- biometryCurrentSet: 限制使用當(dāng)前注冊(cè) touch 或 face ID 訪問
- userPresence: 限制使用生物特征或密碼訪問
- watch: 使用手表訪問
創(chuàng)建完成 accessControl 之后,通過設(shè)置 kSecAttrAccessControl 后正常把信息存儲(chǔ)到 keychain 就可以了,在存儲(chǔ) keychain 時(shí)也有可選的 kSecClass
,一般選用 kSecClassGenericPassword
:
- kSecClassGenericPassword: 通用密碼
- kSecClassInternetPassword: Internet 密碼
- kSecClassCertificate:證書
- kSecClassKey:加密密鑰
- kSecClassIdentity: 身份認(rèn)證
當(dāng)然,此時(shí)你是否發(fā)現(xiàn),在談及 accessControl 和 keychain 時(shí)沒有說明 LAContext
?
其實(shí)在創(chuàng)建 accessControl 時(shí)是有對(duì)應(yīng) kSecUseAuthenticationContext
參數(shù)用于設(shè)置 LAContext
到 keychain 認(rèn)證,但是也可以不設(shè)置,具體為:
- 如果未指定,并且該項(xiàng)目需要 authentication 認(rèn)證,那就會(huì)自動(dòng)創(chuàng)建一個(gè)新的
LAContext
,使用一次后丟棄; - 如果是使用先前已通過身份驗(yàn)證的
LAContext
,則操作直接成功而不要求用戶進(jìn)行身份驗(yàn)證; - 如果是使用先前未經(jīng)過身份驗(yàn)證的
LAContext
,則系統(tǒng)會(huì)嘗試在該LAContext
上進(jìn)行身份驗(yàn)證,如果成功就可以在后續(xù)的鑰匙串操作中重用。
可以看到, iOS 上都只需要簡(jiǎn)單地配置就行了,因?yàn)橄到y(tǒng)層面也不會(huì)給你多余的能力。
三、最后
雖然本篇從頭到位并沒有教你如何使用 Android 或者 iOS 的生物認(rèn)證,但是作為匯總資料,本篇基本覆蓋了 Android 或者 iOS 生物認(rèn)證相關(guān)的基本概念和問題,相信本篇將會(huì)特別適合正在調(diào)研生物認(rèn)證相關(guān)開發(fā)的小伙伴。
最后,還是慣例,如果對(duì)于這方便你有什么問題或者建議,歡迎留言評(píng)論交流。