安卓应用安全指南 5.7 使用指纹认证功能
5.7 使用指纹认证功能
原书:Android Application Secure Design/Secure Coding Guidebook
译者:飞龙
目前正在研究和开发的各种用于生物认证的方法中,使用面部信息和声音特征的方法尤其突出。在这些方法中,使用指纹认证来识别个体的方法自古以来就有所使用,并且今天被用于签名(通过拇指印)和犯罪调查等目的。指纹识别的应用也在计算机世界的几个领域中得到了发展,并且近年来,这些方法已经开始作为高度便利的技术(提供诸如易于输入的优点)而享有广泛认可,用于一些领域,例如识别智能手机的物主(主要用于解锁屏幕)。
在这些趋势下,Android 6.0(API Level 23)在终端上整合了指纹认证框架,允许应用使用指纹认证功能来识别个人身份。在下面我们将讨论一些使用指纹认证时要记住的安全预防措施。
5.7.1 示例代码
下面我们提供示例代码,来允许应用使用 Android 的指纹认证功能。
要点:
- 声明使用
USE_FINGERPRINT
权限 - 从
AndroidKeyStore
供应器获取实例 - 通知用户需要指纹注册才能创建密钥
- 创建(注册)密钥时,请使用没有漏洞的加密算法(符合标准)
- 创建(注册)密钥时,启用用户(指纹)认证请求(不要指定启用认证的持续时间)
- 设计你的应用的前提是,指纹注册的状态将在密钥创建和使用密钥期间发生变化
- 将加密数据限制为,可通过指纹认证以外的方法恢复(替换)的项东西
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());
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
安卓应用安全指南 5.6.1 密码学 示例代码
5.6.1 密码学 示例代码 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA 4.0 针对特定用途和条件开发了各种加密方法,包括加密和解密数据(来确保机密性)和检测数据伪造(来确保完整性)等用例。 以下是示例代码,根据每种技术的目的分为三大类加密技术。 在每种情况下,应该能够根据密码技术的特点,选择适当的加密方法和密钥类型。 对于需要更详细考虑的情况,请参见章节“5.6.3.1 选择加密方法”。 在使用加密技术设计实现之前,请务必阅读“5.6.3.3 防止随机数字生成器中的漏洞的措施”。 保护数据免受第三方窃听 检测第三方所做的数据伪造 5.6.1.1 使用基于密码的密钥的加密和解密 你可以使用基于密码的密钥加密,来保护用户的机密数据资产。 要点: 显式指定加密模式和填充。 使用强加密技术(特别是符合相关标准的技术),包括算法,分组加密模式和填充模式。 从密码生成密钥时,使用盐。 从密码生成密钥时,指定适当的哈希迭代计数。 使用足以保证加密强度的密钥长度。 AesCryp...
- 下一篇
Linux下配置Golang开发环境
前几天无意间看到了微信推送的golang开发的消息,看到golang那么牛逼,突然心血来潮想学习一下go。工欲善其事必先利其器,想做go开发,必须先配置好go的开发环境(就像开发Java先安装配置jdk一样)。在网上找了半天,一直没有找到满意的教程(看来golang在国内还是不火)遂自己摸索,踩了不少坑,特来记录一下,希望能帮助到别人,少走弯路。由于我把除了C/C++以外的开发都搬迁到Linux下了,所以就在Ubuntu下安装配置了golang。其实Windows下的安装也是大同小异了。 1 先去golang官网下载golang的安装包 golang的官网在国内访问比较慢,建议去国内的网站下载https://studygolang.com/dl 根据不同的平台选择对应的安装包 下载.png Linux的下载上面的,Windows下载下面的 2 安装 Windows的双击然后选择目录安装就好了。Linux的用 sudo tar -C /usr/local/ -xzvf go1.10.2.linux-amd64.tar.gz命令解压到 /urs/local/ 目录。解压完成,下一步 3 配...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Hadoop3单机部署,实现最简伪集群
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7,CentOS8安装Elasticsearch6.8.6