Android中的指紋識別

轉載請註明出處:http://blog.csdn.net/wl9739/article/details/52444671css

評論中很是多朋友反映,依據我給出的方案,拿不到指紋信息這個問題,在這裏統一說明一下。html

首先,這篇文章中涉及到的代碼,我在一部魅族手機和一部三星手機上進行測試過,能獲取到信息。java

其它手機機型我沒有測試,不知道具體狀況。android

其次,我在博客中也說明了。在不一樣手機廠商的定製系統裏面獲取到的指紋信息很是多是不一樣的,我測試的魅族手機和三星手機返回的信息格式就不同。依照本文的方法獲取到的指紋信息是一個比較雞肋的功能,做用有限。我眼下也沒有能力給出一個完美的解決方式,假設有哪位找到了完美的解決方式,歡迎分享。git

另外,指紋信息相關 API 是用 @hide 修飾的,這說明 Google 官方是不但願開發人員使用這類 API 。使用這類 API 可能會有風險。如方法參數改變、在興許版本號中被移除等等。github

所以。對於指紋識別這一塊。建議讀者使用本文中說起的指紋識別功能,儘量避免使用本文後面說起的「獲取指紋信息」這一功能。算法

近期項目需要使用到指紋識別的功能,查閱了相關資料後,整理成此文。數據庫

指紋識別是在Android 6.0以後新增的功能,所以在使用的時候需要先推斷用戶手機的系統版本號是否支持指紋識別。另外。實際開發場景中,使用指紋的主要場景有兩種:安全

  • 純本地使用。

    即用戶在本地完畢指紋識別後,不需要將指紋的相關信息給後臺。markdown

  • 與後臺交互。用戶在本地完畢指紋識別後,需要將指紋相關的信息傳給後臺。

因爲使用指紋識別功能需要一個加密對象(CryptoObject)該對象一般是由對稱加密或者非對稱加密得到。上述兩種開發場景的實現大同小異。主要差異在於加密過程當中密鑰的建立和使用,通常來講。純本地的使用指紋識別功能。僅僅需要對稱加密就能夠;而與後臺交互則需要使用非對稱加密:將私鑰用於本地指紋識別。識別成功後將加密信息傳給後臺,後臺開發人員用公鑰解密。以得到用戶信息。

如下先簡介一下對稱加密和非對稱加密的相關概念,而後對兩種開發方式的實現分別進行解說。

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

在正式使用指紋識別功能以前,有必要先了解一下對稱加密和非對稱加密的相關內容。

  • 對稱加密:所謂對稱,就是採用這樣的加密方法的兩方使用方式用相同的密鑰進行加密和解密。密鑰是控制加密及解密過程的指令。算法是一組規則。規定怎樣進行加密和解密。所以加密的安全性不只取決於加密算法自己,密鑰管理的安全性更是重要。因爲加密和解密都使用同一個密鑰,怎樣把密鑰安全地傳遞到解密者手上就成了必需要解決的問題。

  • 非對稱加密:非對稱加密算法需要兩個密鑰:公開密鑰(publickey)和私有密鑰(privatekey)。公開密鑰與私有密鑰是一對。假設用公開密鑰對數據進行加密,僅僅實用相應的私有密鑰才幹解密;假設用私有密鑰對數據進行加密,那麼僅僅實用相應的公開密鑰才幹解密。因爲加密和解密使用的是兩個不一樣的密鑰,因此這樣的算法叫做非對稱加密算法。

    非對稱加密算法實現機密信息交換的基本過程是:甲方生成一對密鑰並將當中的一把做爲公用密鑰向其它方公開;獲得該公用密鑰的乙方使用該密鑰對機密信息進行加密後再發送給甲方;甲方再用本身保存的還有一把專用密鑰對加密後的信息進行解密。

  • 簽名:在信息的後面再加上一段內容。可以證實信息沒有被改動過。一般是對信息作一個hash計算獲得一個hash值。注意,這個過程是不可逆的。也就是說沒法經過hash值得出原來的信息內容。在把信息發送出去時,把這個hash值加密後作爲一個簽名和信息一塊兒發出去。

由以上內容可以瞭解到。對稱加密和非對稱加密的特色例如如下:

  • 對稱加密的長處是速度快,適合於本地數據和本地數據庫的加密。安全性不如非對稱加密。常見的對稱加密算法有DES、3DES、AES、Blowfish、IDEA、RC五、RC6。
  • 非對稱加密的安全性比較高,適合對需要網絡傳輸的數據進行加密,速度不如對稱加密。非對稱加密應用於SSH, HTTPS, TLS,電子證書。電子簽名,電子身份證等等

