指紋識別-Android

指紋識別-Android

@(Android進(jìn)階資料)[Android, 學(xué)習(xí), 讀書筆記, Markdown]
指紋識別是在Android6.0之后新增的功能,所以在使用的時(shí)候首先要判斷用戶的系統(tǒng)版本是否支持指紋識別。另外,實(shí)際開發(fā)場景中,使用指紋的主要場景有兩種:

  • 純本地使用。即用戶在本地完成指紋識別后,不需要將指紋信息傳遞給后臺。
  • 與后臺交互。用戶在本地完成指紋識別后,需要將指紋信息傳遞給后臺。

由于使用指紋識別功能需要一個(gè)加密對象(CryptoObject),該對象一般是由對稱加密或者非對稱加密獲得。上述兩種應(yīng)用場景的實(shí)現(xiàn)大同小異,主要區(qū)別在于加密過程中密鑰的創(chuàng)建和使用,一般來說,純本地的指紋識別功能,只需要對稱加密即可;而與后臺則需要使用非對稱加密:將私鑰用于本地指紋識別,識別成功后將加密信息傳給后臺,后臺用公鑰解密,以獲得用戶信息。

對稱加密、非對稱加密和簽名

在正式使用指紋識別功能之前,有必要先了解一下對稱加密和非對稱加密的相關(guān)內(nèi)容。

  • 對稱加密:所謂對稱,就是采用這種加密方法的雙方使用方式用同樣的密鑰進(jìn)行加密和解密。密鑰是控制加密及解密過程的指令。算法是一組規(guī)則,規(guī)定如何進(jìn)行加密和解密。因此加密的安全性不僅取決于加密算法本身,密鑰管理的安全性更是重要。因?yàn)榧用芎徒饷芏际褂猛粋€(gè)密鑰,如何把密鑰安全地傳遞到解密者手上就成了必須要解決的問題。
  • 非對稱加密:非對稱加密算法需要兩個(gè)密鑰:公開密鑰(publickey)和私有密鑰(privatekey)。公開密鑰與私有密鑰是一對,如果用公開密鑰對數(shù)據(jù)進(jìn)行加密,只有用對應(yīng)的私有密鑰才能解密;如果用私有密鑰對數(shù)據(jù)進(jìn)行加密,那么只有用對應(yīng)的公開密鑰才能解密。因?yàn)榧用芎徒饷苁褂玫氖莾蓚€(gè)不同的密鑰,所以這種算法叫作非對稱加密算法。 非對稱加密算法實(shí)現(xiàn)機(jī)密信息交換的基本過程是:甲方生成一對密鑰并將其中的一把作為公用密鑰向其它方公開;得到該公用密鑰的乙方使用該密鑰對機(jī)密信息進(jìn)行加密后再發(fā)送給甲方;甲方再用自己保存的另一把專用密鑰對加密后的信息進(jìn)行解密。
  • 簽名:在信息的后面再加上一段內(nèi)容,可以證明信息沒有被修改過。一般是對信息做一個(gè)hash計(jì)算得到一個(gè)hash值,注意,這個(gè)過程是不可逆的,也就是說無法通過hash值得出原來的信息內(nèi)容。在把信息發(fā)送出去時(shí),把這個(gè)hash值加密后做為一個(gè)簽名和信息一起發(fā)出去。

由以上內(nèi)容可以了解到,對稱加密和非對稱加密的特點(diǎn)如下:

  • 對稱加密的優(yōu)點(diǎn)是速度快,適合于本地?cái)?shù)據(jù)和本地?cái)?shù)據(jù)庫的加密,安全性不如非對稱加密。常見的對稱加密算法有DES3DESAESBlowfishIDEARC5RC6
  • 非對稱加密的安全性比較高,適合對需要網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)進(jìn)行加密,速度不如對稱加密。非對稱加密應(yīng)用于SSH, HTTPS, TLS電子證書電子簽名電子身份證等等

指紋識別的對稱加密實(shí)現(xiàn)

