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

相关文章
相关标签/搜索