指紋識別的對稱加密實現

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

  1. 使用 KeyGenerator 建立一個對稱密鑰,存放在 KeyStore 裏。
  2. 設置 KeyGenParameterSpec.Builder.setUserAuthenticationRequired() 爲true,
  3. 使用建立好的對稱密鑰初始化一個Cipher對象,並用該對象調用 FingerprintManager.authenticate() 方法啓動指紋傳感器並開始監聽。
  4. 重寫 FingerprintManager.AuthenticationCallback 的幾個回調方法,以處理指紋識別成功(onAuthenticationSucceeded())、失敗(onAuthenticationFailed()onAuthenticationError())等狀況。

建立密鑰

建立密鑰要涉及到兩個類: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 對象比較簡單。方法例如如下:

// 對稱加密, 建立 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();
}

關於 KeyStrore 和 KeyGenerator 的相關介紹,推薦閱讀:Android KeyStore + FingerprintManager 存儲password

建立並初始化 Cipher 對象

Cipher 對象是一個依照必定的加密規則,將數據進行加密後的一個對象。調用指紋識別功能需要使用到這個對象。建立 Cipher 對象很是easy,如同如下代碼那樣:

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("建立Cipher對象失敗", e);
}

而後使用剛纔建立好的密鑰,初始化 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);
}

使用指紋識別功能

真正到了使用指紋識別功能的時候,你會發現事實上很是easy,僅僅是調用 FingerprintManager 類的的方法authenticate()而已,而後系統會有相應的回調反饋給咱們。該方法例如如下:

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

該方法的幾個參數解釋例如如下:

  • 第一個參數是一個加密對象。

    還記得以前咱們大費周章地建立和初始化的Cipher對象嗎?這裏的 CryptoObject 對象就是使用 Cipher 對象建立建立出來的:new FingerprintManager.CryptoObject(cipher)

  • 第二個參數是一個 CancellationSignal 對象,該對象提供了取消操做的能力。

    建立該對象也很是easy。使用 new CancellationSignal() 就可以了。

  • 第三個參數是一個標誌,默以爲0。
  • 第四個參數是 AuthenticationCallback 對象,它自己是 FingerprintManager 類裏面的一個抽象類。該類提供了指紋識別的幾個回調方法,包含指紋識別成功、失敗等。需要咱們重寫。
  • 最後一個 Handler,可以用於處理回調事件,可以傳null。

完畢指紋識別後。還要記得將 AuthenticationCallback 關閉掉:

public void stopListening() {
    if (cancellationSignal != null) {
        selfCancelled = true;
        cancellationSignal.cancel();
        cancellationSignal = null;
    }
}

重寫回調方法

調用了 authenticate() 方法後,系統就會啓動指紋傳感器。並開始掃描。

這時候依據掃描結果,會經過FingerprintManager.AuthenticationCallback類返回幾個回調方法:

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

通常咱們需要重寫這幾個方法。以實現咱們的功能。關於onAuthenticationFaile()onAuthenticationError()的差異,後面會講到。

指紋識別的非對稱加密實現

事實上流程和上面的流程差點兒相同:

  1. 使用 KeyPairGenerator 建立一個非對稱密鑰。
  2. 使用建立好的私鑰進行簽名,使用該簽名建立一個加密對象,並將該對象做爲 FingerprintManager.authenticate() 方法的一個參數,啓動指紋傳感器並開始監聽。
  3. 重寫 FingerprintManager.AuthenticationCallback 類的幾個回調方法,以處理指紋識別成功(onAuthenticationSucceeded())、失敗(onAuthenticationFailed()onAuthenticationError())等狀況。

可以看見,指紋識別的非對稱加密方式和對稱加密方式的實現流程是差點兒相同的。它們之間最明顯的差異是在於密鑰的生成與使用。

建立密鑰

這裏要使用 KeyPairGenerator 來建立一組非對稱密鑰。首先是獲取 KeyPairGenerator 對象:

// 非對稱加密,建立 KeyPairGenerator 對象
try {
    mKeyPairGenerator =  KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
    throw new RuntimeException("Failed to get an instance of KeyPairGenerator", e);
}

獲得了 KeyPairGenerator 對象後,就可以建立 KeyPair(密鑰對)了:

try {
    // Set the alias of the entry in Android KeyStore where the key will appear
    // and the constrains (purposes) in the constructor of the Builder
    mKeyPairGenerator.initialize(
            new KeyGenParameterSpec.Builder(KEY_NAME,
                    KeyProperties.PURPOSE_SIGN)
                    .setDigests(KeyProperties.DIGEST_SHA256)
                    .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
                    // Require the user to authenticate with a fingerprint to authorize
                    // every use of the private key
                    .setUserAuthenticationRequired(true)
                    .build());
    mKeyPairGenerator.generateKeyPair();
} catch (InvalidAlgorithmParameterException e) {
    throw new RuntimeException(e);
}