使用指紋識別的對稱加密功能的主要流程如下:

  1. 使用 KeyGenerator 創(chuàng)建一個(gè)對稱密鑰,存放在 KeyStore 里。
  2. 設(shè)置 KeyGenParameterSpec.Builder.setUserAuthenticationRequired() 為true
  3. 使用創(chuàng)建好的對稱密鑰初始化一個(gè)Cipher對象,并用該對象調(diào)用 FingerprintManager.authenticate() 方法啟動(dòng)指紋傳感器并開始監(jiān)聽。
  4. 重寫 FingerprintManager.AuthenticationCallback 的幾個(gè)回調(diào)方法,以處理指紋識別成功(onAuthenticationSucceeded())、失敗(onAuthenticationFailed()onAuthenticationError())等情況。

創(chuàng)建密鑰

創(chuàng)建密鑰要涉及到兩個(gè)類: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 類。獲取一個(gè) 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 對象后,就可以生成一個(gè) 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 對象是一個(gè)按照一定的加密規(guī)則,將數(shù)據(jù)進(jìn)行加密后的一個(gè)對象。調(diào)用指紋識別功能需要使用到這個(gè)對象。創(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);
}

使用指紋識別功能

真正到了使用指紋識別功能的時(shí)候,你會發(fā)現(xiàn)其實(shí)很簡單,只是調(diào)用 FingerprintManager 類的的方法authenticate()而已,然后系統(tǒng)會有相應(yīng)的回調(diào)反饋給我們,該方法如下:

public void authenticate(CryptoObject crypto, CancellationSignal cancel, int flags, AuthenticationCallback callback, Handler handler)

該方法的幾個(gè)參數(shù)解釋如下:

  • 第一個(gè)參數(shù)是一個(gè)加密對象。還記得之前我們大費(fèi)周章地創(chuàng)建和初始化的Cipher對象嗎?這里的 CryptoObject 對象就是使用 Cipher 對象創(chuàng)建創(chuàng)建出來的:new FingerprintManager.CryptoObject(cipher)
  • 第二個(gè)參數(shù)是一個(gè) CancellationSignal 對象,該對象提供了取消操作的能力。創(chuàng)建該對象也很簡單,使用 new CancellationSignal() 就可以了。
  • 第三個(gè)參數(shù)是一個(gè)標(biāo)志,默認(rèn)為0。
  • 第四個(gè)參數(shù)是 AuthenticationCallback 對象,它本身是 FingerprintManager 類里面的一個(gè)抽象類。該類提供了指紋識別的幾個(gè)回調(diào)方法,包括指紋識別成功、失敗等。需要我們重寫。
  • 最后一個(gè) 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)就會啟動(dòng)指紋傳感器,并開始掃描。這時(shí)候根據(jù)掃描結(jié)果,會通過FingerprintManager.AuthenticationCallback類返回幾個(gè)回調(diào)方法:

// 成功
onAuthenticationSucceeded()
// 失敗
onAuthenticationFaile()
// 錯(cuò)誤
onAuthenticationError()

一般我們需要重寫這幾個(gè)方法,以實(shí)現(xiàn)我們的功能。關(guān)于onAuthenticationFaile()和onAuthenticationError()的區(qū)別,后面會講到。

實(shí)際應(yīng)用中的注意事項(xiàng)

判斷用戶是否可以使用指紋識別功能

一般來說,為了增加安全性,要求用戶在手機(jī)的“設(shè)置”中開啟了密碼鎖屏功能。當(dāng)然,使用指紋解鎖的前提是至少錄入了一個(gè)指紋。
// 如果沒有設(shè)置密碼鎖屏,則不能使用指紋識別

if (!keyguardManager.isKeyguardSecure()) {
    Toast.makeText(this, "請?jiān)谠O(shè)置界面開啟密碼鎖屏功能",
            Toast.LENGTH_LONG).show();
}
// 如果沒有錄入指紋,則不能使用指紋識別
if (!fingerprintManager.hasEnrolledFingerprints()) {
    Toast.makeText(this, "您還沒有錄入指紋, 請?jiān)谠O(shè)置界面錄入至少一個(gè)指紋",
            Toast.LENGTH_LONG).show();
}

