首先是安卓提供的接口姨蝴,所有關(guān)于指紋識別的接口全部在handroid.hardware.fingerprint的這個類中籽懦,這個包中包含的內(nèi)容不是很多,如下圖:
上面的圖中八拱,我們看到這個包中總共有4個類藏雏,下面我們簡要介紹一下他們:
- FingerprintManager:主要用來協(xié)調(diào)管理和訪問指紋識別硬件設(shè)備
- FingerprintManager.AuthenticationCallback這個一個callback接口,當(dāng)指紋認(rèn)證后系統(tǒng)會回調(diào)這個接口通知app認(rèn)證的結(jié)果是什么
- FingerprintManager.AuthenticationResult這是一個表示認(rèn)證結(jié)果的類熄云,會在回調(diào)接口中以參數(shù)給出
- FingerprintManager.CryptoObject這是一個加密的對象類膨更,用來保證認(rèn)證的安全性,這是一個重點缴允,下面我們會分析荚守。
好了珍德,到這里我們簡要知道了android 6.0給出的指紋識別的接口不是很多,可以說是簡短干練矗漾。
開始指紋驗證之前锈候,首先要確認(rèn)指紋是否支持指紋識別
主要是以下的幾個方面:
- 在AndroidManifest.xml中聲明權(quán)限
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
- API level 23 。
指紋識別API是在api level 23也就是android 6.0中加入的敞贡,因此我們的app必須運(yùn)行在這個系統(tǒng)版本之上泵琳。因此google推薦使用 Android Support Library v4包來獲得FingerprintManagerCompat對象,因為在獲得的時候這個包會檢查當(dāng)前系統(tǒng)平臺的版本誊役。
- 硬件
硬件識別要求設(shè)備(手機(jī))上有指紋識的硬件获列,因此在運(yùn)行中需要檢查當(dāng)前系統(tǒng)中是否有識別的硬件。
if (!fingerprintManager.isHardwareDetected()) {
// no fingerprint sensor is detected, show dialog to tell user.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.no_sensor_dialog_title);
builder.setMessage(R.string.no_sensor_dialog_message);
builder.setIcon(android.R.drawable.stat_sys_warning);
builder.setCancelable(false);
builder.setNegativeButton(R.string.cancel_btn_dialog, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
// show this dialog.
builder.create().show();
}
- 當(dāng)前設(shè)備必須處在安全保護(hù)中蛔垢。
安全保護(hù)的意思指的是當(dāng)前設(shè)備必須使用屏幕鎖保護(hù)击孩,可以是password,可以是pin或者是圖形解鎖鹏漆。因為谷歌認(rèn)為當(dāng)前指紋解鎖還有一定的不足巩梢,仍然需要配合傳統(tǒng)的解鎖方式,使用以下代碼檢查:
KeyguardManager keyguardManager =(KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);
if (keyguardManager.isKeyguardSecure()) {
// this device is secure.
}
- 系統(tǒng)中是不是有注冊的指紋艺玲。
普通app想要使用指紋識別功能括蝠,必須現(xiàn)在setting中注冊過一個指紋,否則不能使用饭聚,使用以下代碼進(jìn)行檢查:
if (!fingerprintManager.hasEnrolledFingerprints()) {
// no fingerprint image has been enrolled.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.no_fingerprint_enrolled_dialog_title);
builder.setMessage(R.string.no_fingerprint_enrolled_dialog_message);
builder.setIcon(android.R.drawable.stat_sys_warning);
builder.setCancelable(false);
builder.setNegativeButton(R.string.cancel_btn_dialog, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
// show this dialog
builder.create().show();
}
目前谷歌對于所有普通app忌警,只允許對指紋進(jìn)行識別,并沒有提供指紋注冊的權(quán)限秒梳。
一個好的app應(yīng)該對以上的幾個條件進(jìn)行檢查慨蓝。
開始使用指紋識別
主要分以下幾個步驟:
- 申明權(quán)限
- 獲得FingerprintManager的對象引用
- 檢查指紋識別兼容性
- 掃描用戶指紋
- 處理驗證信息
- 其他(取消掃描)
1、申請權(quán)限
這個比較簡單
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
2端幼、獲得FingerprintManager的對象引用
這個過程有兩種方式
// Using the Android Support Library v4
fingerprintManager = FingerprintManagerCompat.from(this);
// Using API level 23:
fingerprintManager = (FingerprintManager)getSystemService(Context.FINGERPRINT_SERVICE);
谷歌推行第一種使用方式,通過v4包獲得對象引用弧满。
第二種方式是使用api 23 framework中的接口獲得對象引用婆跑。
注:這里使用的是FingerprintManagerCompat而不是FingerprintManager。前者兼容后者庭呜,
3滑进、檢查運(yùn)行條件,主要指之前的一個板塊募谎。這里不多贅述
4扶关、掃描用戶指紋
這一步也比較簡單,只需要調(diào)用FingerprintManager的authenticate方法即可数冬。
看一下這個接口
上圖是google的api文檔中的描述节槐,現(xiàn)在我們挨個解釋一下這些參數(shù)都是什么:
- crypto這是一個加密類的對象搀庶,指紋掃描器會使用這個對象來判斷認(rèn)證結(jié)果的合法性。這個對象可以是null铜异,但是這樣的話哥倔,就意味這app無條件信任認(rèn)證的結(jié)果,雖然從理論上這個過程可能被攻擊揍庄,數(shù)據(jù)可以被篡改咆蒿,這是app在這種情況下必須承擔(dān)的風(fēng)險。因此蚂子,建議這個參數(shù)不要置為null沃测。這個類的實例化有點麻煩,主要使用javax的security接口實現(xiàn)食茎,后面有一個例子可以參考
- cancel 這個是CancellationSignal類的一個對象蒂破,這個對象用來在指紋識別器掃描用戶指紋的是時候取消當(dāng)前的掃描操作,如果不取消的話董瞻,那么指紋掃描器會移植掃描直到超時(一般為30s寞蚌,取決于具體的廠商實現(xiàn)),這樣的話就會比較耗電钠糊。建議這個參數(shù)不要置為null挟秤。
- flags 標(biāo)識位,根據(jù)上圖的文檔描述抄伍,這個位暫時應(yīng)該為0艘刚,這個標(biāo)志位應(yīng)該是保留將來使用的。
- callback 這個是FingerprintManager.AuthenticationCallback類的對象截珍,這個是這個接口中除了第一個參數(shù)之外最重要的參數(shù)了攀甚。當(dāng)系統(tǒng)完成了指紋認(rèn)證過程(失敗或者成功都會)后,會回調(diào)這個對象中的接口岗喉,通知app認(rèn)證的結(jié)果秋度。這個參數(shù)不能為NULL。
- handler 這是Handler類的對象钱床,如果這個參數(shù)不為null的話荚斯,那么FingerprintManager將會使用這個handler中的looper來處理來自指紋識別硬件的消息。通常來講查牌,開發(fā)這不用提供這個參數(shù)事期,可以直接置為null,因為FingerprintManager會默認(rèn)使用app的main looper來處理纸颜。
5兽泣、處理驗證信息
在上一步中我們對指紋進(jìn)行了識別,接下來我們需要對callback結(jié)果進(jìn)行驗證胁孙。
實際上唠倦,這一步應(yīng)該在驗證之間進(jìn)行称鳞,首先是創(chuàng)建一個callback對象,這個對象中牵敷,對返回信息進(jìn)行重寫胡岔,然后直接進(jìn)行驗證,驗證結(jié)果就執(zhí)行我們重寫的內(nèi)容枷餐,達(dá)到處理驗證信息的目的靶瘸。
代碼如下:
FingerprintManagerCompat.AuthenticationCallback callback = new FingerprintManagerCompat.AuthenticationCallback(){
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
Toast.makeText(context, "succeed", Toast.LENGTH_SHORT).show();
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
super.onAuthenticationError(errMsgId, errString);
Toast.makeText(context, "error", Toast.LENGTH_SHORT).show();
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Toast.makeText(context, "failed", Toast.LENGTH_SHORT).show();
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
super.onAuthenticationHelp(helpMsgId, helpString);
Toast.makeText(context, "help", Toast.LENGTH_SHORT).show();
}
};
上面的代碼也可以直接新建一個類,繼承FingerprintManagerCompat.AuthenticationCallback
其中最主要的是這4個返回結(jié)果
OnAuthenticationError(int errorCode, ICharSequence errString) 這個接口會再系統(tǒng)指紋認(rèn)證出現(xiàn)不可恢復(fù)的錯誤的時候才會調(diào)用毛肋,并且參數(shù)errorCode就給出了錯誤碼怨咪,標(biāo)識了錯誤的原因。這個時候app能做的只能是提示用戶重新嘗試一遍润匙。
OnAuthenticationFailed() 這個接口會在系統(tǒng)指紋認(rèn)證失敗的情況的下才會回調(diào)诗眨。注意這里的認(rèn)證失敗和上面的認(rèn)證錯誤是不一樣的,雖然結(jié)果都是不能認(rèn)證孕讳。認(rèn)證失敗是指所有的信息都采集完整匠楚,并且沒有任何異常,但是這個指紋和之前注冊的指紋是不相符的厂财;但是認(rèn)證錯誤是指在采集或者認(rèn)證的過程中出現(xiàn)了錯誤芋簿,比如指紋傳感器工作異常等。也就是說認(rèn)證失敗是一個可以預(yù)期的正常情況璃饱,而認(rèn)證錯誤是不可預(yù)期的異常情況与斤。
OnAuthenticationHelp(int helpMsgId, ICharSequence helpString) 上面的認(rèn)證失敗是認(rèn)證過程中的一個異常情況,我們說那種情況是因為出現(xiàn)了不可恢復(fù)的錯誤荚恶,而我們這里的OnAuthenticationHelp方法是出現(xiàn)了可以回復(fù)的異常才會調(diào)用的撩穿。什么是可以恢復(fù)的異常呢?一個常見的例子就是:手指移動太快谒撼,當(dāng)我們把手指放到傳感器上的時候食寡,如果我們很快地將手指移走的話,那么指紋傳感器可能只采集了部分的信息廓潜,因此認(rèn)證會失敗冻河。但是這個錯誤是可以恢復(fù)的,因此只要提示用戶再次按下指紋茉帅,并且不要太快移走就可以解決。
OnAuthenticationSucceeded(FingerprintManagerCompati.AuthenticationResult result)這個接口會在認(rèn)證成功之后回調(diào)锭弊。我們可以在這個方法中提示用戶認(rèn)證成功堪澎。這里需要說明一下,如果我們上面在調(diào)用authenticate的時候味滞,我們的CryptoObject不是null的話樱蛤,那么我們在這個方法中可以通過AuthenticationResult來獲得Cypher對象然后調(diào)用它的doFinal方法钮呀。doFinal方法會檢查結(jié)果是不是會攔截或者篡改過,如果是的話會拋出一個異常昨凡。當(dāng)我們發(fā)現(xiàn)這些異常的時候都應(yīng)該將認(rèn)證當(dāng)做是失敗來來處理爽醋,為了安全建議大家都這么做。
關(guān)于上面的接口還有2點需要補(bǔ)充一下:-
上面我們說道OnAuthenticationError 和 OnAuthenticationHelp方法中會有錯誤或者幫助碼以提示為什么認(rèn)證不成功便脊。Android系統(tǒng)定義了幾個錯誤和幫助碼在FingerprintManager類中蚂四,如下:
我們的callback類實現(xiàn)的時候最好需要處理這些錯誤和幫助碼。
當(dāng)指紋掃描器正在工作的時候哪痰,如果我們?nèi)∠敬尾僮鞯脑捤煸到y(tǒng)也會回調(diào)OnAuthenticationError方法的,只是這個時候的錯誤碼是FingerprintManager.FINGERPRINT_ERROR_CANCELED(值為5)晌杰,因此app需要區(qū)別對待跷睦。
6、其他處理肋演。
上一步中抑诸,當(dāng)errorcode為5的時候,表示取消處理爹殊。這個處理是比較常見的操作蜕乡。直接使用CancellationSignal的cancel方法實現(xiàn)。
具體使用就是边灭,實力化一個CancellationSignal的對象异希,在認(rèn)證時,將該對象傳入绒瘦。需要取消的時候称簿,調(diào)用該對象的cancel方法就行了。
附:
創(chuàng)建CryptoObject類對象
上面我們分析FingerprintManager的authenticate方法的時候惰帽,看到這個方法的第一個參數(shù)就是CryptoObject類的對象憨降,現(xiàn)在我們看一下這個對象怎么去實例化。
我們知道该酗,指紋識別的結(jié)果可靠性是非常重要的授药,我們肯定不希望認(rèn)證的過程被一個第三方以某種形式攻擊,因為我們引入指紋認(rèn)證的目的就是要提高安全性呜魄。但是悔叽,從理論角度來說,指紋認(rèn)證的過程是可能被第三方的中間件惡意攻擊的爵嗅,常見的攻擊的手段就是攔截和篡改指紋識別器提供的結(jié)果娇澎。這里我們可以提供CryptoObject對象給authenticate方法來避免這種形式的攻擊。
FingerprintManager.CryptoObject是基于Java加密API的一個包裝類睹晒,并且被FingerprintManager用來保證認(rèn)證結(jié)果的完整性趟庄。通常來講括细,用來加密指紋掃描結(jié)果的機(jī)制就是一個Javax.Crypto.Cipher對象。Cipher對象本身會使用由應(yīng)用調(diào)用Android keystore的API產(chǎn)生一個key來實現(xiàn)上面說道的保護(hù)功能戚啥。
為了理解這些類之間是怎么協(xié)同工作的奋单,這里我給出一個用于實例化CryptoObject對象的包裝類代碼,我們先看下這個代碼是怎么實現(xiàn)的猫十,然后再解釋一下為什么是這樣览濒。
public class CryptoObjectHelper
{
// This can be key name you want. Should be unique for the app.
static final String KEY_NAME = "com.createchance.android.sample.fingerprint_authentication_key";
// We always use this keystore on Android.
static final String KEYSTORE_NAME = "AndroidKeyStore";
// Should be no need to change these values.
static final String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
static final String TRANSFORMATION = KEY_ALGORITHM + "/" +
BLOCK_MODE + "/" +
ENCRYPTION_PADDING;
final KeyStore _keystore;
public CryptoObjectHelper() throws Exception
{
_keystore = KeyStore.getInstance(KEYSTORE_NAME);
_keystore.load(null);
}
public FingerprintManagerCompat.CryptoObject buildCryptoObject() throws Exception
{
Cipher cipher = createCipher(true);
return new FingerprintManagerCompat.CryptoObject(cipher);
}
Cipher createCipher(boolean retry) throws Exception
{
Key key = GetKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
try
{
cipher.init(Cipher.ENCRYPT_MODE | Cipher.DECRYPT_MODE, key);
} catch(KeyPermanentlyInvalidatedException e)
{
_keystore.deleteEntry(KEY_NAME);
if(retry)
{
createCipher(false);
} else
{
throw new Exception("Could not create the cipher for fingerprint authentication.", e);
}
}
return cipher;
}
Key GetKey() throws Exception
{
Key secretKey;
if(!_keystore.isKeyEntry(KEY_NAME))
{
CreateKey();
}
secretKey = _keystore.getKey(KEY_NAME, null);
return secretKey;
}
void CreateKey() throws Exception
{
KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, KEYSTORE_NAME);
KeyGenParameterSpec keyGenSpec =
new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(BLOCK_MODE)
.setEncryptionPaddings(ENCRYPTION_PADDING)
.setUserAuthenticationRequired(true)
.build();
keyGen.init(keyGenSpec);
keyGen.generateKey();
}
}
上面的類會針對每個CryptoObject對象都會新建一個Cipher對象,并且會使用由應(yīng)用生成的key炫彩。這個key的名字是使用KEY_NAME變量定義的匾七,這個名字應(yīng)該是保證唯一的,建議使用域名區(qū)別江兢。GetKey方法會嘗試使用Android Keystore的API來解析一個key(名字就是上面我們定義的)昨忆,如果key不存在的話,那就調(diào)用CreateKey方法新建一個key杉允。
cipher變量的實例化是通過調(diào)用Cipher.getInstance方法獲得的邑贴,這個方法接受一個transformation參數(shù),這個參數(shù)制定了數(shù)據(jù)怎么加密和解密叔磷。然后調(diào)用Cipher.init方法就會使用應(yīng)用的key來完成cipher對象的實例化工作拢驾。
這里需要強(qiáng)調(diào)一點,在以下情況下改基,android會認(rèn)為當(dāng)前key是無效的:
- 一個新的指紋image已經(jīng)注冊到系統(tǒng)中
- 當(dāng)前設(shè)備中的曾經(jīng)注冊過的指紋現(xiàn)在不存在了繁疤,可能是被全部刪除了
- 用戶關(guān)閉了屏幕鎖功能
- 用戶改變了屏幕鎖的方式
當(dāng)上面的情況發(fā)生的時候,Cipher.init方法都會拋出KeyPermanentlyInvalidatedException的異常秕狰,上面我的代碼中捕獲了這個異常稠腊,并且刪除了當(dāng)前無效的key,然后根據(jù)參數(shù)嘗試再次創(chuàng)建鸣哀。
上面的代碼中使用了android的KeyGenerator來創(chuàng)建一個key并且把它存儲在設(shè)備中架忌。KeyGenerator類會創(chuàng)建一個key,但是需要一些原始數(shù)據(jù)才能創(chuàng)建key我衬,這些原始的信息是通過KeyGenParameterSpec類的對象來提供的叹放。KeyGenerator類對象的實例化是使用它的工廠方法getInstance進(jìn)行的,從上面的代碼中我們可以看到這里使用的AES(Advanced Encryption Standard )加密算法的挠羔,AES會將數(shù)據(jù)分成幾個組井仰,然后針對幾個組進(jìn)行加密。
接下來破加,KeyGenParameterSpec的實例化是使用它的Builder方法俱恶,KeyGenParameterSpec.Builder封裝了以下重要的信息:
- key的名字
- key必須在加密和解密的時候是有效的
- 上面代碼中BLOCK_MODE被設(shè)置為Cipher Block Chaining也就是KeyProperties.BLOCK_MODE_CBC,這意味著每一個被AES切分的數(shù)據(jù)塊都與之前的數(shù)據(jù)塊進(jìn)行了異或運(yùn)算了,這樣的目的就是為了建立每個數(shù)據(jù)塊之間的依賴關(guān)系速那。
- CryptoObjectHelper類使用了PKSC7(Public Key Cryptography Standard #7)的方式去產(chǎn)生用于填充AES數(shù)據(jù)塊的字節(jié),這樣就是要保證每個數(shù)據(jù)塊的大小是等同的(因為需要異或計算還有方面算法進(jìn)行數(shù)據(jù)處理尿背,詳細(xì)可以查看AES的算法原理)端仰。
- setUserAuthenticationRequired(true)調(diào)用意味著在使用key之前用戶的身份需要被認(rèn)證。
每次KeyGenParameterSpec創(chuàng)建的時候田藐,他都被用來初始化KeyGenerator荔烧,這個對象會產(chǎn)生存儲在設(shè)備上的key。
比較簡單點理解就是汽久,指紋也是屬于密碼的一種鹤竭,需要我們對指紋的驗證過程進(jìn)行加密,而這個類就是用來對指紋的驗證過程加密景醇。