5.7 使用指纹认证功能 原书:Android Application Secure Design/Secure Coding Guidebook

译者:飞龙

协议:CC BY-NC-SA 4.0

目前正在研究和开发的各种用于生物认证的方法中,使用面部信息和声音特征的方法尤其突出。在这些方法中,使用指纹认证来识别个体的方法自古以来就有所使用,并且今天被用于签名(通过拇指印)和犯罪调查等目的。指纹识别的应用也在计算机世界的几个领域中得到了发展,并且近年来,这些方法已经开始作为高度便利的技术(提供诸如易于输入的优点)而享有广泛认可,用于一些领域,例如识别智能手机的物主(主要用于解锁屏幕)。

在这些趋势下,Android 6.0(API Level 23)在终端上整合了指纹认证框架,允许应用使用指纹认证功能来识别个人身份。在下面我们将讨论一些使用指纹认证时要记住的安全预防措施。

5.7.1 示例代码下面我们提供示例代码,来允许应用使用 Android 的指纹认证功能。

要点:

声明使用USE_FINGERPRINT权限从AndroidKeyStore供应器获取实例通知用户需要指纹注册才能创建密钥创建(注册)密钥时,请使用没有漏洞的加密算法(符合标准)创建(注册)密钥时,启用用户(指纹)认证请求(不要指定启用认证的持续时间)设计你的应用的前提是,指纹注册的状态将在密钥创建和使用密钥期间发生变化将加密数据限制为,可通过指纹认证以外的方法恢复(替换)的项东西MainActivity.java

代码语言:javascript复制package authentication.fingerprint.android.jssec.org.fingerprintauthentication;

import android.app.AlertDialog;

import android.hardware.fingerprint.FingerprintManager;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.util.Base64;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

import java.text.SimpleDateFormat;

import java.util.Date;

import javax.crypto.BadPaddingException;

import javax.crypto.Cipher;

import javax.crypto.IllegalBlockSizeException;

public class MainActivity extends AppCompatActivity {

private FingerprintAuthentication mFingerprintAuthentication;

private static final String SENSITIVE_DATA = "sensitive data";

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mFingerprintAuthentication = new FingerprintAuthentication(this);

Button button_fingerprint_auth = (Button) findViewById(R.id.button_fingerprint_auth);

button_fingerprint_auth.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

if (!mFingerprintAuthentication.isAuthenticating()) {

if (authenticateByFingerprint()) {

showEncryptedData(null);

setAuthenticationState(true);

}

} else {

mFingerprintAuthentication.cancel();

}

}

});

}

private boolean authenticateByFingerprint() {

if (!mFingerprintAuthentication.isFingerprintHardwareDetected()) {

// Terminal is not equipped with a fingerprint sensor

return false;

}

if (!mFingerprintAuthentication.isFingerprintAuthAvailable()) {

// *** POINT 3 *** Notify users that fingerprint registration will be required to create a key

new AlertDialog.Builder(this)

.setTitle(R.string.app_name)

.setMessage("No fingerprint information has been registered.¥n" +

"Click ¥"Security¥" on the Settings menu to register fingerprints. ¥n" +

"Registering fingerprints allows easy authentication.")

.setPositiveButton("OK", null)

.show();

return false;

}

// Callback that receives the results of fingerprint authentication

FingerprintManager.AuthenticationCallback callback = new FingerprintManager.AuthenticationCallback() {

@Override

public void onAuthenticationError(int errorCode, CharSequence errString) {

showMessage(errString, R.color.colorError);

reset();

}

@Override

public void onAuthenticationHelp(int helpCode, CharSequence helpString) {

showMessage(helpString, R.color.colorHelp);

}

@Override

public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {

Cipher cipher = result.getCryptoObject().getCipher();

try {

// *** POINT 7*** Restrict encrypted data to items that can be restored (replaced) by methods other than fingerprint authentication

byte[] encrypted = cipher.doFinal(SENSITIVE_DATA.getBytes());

showEncryptedData(encrypted);

} catch (IllegalBlockSizeException | BadPaddingException e) {

}

showMessage(getString(R.string.fingerprint_auth_succeeded), R.color.colorAuthenticated);

reset();

}

@Override

public void onAuthenticationFailed() {

showMessage(getString(R.string.fingerprint_auth_failed), R.color.colorError);

}

};

if (mFingerprintAuthentication.startAuthentication(callback)) {

showMessage(getString(R.string.fingerprint_processing), R.color.colorNormal);

return true;

}

return false;

}