簽名

指紋識別的對稱加密實現中使用了Cipher對象來建立CryptoObject對象,而在這裏,咱們將會使用私鑰進行簽名,用簽名對象來建立CryptoObject對象:

// 使用私鑰簽名
try {
    mKeyStore.load(null);
    PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_NAME, null);
    mSignature.initSign(key);
    return true;
} catch (KeyPermanentlyInvalidatedException e) {
    return false;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
        | NoSuchAlgorithmException | InvalidKeyException e) {
    throw new RuntimeException("Failed to init Cipher", e);
}

相同的,調用new FingerprintManager.CryptoObject(mSignature)方法建立一個CryptoObject對象。

調用指紋識別方法

這裏的用法和前面「指紋識別的對稱加密實現」中的調用方法是同樣的,都是調用FingerprintManager.authenticate()方法。這裏就再也不敘述。

監聽回調

監聽回調也和以前的類似,惟一不一樣的是。咱們在識別成功後需要和後臺進行交互,也就是onAuthenticationSucceeded()中處理的邏輯不同。

實際應用中的注意事項

推斷用戶可否夠使用指紋識別功能

通常來講,爲了添加安全性。要求用戶在手機的「設置」中開啓了password鎖屏功能。固然,使用指紋解鎖的前提是至少錄入了一個指紋。

// 假設沒有設置password鎖屏,則不能使用指紋識別
if (!keyguardManager.isKeyguardSecure()) {
    Toast.makeText(this, "請在設置界面開啓password鎖屏功能",
            Toast.LENGTH_LONG).show();
}
// 假設沒有錄入指紋,則不能使用指紋識別
if (!fingerprintManager.hasEnrolledFingerprints()) {
    Toast.makeText(this, "您尚未錄入指紋, 請在設置界面錄入至少一個指紋",
            Toast.LENGTH_LONG).show();
}

這裏用到了兩個類:KeyguardManagerFingerprintManager,前者是屏幕保護的相關類。後者是指紋識別的核心類。

關於指紋識別回調方法

前面說到AuthenticationCallback類裏面的幾個回調方法,當中有三個是咱們開發中需要用到的:

onAuthenticationError()
onAuthenticationSucceeded()
onAuthenticationFailed()

關於這三個回調方法,有幾點需要注意的:

  1. 當指紋識別失敗後,會調用onAuthenticationFailed()方法,這時候指紋傳感器並無關閉,系統給咱們提供了5次重試機會。也就是說,連續調用了5次onAuthenticationFailed()方法後。會調用onAuthenticationError()方法。

  2. 當系統調用了onAuthenticationError()onAuthenticationSucceeded()後。傳感器會關閉,僅僅有咱們又一次受權,再次調用authenticate()方法後才幹繼續使用指紋識別功能。

  3. 當系統回調了onAuthenticationError()方法關閉傳感器後。這樣的狀況下再次調用authenticate()會有一段時間的禁用期。也就是說這段時間裏是沒法再次使用指紋識別的。固然。具體的禁用時間由手機廠商的系統不一樣而有稍微差異,有的是1分鐘,有的是30秒等等。而且,因爲手機廠商的系統差異,有些系統上調用了onAuthenticationError()後,在禁用時間內。其它APP裏面的指紋識別功能也沒法使用,甚至系統的指紋解鎖功能也沒法使用。

    而有的系統上,在禁用時間內調用其它APP的指紋解鎖功能,或者系統的指紋解鎖功能,就能立刻重置指紋識別功能。

演示樣例代碼

最後。 Android Sample 裏面關於指紋的演示樣例代碼地址例如如下:

對稱加密方式:android-FingerprintDialog

非對稱加密方式:android-AsymmetricFingerprintDialog


如下內容更新於 2017.6.5

獲取指紋信息

愈來愈多的朋友開始關心指紋識別這一功能模塊。而且經過各類渠道向我諮詢一些關於指紋識別的需求解決方式。

這裏就統一說明一下,就不一一回復了。

前面已經說到了。監聽指紋識別成功以後會有一個 onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) 回調方法。該方法會給咱們一個 AuthenticationResult 類的對象 result。該類的源代碼例如如下:

public static class AuthenticationResult {
    private Fingerprint mFingerprint;
    private CryptoObject mCryptoObject;

