您现在的位置是:首页 > 文章详情

安卓应用安全指南 5.7 使用指纹认证功能

日期:2018-04-04点击:300

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 的指纹认证功能。

要点:

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

MainActivity.java

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

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

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="authentication.fingerprint.android.jssec.org.fingerprintauthentication" > <!-- +++ POINT 1 *** Declare the use of the USE_FINGERPRINT permission --> <uses-permission android:name="android.permission.USE_FINGERPRINT" /> <application  android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" > <activity  android:name=".MainActivity" android:screenOrientation="portrait" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

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 通知用户需要注册指纹才能创建密钥(推荐)

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

通知用户需要注册指纹

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时配置参数设置,来启用用户认证请求。

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

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条目,将该密钥注册到已添加设置的东西,来启用用户认证请求。

关联指纹信息和现有密钥

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());
原文链接:https://yq.aliyun.com/articles/595235
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章