指紋識別-Android
@(Android進階資料)[Android, 學(xué)習(xí), 讀書筆記, Markdown]
指紋識別是在Android6.0之后新增的功能,所以在使用的時候首先要判斷用戶的系統(tǒng)版本是否支持指紋識別雕凹。另外,實際開發(fā)場景中,使用指紋的主要場景有兩種:
- 純本地使用庆寺。即用戶在本地完成指紋識別后,不需要將指紋信息傳遞給后臺。
- 與后臺交互。用戶在本地完成指紋識別后,需要將指紋信息傳遞給后臺趴泌。
由于使用指紋識別功能需要一個加密對象(CryptoObject),該對象一般是由對稱加密或者非對稱加密獲得泄私。上述兩種應(yīng)用場景的實現(xiàn)大同小異,主要區(qū)別在于加密過程中密鑰的創(chuàng)建和使用,一般來說,純本地的指紋識別功能,只需要對稱加密即可粥烁;而與后臺則需要使用非對稱加密:將私鑰用于本地指紋識別,識別成功后將加密信息傳給后臺,后臺用公鑰解密,以獲得用戶信息见秽。
對稱加密、非對稱加密和簽名
在正式使用指紋識別功能之前,有必要先了解一下對稱加密和非對稱加密的相關(guān)內(nèi)容是嗜。
- 對稱加密:所謂對稱愈案,就是采用這種加密方法的雙方使用方式用同樣的密鑰進行加密和解密。密鑰是控制加密及解密過程的指令鹅搪。算法是一組規(guī)則站绪,規(guī)定如何進行加密和解密。因此加密的安全性不僅取決于加密算法本身丽柿,密鑰管理的安全性更是重要恢准。因為加密和解密都使用同一個密鑰,如何把密鑰安全地傳遞到解密者手上就成了必須要解決的問題甫题。
- 非對稱加密:非對稱加密算法需要兩個密鑰:公開密鑰(publickey)和私有密鑰(privatekey)馁筐。公開密鑰與私有密鑰是一對,如果用公開密鑰對數(shù)據(jù)進行加密坠非,只有用對應(yīng)的私有密鑰才能解密敏沉;如果用私有密鑰對數(shù)據(jù)進行加密,那么只有用對應(yīng)的公開密鑰才能解密炎码。因為加密和解密使用的是兩個不同的密鑰盟迟,所以這種算法叫作非對稱加密算法。 非對稱加密算法實現(xiàn)機密信息交換的基本過程是:甲方生成一對密鑰并將其中的一把作為公用密鑰向其它方公開潦闲;得到該公用密鑰的乙方使用該密鑰對機密信息進行加密后再發(fā)送給甲方攒菠;甲方再用自己保存的另一把專用密鑰對加密后的信息進行解密。
- 簽名:在信息的后面再加上一段內(nèi)容歉闰,可以證明信息沒有被修改過辖众。一般是對信息做一個hash計算得到一個hash值,注意和敬,這個過程是不可逆的凹炸,也就是說無法通過hash值得出原來的信息內(nèi)容。在把信息發(fā)送出去時概龄,把這個hash值加密后做為一個簽名和信息一起發(fā)出去还惠。
由以上內(nèi)容可以了解到饲握,對稱加密和非對稱加密的特點如下:
- 對稱加密的優(yōu)點是速度快私杜,適合于本地數(shù)據(jù)和本地數(shù)據(jù)庫的加密,安全性不如非對稱加密救欧。常見的對稱加密算法有DES衰粹、3DES、AES笆怠、Blowfish铝耻、IDEA、RC5、RC6瓢捉。
- 非對稱加密的安全性比較高频丘,適合對需要網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)進行加密,速度不如對稱加密泡态。非對稱加密應(yīng)用于SSH, HTTPS, TLS搂漠,電子證書,電子簽名某弦,電子身份證等等
指紋識別的對稱加密實現(xiàn)
使用指紋識別的對稱加密功能的主要流程如下:
- 使用
KeyGenerator
創(chuàng)建一個對稱密鑰桐汤,存放在KeyStore
里。 - 設(shè)置
KeyGenParameterSpec.Builder.setUserAuthenticationRequired()
為true - 使用創(chuàng)建好的對稱密鑰初始化一個
Cipher
對象靶壮,并用該對象調(diào)用 FingerprintManager.authenticate() 方法啟動指紋傳感器并開始監(jiān)聽怔毛。 - 重寫
FingerprintManager.AuthenticationCallback
的幾個回調(diào)方法,以處理指紋識別成功(onAuthenticationSucceeded()
)腾降、失敿鸲取(onAuthenticationFailed()
和onAuthenticationError()
)等情況。
創(chuàng)建密鑰
創(chuàng)建密鑰要涉及到兩個類:KeyStore 和 KeyGenerator蜂莉。
KeyStore 是用于存儲蜡娶、獲取密鑰(Key)的容器,獲取 KeyStore的方法如下:
try {
mKeyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (KeyStoreException e) {
throw new RuntimeException("Failed to get an instance of KeyStore", e);
}
而生成 Key映穗,如果是對稱加密窖张,就需要 KeyGenerator 類。獲取一個 KeyGenerator 對象比較簡單蚁滋,方法如下:
// 對稱加密宿接, 創(chuàng)建 KeyGenerator 對象
try {
mKeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,"AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
}
獲得 KeyGenerator 對象后,就可以生成一個 Key 了:
try {
keyStore.load(null);
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(defaultKeyName,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setInvalidatedByBiometricEnrollment(true);
}
keyGenerator.init(builder.build());
keyGenerator.generateKey();
} catch (CertificateException | NoSuchAlgorithmException | IOException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
創(chuàng)建并初始化 Cipher 對象
Cipher 對象是一個按照一定的加密規(guī)則辕录,將數(shù)據(jù)進行加密后的一個對象睦霎。調(diào)用指紋識別功能需要使用到這個對象。創(chuàng)建 Cipher 對象很簡單走诞,如同下面代碼那樣:
Cipher defaultCipher;
try {
defaultCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("創(chuàng)建Cipher對象失敗", e);
}
然后使用剛才創(chuàng)建好的密鑰副女,初始化 Cipher 對象:
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(keyName, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyStoreException | InvalidKeyException e) {
throw new RuntimeException("初始化 cipher 失敗", e);
}
使用指紋識別功能
真正到了使用指紋識別功能的時候,你會發(fā)現(xiàn)其實很簡單蚣旱,只是調(diào)用 FingerprintManager
類的的方法authenticate()
而已碑幅,然后系統(tǒng)會有相應(yīng)的回調(diào)反饋給我們,該方法如下:
public void authenticate(CryptoObject crypto, CancellationSignal cancel, int flags, AuthenticationCallback callback, Handler handler)
該方法的幾個參數(shù)解釋如下:
- 第一個參數(shù)是一個加密對象塞绿。還記得之前我們大費周章地創(chuàng)建和初始化的Cipher對象嗎沟涨?這里的
CryptoObject
對象就是使用Cipher
對象創(chuàng)建創(chuàng)建出來的:new FingerprintManager.CryptoObject(cipher)
。 - 第二個參數(shù)是一個
CancellationSignal
對象异吻,該對象提供了取消操作的能力裹赴。創(chuàng)建該對象也很簡單,使用new CancellationSignal()
就可以了。 - 第三個參數(shù)是一個標志棋返,默認為0延都。
- 第四個參數(shù)是
AuthenticationCallback
對象,它本身是FingerprintManager
類里面的一個抽象類睛竣。該類提供了指紋識別的幾個回調(diào)方法窄潭,包括指紋識別成功、失敗等酵颁。需要我們重寫嫉你。 - 最后一個 Handler,可以用于處理回調(diào)事件躏惋,可以傳null幽污。
完成指紋識別后,還要記得將 AuthenticationCallback 關(guān)閉掉:
public void stopListening() {
if (cancellationSignal != null) {
selfCancelled = true;
cancellationSignal.cancel();
cancellationSignal = null;
}
}
重寫回調(diào)方法
調(diào)用了 authenticate()
方法后簿姨,系統(tǒng)就會啟動指紋傳感器距误,并開始掃描。這時候根據(jù)掃描結(jié)果扁位,會通過FingerprintManager.AuthenticationCallback類返回幾個回調(diào)方法:
// 成功
onAuthenticationSucceeded()
// 失敗
onAuthenticationFaile()
// 錯誤
onAuthenticationError()
一般我們需要重寫這幾個方法准潭,以實現(xiàn)我們的功能。關(guān)于onAuthenticationFaile()和onAuthenticationError()的區(qū)別域仇,后面會講到刑然。
實際應(yīng)用中的注意事項
判斷用戶是否可以使用指紋識別功能
一般來說,為了增加安全性暇务,要求用戶在手機的“設(shè)置”中開啟了密碼鎖屏功能泼掠。當然,使用指紋解鎖的前提是至少錄入了一個指紋垦细。
// 如果沒有設(shè)置密碼鎖屏择镇,則不能使用指紋識別
if (!keyguardManager.isKeyguardSecure()) {
Toast.makeText(this, "請在設(shè)置界面開啟密碼鎖屏功能",
Toast.LENGTH_LONG).show();
}
// 如果沒有錄入指紋,則不能使用指紋識別
if (!fingerprintManager.hasEnrolledFingerprints()) {
Toast.makeText(this, "您還沒有錄入指紋, 請在設(shè)置界面錄入至少一個指紋",
Toast.LENGTH_LONG).show();
}
這里用到了兩個類:KeyguardManager
和 FingerprintManager
括改,前者是屏幕保護的相關(guān)類腻豌。后者是指紋識別的核心類。
關(guān)于指紋識別回調(diào)方法
前面說到AuthenticationCallback
類里面的幾個回調(diào)方法嘱能,其中有三個是我們開發(fā)中需要用到的:
onAuthenticationError()
onAuthenticationSucceeded()
onAuthenticationFailed()
關(guān)于這三個回調(diào)方法吝梅,有幾點需要注意的:
- 當指紋識別失敗后,會調(diào)用
onAuthenticationFailed()
方法焰檩,這時候指紋傳感器并沒有關(guān)閉憔涉,系統(tǒng)給我們提供了5次重試機會订框,也就是說析苫,連續(xù)調(diào)用了5次onAuthenticationFailed()
方法后,會調(diào)用onAuthenticationError()
方法。 - 當系統(tǒng)調(diào)用了
onAuthenticationError()
和onAuthenticationSucceeded()
后衩侥,傳感器會關(guān)閉国旷,只有我們重新授權(quán),再次調(diào)用authenticate()方法后才能繼續(xù)使用指紋識別功能茫死。 - 當系統(tǒng)回調(diào)了
onAuthenticationError()
方法關(guān)閉傳感器后跪但,這種情況下再次調(diào)用authenticate()
會有一段時間的禁用期,也就是說這段時間里是無法再次使用指紋識別的峦萎。當然屡久,具體的禁用時間由手機廠商的系統(tǒng)不同而有略微差別,有的是1分鐘爱榔,有的是30秒等等被环。而且,由于手機廠商的系統(tǒng)區(qū)別详幽,有些系統(tǒng)上調(diào)用了onAuthenticationError()
后筛欢,在禁用時間內(nèi),其他APP里面的指紋識別功能也無法使用唇聘,甚至系統(tǒng)的指紋解鎖功能也無法使用版姑。而有的系統(tǒng)上,在禁用時間內(nèi)調(diào)用其他APP的指紋解鎖功能迟郎,或者系統(tǒng)的指紋解鎖功能剥险,就能立即重置指紋識別功能。
示例代碼
最后宪肖, Android Sample 里面關(guān)于指紋的示例代碼地址如下:
- 對稱加密方式:android-FingerprintDialog
- 非對稱加密方式:android-AsymmetricFingerprintDialog
- 參考鏈接: New in Android Samples: Authenticating to remote servers using the Fingerprint API
完整代碼
package com.zichenjiao.testapp;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
@TargetApi(Build.VERSION_CODES.M)
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private final static int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 0;
Button testfingerprint;
CancellationSignal mCancellationSignal = new CancellationSignal();
FingerprintManager manager;
KeyguardManager mKeyManager;
//回調(diào)方法
FingerprintManager.AuthenticationCallback mSelfCancelled = new FingerprintManager.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
//但多次指紋密碼驗證錯誤后炒嘲,進入此方法;并且匈庭,不能短時間內(nèi)調(diào)用指紋驗證
Toast.makeText(MainActivity.this, errString, Toast.LENGTH_SHORT).show();
showAuthenticationScreen();
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
Toast.makeText(MainActivity.this, helpString, Toast.LENGTH_SHORT).show();
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
Toast.makeText(MainActivity.this, "指紋識別成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onAuthenticationFailed() {
Toast.makeText(MainActivity.this, "指紋識別失敗", Toast.LENGTH_SHORT).show();
}
};
private String TAG = "TAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testfingerprint = (Button) findViewById(R.id.testfingerprint);
testfingerprint.setOnClickListener(this);
//初始化指紋識別管理器
manager = (FingerprintManager) this.getSystemService(Context.FINGERPRINT_SERVICE);
mKeyManager = (KeyguardManager) this.getSystemService(Context.KEYGUARD_SERVICE);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.testfingerprint://測試指紋識別
if (isFinger()) {
Toast.makeText(MainActivity.this, "請進行指紋識別", Toast.LENGTH_LONG).show();
Log(TAG, "keyi");
startListening(null);
}
break;
}
}
public boolean isFinger() {
//android studio 上夫凸,沒有這個會報錯
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "沒有指紋識別權(quán)限", Toast.LENGTH_SHORT).show();
return false;
}
Log(TAG, "有指紋權(quán)限");
//判斷硬件是否支持指紋識別
if (!manager.isHardwareDetected()) {
Toast.makeText(this, "沒有指紋識別模塊", Toast.LENGTH_SHORT).show();
return false;
}
Log(TAG, "有指紋模塊");
//判斷 是否開啟鎖屏密碼
if (!mKeyManager.isKeyguardSecure()) {
Toast.makeText(this, "沒有開啟鎖屏密碼", Toast.LENGTH_SHORT).show();
return false;
}
Log(TAG, "已開啟鎖屏密碼");
//判斷是否有指紋錄入
if (!manager.hasEnrolledFingerprints()) {
Toast.makeText(this, "沒有錄入指紋", Toast.LENGTH_SHORT).show();
return false;
}
Log(TAG, "已錄入指紋");
return true;
}
public void startListening(FingerprintManager.CryptoObject cryptoObject) {
//android studio 上,沒有這個會報錯
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "沒有指紋識別權(quán)限", Toast.LENGTH_SHORT).show();
return;
}
manager.authenticate(cryptoObject, mCancellationSignal, 0, mSelfCancelled, null);
}
/**
* 鎖屏密碼
*/
private void showAuthenticationScreen() {
Intent intent = mKeyManager.createConfirmDeviceCredentialIntent("finger", "測試指紋識別");
if (intent != null) {
startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
// Challenge completed, proceed with using cipher
if (resultCode == RESULT_OK) {
Toast.makeText(this, "識別成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "識別失敗", Toast.LENGTH_SHORT).show();
}
}
}
private void Log(String tag, String msg) {
Log.d(tag, msg);
}
}