private void setAuthenticationState(boolean authenticating) {

Button button = (Button) findViewById(R.id.button_fingerprint_auth);

button.setText(authenticating ? R.string.cancel : R.string.authenticate);

}

private void showEncryptedData(byte[] encrypted) {

TextView textView = (TextView) findViewById(R.id.encryptedData);

if (encrypted != null) {

textView.setText(Base64.encodeToString(encrypted, 0));

} else {

textView.setText("");

}

}

private String getCurrentTimeString() {

long currentTimeMillis = System.currentTimeMillis();

Date date = new Date(currentTimeMillis);

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");

return simpleDateFormat.format(date);

}

private void showMessage(CharSequence msg, int colorId) {

TextView textView = (TextView) findViewById(R.id.textView);

textView.setText(getCurrentTimeString() + " :¥n" + msg);

textView.setTextColor(getResources().getColor(colorId, null));

}

private void reset() {

setAuthenticationState(false);

}

}FingerprintAuthentication.java

代码语言:javascript复制package authentication.fingerprint.android.jssec.org.fingerprintauthentication;

import android.app.KeyguardManager;

import android.content.Context;

import android.hardware.fingerprint.FingerprintManager;

import android.os.CancellationSignal;

import android.security.keystore.KeyGenParameterSpec;

import android.security.keystore.KeyInfo;

import android.security.keystore.KeyPermanentlyInvalidatedException;

import android.security.keystore.KeyProperties;

import java.io.IOException;

import java.security.InvalidAlgorithmParameterException;

import java.security.InvalidKeyException;

import java.security.KeyStore;

import java.security.KeyStoreException;

import java.security.NoSuchAlgorithmException;

import java.security.NoSuchProviderException;

import java.security.UnrecoverableKeyException;

import java.security.cert.CertificateException;

import java.security.spec.InvalidKeySpecException;

import javax.crypto.Cipher;

import javax.crypto.KeyGenerator;

import javax.crypto.NoSuchPaddingException;

import javax.crypto.SecretKey;

import javax.crypto.SecretKeyFactory;

public class FingerprintAuthentication {

private static final String KEY_NAME = "KeyForFingerprintAuthentication";

private static final String PROVIDER_NAME = "AndroidKeyStore";

private KeyguardManager mKeyguardManager;

private FingerprintManager mFingerprintManager;

private CancellationSignal mCancellationSignal;

private KeyStore mKeyStore;

private KeyGenerator mKeyGenerator;

private Cipher mCipher;

public FingerprintAuthentication(Context context) {

mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);

mFingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE

);

reset();

}

public boolean startAuthentication(final FingerprintManager.AuthenticationCallback callback) {

if (!generateAndStoreKey())

return false;

if (!initializeCipherObject())

return false;

FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(mCipher);

mCancellationSignal = new CancellationSignal();

// Callback to receive the results of fingerprint authentication

FingerprintManager.AuthenticationCallback hook = new FingerprintManager.AuthenticationCallback() {

@Override

public void onAuthenticationError(int errorCode, CharSequence errString) {

if (callback != null)

callback.onAuthenticationError(errorCode, errString);

reset();

}

@Override

public void onAuthenticationHelp(int helpCode, CharSequence helpString) {

if (callback != null)

callback.onAuthenticationHelp(helpCode, helpString);

}

@Override

public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {

if (callback != null)

callback.onAuthenticationSucceeded(result);

reset();

}

@Override

public void onAuthenticationFailed() {

if (callback != null)

callback.onAuthenticationFailed();

}

};

// Execute fingerprint authentication

mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, 0, hook, null);

return true;

}

public boolean isAuthenticating() {

return mCancellationSignal != null && !mCancellationSignal.isCanceled();

}