這里用到了兩個(gè)類:KeyguardManagerFingerprintManager,前者是屏幕保護(hù)的相關(guān)類。后者是指紋識別的核心類。

關(guān)于指紋識別回調(diào)方法

前面說到AuthenticationCallback類里面的幾個(gè)回調(diào)方法,其中有三個(gè)是我們開發(fā)中需要用到的:

onAuthenticationError()
onAuthenticationSucceeded()
onAuthenticationFailed()

關(guān)于這三個(gè)回調(diào)方法,有幾點(diǎn)需要注意的:

  1. 當(dāng)指紋識別失敗后,會調(diào)用onAuthenticationFailed()方法,這時(shí)候指紋傳感器并沒有關(guān)閉,系統(tǒng)給我們提供了5次重試機(jī)會,也就是說,連續(xù)調(diào)用了5次onAuthenticationFailed()方法后,會調(diào)用onAuthenticationError()方法。
  2. 當(dāng)系統(tǒng)調(diào)用了onAuthenticationError()onAuthenticationSucceeded()后,傳感器會關(guān)閉,只有我們重新授權(quán),再次調(diào)用authenticate()方法后才能繼續(xù)使用指紋識別功能。
  3. 當(dāng)系統(tǒng)回調(diào)了onAuthenticationError()方法關(guān)閉傳感器后,這種情況下再次調(diào)用authenticate()會有一段時(shí)間的禁用期,也就是說這段時(shí)間里是無法再次使用指紋識別的。當(dāng)然,具體的禁用時(shí)間由手機(jī)廠商的系統(tǒng)不同而有略微差別,有的是1分鐘,有的是30秒等等。而且,由于手機(jī)廠商的系統(tǒng)區(qū)別,有些系統(tǒng)上調(diào)用了onAuthenticationError()后,在禁用時(shí)間內(nèi),其他APP里面的指紋識別功能也無法使用,甚至系統(tǒng)的指紋解鎖功能也無法使用。而有的系統(tǒng)上,在禁用時(shí)間內(nèi)調(diào)用其他APP的指紋解鎖功能,或者系統(tǒng)的指紋解鎖功能,就能立即重置指紋識別功能。

示例代碼

最后, Android Sample 里面關(guān)于指紋的示例代碼地址如下:

完整代碼

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) {
            //但多次指紋密碼驗(yàn)證錯(cuò)誤后,進(jìn)入此方法;并且,不能短時(shí)間內(nèi)調(diào)用指紋驗(yàn)證
            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, "請進(jìn)行指紋識別", Toast.LENGTH_LONG).show();
                    Log(TAG, "keyi");
                    startListening(null);
                }
                break;
        }
    }

    public boolean isFinger() {

        //android studio 上,沒有這個(gè)會報(bào)錯(cuò)
        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 上,沒有這個(gè)會報(bào)錯(cuò)
        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);
    }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 最近項(xiàng)目需要使用到指紋識別的功能,查閱了相關(guān)資料后,整理成此文。 指紋識別是在Android 6.0之后新增的功能...
    湫水長天閱讀 3,769評論 2 46
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • 目錄 準(zhǔn)備 分析2.1. 三次握手2.2. 創(chuàng)建 HTTP 代理(非必要)2.3. TLS/SSL 握手2.4. ...
    RunAlgorithm閱讀 38,540評論 12 117
  • 本文主要介紹移動(dòng)端的加解密算法的分類、其優(yōu)缺點(diǎn)特性及應(yīng)用,幫助讀者由淺入深地了解和選擇加解密算法。文中會包含算法的...
    蘋果粉閱讀 11,553評論 5 29
  • 觸手可及之處, 天還是原來的天, 我還是原來的我。 匆匆歲月, 變化的是人心, 不變的是心境。 起風(fēng)了, 天不再是...
    落花有春閱讀 240評論 3 1