    /** * Authentication result * * @param crypto the crypto object * @param fingerprint the recognized fingerprint data, if allowed. * @hide */
    public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint) {
        mCryptoObject = crypto;
        mFingerprint = fingerprint;
    }

    /** * Obtain the crypto object associated with this transaction * @return crypto object provided to {@link FingerprintManager#authenticate(CryptoObject, * CancellationSignal, int, AuthenticationCallback, Handler)}. */
    public CryptoObject getCryptoObject() { return mCryptoObject; }

    /** * Obtain the Fingerprint associated with this operation. Applications are strongly * discouraged from associating specific fingers with specific applications or operations. * * @hide */
    public Fingerprint getFingerprint() { return mFingerprint; }
};

這個類裏面包含了一個 Fingerprint 對象。假設咱們查看 Fingerprint 類的源代碼。可以得知該類提供了指紋的一些屬性。包含指紋的名稱、GroupId、FingerId 和 DeviceId 等屬性。也就是說,經過 onAuthenticationSucceeded() 回調方法,咱們可以獲得識別的指紋的一些信息。

那爲何咱們以前不使用該返回給咱們的 AuthenticationResult 類對象 result 呢?因爲咱們沒辦法經過正常途徑獲取到這些信息。因爲 mFingerprint 屬性是私有的,getFingerprint() 方法是被 @hide 修飾的。甚至連存儲指紋信息的 Fingerprint 類也是被 @hide 修飾的。

在 Android SDK 中,有兩種 API 是咱們沒法直接獲取的。一種是位於包 com.android.internal 如下的 API。還有一種是被 @hide 修飾的類和方法。

然而,針對另一種被隱藏的 API。咱們可以經過反射的方式來調用相關的類和方法。

onAuthenticationSucceeded() 回調方法爲例,看看怎樣獲取識別成功的指紋信息:

@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
    try {
        Field field = result.getClass().getDeclaredField("mFingerprint");
        field.setAccessible(true);
        Object fingerPrint = field.get(result);

        Class<?> clzz = Class.forName("android.hardware.fingerprint.Fingerprint");
        Method getName = clzz.getDeclaredMethod("getName");
        Method getFingerId = clzz.getDeclaredMethod("getFingerId");
        Method getGroupId = clzz.getDeclaredMethod("getGroupId");
        Method getDeviceId = clzz.getDeclaredMethod("getDeviceId");

        CharSequence name = (CharSequence) getName.invoke(fingerPrint);
        int fingerId = (int) getFingerId.invoke(fingerPrint);
        int groupId = (int) getGroupId.invoke(fingerPrint);
        long deviceId = (long) getDeviceId.invoke(fingerPrint);

        Log.d(TAG, "name: " + name);
        Log.d(TAG, "fingerId: " + fingerId);
        Log.d(TAG, "groupId: " + groupId);
        Log.d(TAG, "deviceId: " + deviceId);
    } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

顯示的結果例如如下:

name: 
fingerId: 1765296462
groupId: 0
deviceId: 547946660160

當中,返回的 name 爲空字符串。

這裏要提醒一點,因爲每個手機廠商都會對 Android 系統進行或多或少的定製,所以在不一樣的手機上調用上面的方法獲得的結果可能會不同。比方在個人手機上,fingerId 是一個十位數的整數,在其它手機上面可能就是個一位數的整數。

獲得了指紋信息以後,咱們就可以作一些額外的功能了,比方監聽用戶是否使用同一個指紋來解鎖,就可以用上面的方法來推斷。

除了在 onAuthenticationSucceeded() 回調方法中獲取被識別的指紋信息外,還可以利用 FingerprintManager 類的 getEnrolledFingerprints() 方法來獲取手機中存儲的指紋列表:

public void getFingerprintInfo() {
    try {
        FingerprintManager fingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
        Method method = FingerprintManager.class.getDeclaredMethod("getEnrolledFingerprints");
        Object obj = method.invoke(fingerprintManager);
        if (obj != null) {
            Class<?> clazz = Class.forName("android.hardware.fingerprint.Fingerprint");
            Method getFingerId = clazz.getDeclaredMethod("getFingerId");
            for (int i = 0; i < ((List) obj).size(); i++) {
                Object item = ((List) obj).get(i);
                if (null == item) {
                    continue;
                }

                Log.d(TAG, "fingerId: " + getFingerId.invoke(item));
            }
        }
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) {
        e.printStackTrace();
    }
}

在一些場景中,需要對用戶手機裏面錄入的指紋進行監聽,當用戶手機裏面的指紋列表發生了變化(比方用戶刪除了指紋或者新增了指紋)。就需要用戶又一次輸入password,這時就可以用上面的方法,記錄用戶手機裏面的指紋 ID,並進行先後對照。(固然,這樣的作法的安全係數並不高,也難以兼容衆多設備,這裏僅僅是舉例說明用途)

參考連接:New in Android Samples: Authenticating to remote servers using the Fingerprint API

相關文章
相關標籤/搜索