public void cancel() {

if (mCancellationSignal != null) {

if (!mCancellationSignal.isCanceled())

mCancellationSignal.cancel();

}

}

private void reset() {

try {

// *** POINT 2 *** Obtain an instance from the "AndroidKeyStore" Provider

mKeyStore = KeyStore.getInstance(PROVIDER_NAME);

mKeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, PROVIDER_NAME);

mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES

+ "/" + KeyProperties.BLOCK_MODE_CBC

+ "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);

} catch (KeyStoreException | NoSuchPaddingException

| NoSuchAlgorithmException | NoSuchProviderException e) {

throw new RuntimeException("failed to get cipher instances", e);

}

mCancellationSignal = null;

}

public boolean isFingerprintAuthAvailable() {

return (mKeyguardManager.isKeyguardSecure()

&& mFingerprintManager.hasEnrolledFingerprints()) ? true : false;

}

public boolean isFingerprintHardwareDetected() {

return mFingerprintManager.isHardwareDetected();

}

private boolean generateAndStoreKey() {

try {

mKeyStore.load(null);

if (mKeyStore.containsAlias(KEY_NAME))

mKeyStore.deleteEntry(KEY_NAME);

mKeyGenerator.init(

// *** POINT 4 *** When creating (registering) keys, use an encryption algorithm that is not vulnerable (meets standards)

new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT)

.setBlockModes(KeyProperties.BLOCK_MODE_CBC)

.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)

// *** POINT 5 *** When creating (registering) keys, enable requests for user (fingerprint) authentication (do not specify the duration over which authentication is enabled)

.setUserAuthenticationRequired(true)

.build());

// Generate a key and store it in Keystore(AndroidKeyStore)

mKeyGenerator.generateKey();

return true;

} catch (IllegalStateException e) {

return false;

} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException

| CertificateException | KeyStoreException | IOException e) {

throw new RuntimeException("failed to generate a key", e);

}

}

private boolean initializeCipherObject() {

try {

mKeyStore.load(null);

SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);

SecretKeyFactory factory = SecretKeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_AES, PROVIDER_NAME);

KeyInfo info = (KeyInfo) factory.getKeySpec(key, KeyInfo.class);

mCipher.init(Cipher.ENCRYPT_MODE, key);

return true;

} catch (KeyPermanentlyInvalidatedException e) {

// *** POINT 6 *** Design your app on the assumption that the status of fingerprint registration will change between when keys are created and when keys are used

return false;

} catch (KeyStoreException | CertificateException

| UnrecoverableKeyException | IOException

| NoSuchAlgorithmException | InvalidKeySpecException

| NoSuchProviderException | InvalidKeyException e) {

throw new RuntimeException("failed to init Cipher", e);

}

}

}AndroidManifest.xml

代码语言:javascript复制

package="authentication.fingerprint.android.jssec.org.fingerprintauthentication" >

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:supportsRtl="true"

android:theme="@style/AppTheme" >

android:name=".MainActivity"

android:screenOrientation="portrait" >

5.7.2 规则书使用指纹认证的时候,遵循下列规则:

5.7.2.1 创建(注册)密钥时,请使用没有漏洞的加密算法(符合标准)(必需)与“5.6 使用密码学”中讨论的密码密钥和公密一样,使用指纹认证功能来创建密钥时,必须使用没有漏洞的加密算法 - 即符合某些标准的算法,来防止第三方的窃听。 事实上,安全和没有漏洞的选择不仅适用于加密算法,而且适用于加密模式和填充。

算法选择的更多信息,请参见“5.6.2.2 使用强算法(特别是符合相关标准的算法)(必需)”部分。

5.7.2.2 将加密数据限制为,可通过指纹认证以外的方法恢复(替换)的东西(必需)当应用使用指纹认证功能,对应用中的数据进行加密时,应用的设计必须允许通过指纹认证以外的方法恢复(替换)数据。 一般来说,使用生物信息会带来各种问题 - 包括保密性,修改难度和错误识别 - 因此,最好避免单纯依靠生物信息进行认证。

