Android中的指纹识别

转载请注明出处:http://blog.csdn.net/wl9739/article/details/52444671

评论中非常多朋友反映,依据我给出的方案,拿不到指纹信息这个问题,在这里统一说明一下。

首先,这篇文章中涉及到的代码,我在一部魅族手机和一部三星手机上进行測试过,能获取到信息。

其它手机机型我没有測试,不知道具体情况。

其次,我在博客中也说明了。在不同手机厂商的定制系统里面获取到的指纹信息非常可能是不同的,我測试的魅族手机和三星手机返回的信息格式就不一样。依照本文的方法获取到的指纹信息是一个比較鸡肋的功能,作用有限。我眼下也没有能力给出一个完美的解决方式,假设有哪位找到了完美的解决方式,欢迎分享。

另外,指纹信息相关 API 是用 @hide 修饰的,这说明 Google 官方是不希望开发人员使用这类 API 。使用这类 API 可能会有风险。如方法參数改变、在兴许版本号中被移除等等。

因此。对于指纹识别这一块。建议读者使用本文中提及的指纹识别功能,尽可能避免使用本文后面提及的“获取指纹信息”这一功能。

近期项目须要使用到指纹识别的功能,查阅了相关资料后,整理成此文。

指纹识别是在Android 6.0之后新增的功能,因此在使用的时候须要先推断用户手机的系统版本号是否支持指纹识别。另外。实际开发场景中,使用指纹的主要场景有两种:

  • 纯本地使用。

    即用户在本地完毕指纹识别后,不须要将指纹的相关信息给后台。

  • 与后台交互。用户在本地完毕指纹识别后,须要将指纹相关的信息传给后台。

由于使用指纹识别功能须要一个加密对象(CryptoObject)该对象通常是由对称加密或者非对称加密获得。上述两种开发场景的实现大同小异。主要差别在于加密过程中密钥的创建和使用,一般来说。纯本地的使用指纹识别功能。仅仅须要对称加密就可以;而与后台交互则须要使用非对称加密:将私钥用于本地指纹识别。识别成功后将加密信息传给后台,后台开发人员用公钥解密。以获得用户信息。

以下先简介一下对称加密和非对称加密的相关概念,然后对两种开发方式的实现分别进行解说。

对称加密、非对称加密和签名

在正式使用指纹识别功能之前,有必要先了解一下对称加密和非对称加密的相关内容。

  • 对称加密:所谓对称,就是採用这样的加密方法的两方使用方式用相同的密钥进行加密和解密。密钥是控制加密及解密过程的指令。算法是一组规则。规定怎样进行加密和解密。因此加密的安全性不仅取决于加密算法本身,密钥管理的安全性更是重要。由于加密和解密都使用同一个密钥,怎样把密钥安全地传递到解密者手上就成了必须要解决的问题。

  • 非对称加密:非对称加密算法须要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对。假设用公开密钥对数据进行加密,仅仅实用相应的私有密钥才干解密;假设用私有密钥对数据进行加密,那么仅仅实用相应的公开密钥才干解密。由于加密和解密使用的是两个不同的密钥,所以这样的算法叫作非对称加密算法。

    非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将当中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的还有一把专用密钥对加密后的信息进行解密。

  • 签名:在信息的后面再加上一段内容。能够证明信息没有被改动过。通常是对信息做一个hash计算得到一个hash值。注意,这个过程是不可逆的。也就是说无法通过hash值得出原来的信息内容。在把信息发送出去时,把这个hash值加密后做为一个签名和信息一起发出去。

由以上内容能够了解到。对称加密和非对称加密的特点例如以下:

  • 对称加密的长处是速度快,适合于本地数据和本地数据库的加密。安全性不如非对称加密。常见的对称加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、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

posted @ 2017-08-21 10:55  jhcelue  阅读(283)  评论(0编辑  收藏