例如,假设应用内部的数据使用密钥加密,密钥由指纹认证功能生成,但存储在终端内的指纹数据随后会被用户删除。 然后用于加密数据的密钥不可用,也不可能复制数据。 如果数据不能通过指纹认证功能以外的某种方式恢复,则存在数据无法使用的巨大风险。

此外,指纹信息的删除不是唯一的情况,即使用指纹认证功能创建的密钥可能变得不可用。 在 Nexus5X 中,如果使用指纹认证功能来创建密钥,然后将该密钥注册为额外的指纹信息,则据观察,之前创建的密钥不可用 [30]。此外,不能排除这种可能性,由于指纹传感器的错误识别,通常可以正确使用的密钥变得不可用。

[30] 信息来自 2016 年 9 月 1 日的版本。 这可能会在未来进行修改。

5.7.2.3 通知用户需要注册指纹才能创建密钥(推荐)为了使用指纹认证创建密钥,有必要在终端上注册用户的指纹。 设计应用来引导用户进入设置菜单来鼓励指纹注册时,开发人员必须记住,指纹代表重要的个人数据,并且希望向用户解释为什么应用使用指纹信息是必要的或便利的。

通知用户需要注册指纹

代码语言:javascript复制if (!mFingerprintAuthentication.isFingerprintAuthAvailable()) {

// **Point** Notify users that fingerprint registration will be required to create a key

new AlertDialog.Builder(this)

.setTitle(R.string.app_name)

.setMessage("No fingerprint information has been registered.¥n" +

" Click ¥"Security¥" on the Settings menu to register fingerprints.¥n" +

" Registering fingerprints allows easy authentication.")

.setPositiveButton("OK", null)

.show();

return false;

}5.7.3 高级话题5.7.3.1 Android 应用使用指纹认证功能的先决条件为了让应用使用指纹认证,必须满足以下两个条件。

用户指纹必须在终端内注册。(特定于应用的)密钥必须关联注册的指纹。注册用户指纹

用户指纹信息只能通过设置菜单中的“安全”选项进行注册;一般应用不能执行指纹注册过程。 因此,如果应用尝试使用指纹认证功能时未注册指纹,则应用必须引导用户进入设置菜单并鼓励用户注册指纹。 此时,应用需要向用户提供一些解释,说明为什么使用指纹信息是必要和方便的。

另外,作为指纹注册的必要前提条件,终端必须配置一个替代的屏幕锁定机制。 在指纹已在终端中注册的状态下,如果屏幕锁定被禁用,注册的指纹信息将被删除。

创建和注册密钥

为了关联密钥和终端中注册的指纹,请使用由AndroidKeyStore供应器提供的KeyStore实例,来创建并注册新密钥或注册现有密钥。 为了创建关联指纹信息的密钥,请在创建KeyGenerator时配置参数设置,来启用用户认证请求。

创建并注册关联指纹信息的密钥

代码语言:javascript复制try {

// Obtain an instance from the "AndroidKeyStore" Provider

KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");

keyGenerator.init(

new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT)

.setBlockModes(KeyProperties.BLOCK_MODE_CBC)

.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)

.setUserAuthenticationRequired(true) // Enable requests for user (fingerprint) authentication

.build());

keyGenerator.generateKey();

} catch (IllegalStateException e) {

// no fingerprints have been registered in this terminal

throw new RuntimeException(“No fingerprint registered”, e);

} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException

CertificateException | KeyStoreException | IOException e) {

// failed to generate a key

throw new RuntimeException("Failed to generate a key", e);

}为了关联指纹信息和现有密钥,请使用KeyStore条目,将该密钥注册到已添加设置的东西,来启用用户认证请求。

关联指纹信息和现有密钥

代码语言:javascript复制SecretKey key = …; // existing key

KeyStore keyStore = KeyStore.getInstance(“AndroidKeyStore”);

keyStore.load(null);

keyStore.setEntry(

"alias_for_the_key",

new KeyStore.SecretKeyEntry(key),

new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)

.setUserAuthenticationRequired(true) // Enable requests for user (fingerprint) authentication

.build());


掌阅满1000减200阅饼怎么使用
什么是慧眼?拥有慧眼有什么意义?如何才能开启慧眼?