Android指纹锁
在本教程中,我们将讨论Android指纹API,并在我们的android应用程序中实现"指纹对话框"。
Android指纹管理器
指纹管理器是用于从设备(如果存在)访问指纹硬件的类。
Google建议通过向用户显示带有指纹图标的DialogFragment来对应用程序中的指纹进行身份验证。
为了实现指纹认证,您需要在AndroidManifest.xml文件中添加以下权限:
<uses-permission android:name="android.permission.USE_FINGERPRINT"
以下是在您的应用程序中实现指纹认证的步骤:
检查锁定屏幕上是否有安全锁定
使用
FingerprintManager
类检查指纹硬件是否可用。检查用户是否注册了至少一个指纹。
获取对Android密钥库的访问权限,以存储用于启动密码的密钥。
启动身份验证方法并添加回调方法
Android Keystore系统可让您将加密密钥存储在容器中,以使其更难从设备中提取。
代码
下面给出了" activity_main.xml"布局的代码:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="START AUTHENTICATION" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" </android.support.constraint.ConstraintLayout>
下面给出了" dialog_fingerprint.xml"的代码:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="24dp" android:paddingTop="24dp" android:paddingRight="24dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"> <TextView android:id="@+id/titleTextView" android:layout_width="0dp" android:layout_height="wrap_content" android:text="Fingerprint Dialog" android:textAppearance="?android:attr/textAppearanceLarge" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" <TextView android:id="@+id/subtitleTextView" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:text="Confirm fingerprint to continue." app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/titleTextView" <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="28dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/subtitleTextView" app:srcCompat="@drawable/ic_fingerprint_white_24dp" <TextView android:id="@+id/errorTextView" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="16dp" android:gravity="center_vertical" android:text="Touch sensor" app:layout_constraintBottom_toBottomOf="@id/fab" app:layout_constraintLeft_toRightOf="@id/fab" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="@id/fab" <LinearLayout android:id="@+id/buttons" style="?android:attr/buttonBarStyle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:gravity="end" android:orientation="horizontal" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/fab"> <Button android:id="@+id/btnCancel" style="?android:attr/buttonBarButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cancel" </LinearLayout> </android.support.constraint.ConstraintLayout>
在FingerprintHelper.java类中,我们定义了指纹和相关类对象的身份验证和初始化方法:
package com.theitroad.androidfingerprintapi; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.CancellationSignal; import android.support.v4.app.ActivityCompat; public class FingerprintHelper extends FingerprintManager.AuthenticationCallback { private Context mContext; private FingerprintManager mFingerprintManager; private CancellationSignal mCancellationSignal; private Callback mCallback; public FingerprintHelper(FingerprintManager fingerprintManager, Context context, Callback callback) { mContext = context; mFingerprintManager = fingerprintManager; mCallback = callback; } public boolean isFingerprintAuthAvailable() { return mFingerprintManager.isHardwareDetected() && mFingerprintManager.hasEnrolledFingerprints(); } public void startAuthentication(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) { if (!isFingerprintAuthAvailable()) return; mCancellationSignal = new CancellationSignal(); if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { return; } manager.authenticate(cryptoObject, mCancellationSignal, 0, this, null); } public void stopListening() { if (mCancellationSignal != null) { mCancellationSignal.cancel(); mCancellationSignal = null; } } @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { mCallback.onError(errString.toString()); } @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { mCallback.onHelp(helpString.toString()); } @Override public void onAuthenticationFailed() { mCallback.onAuthenticated(false); } @Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { mCallback.onAuthenticated(true); } public interface Callback { void onAuthenticated(boolean b); void onError(String s); void onHelp(String s); } }
manager.authenticate(cryptoObject,mCancellationSignal,0,this,null);
开始认证。
回调接口用于将信息传递到UI,最终将其显示给用户。
MainActivity.java类的代码如下:
package com.theitroad.androidfingerprintapi; import android.support.v4.hardware.fingerprint.FingerprintManagerCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity implements View.OnClickListener { Button button; FingerprintManagerCompat managerCompat; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = findViewById(R.id.button); button.setOnClickListener(this); } private void showFingerPrintDialog() { FingerprintDialog fragment = new FingerprintDialog(); fragment.setContext(this); fragment.show(getSupportFragmentManager(), ""); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.button: managerCompat = FingerprintManagerCompat.from(MainActivity.this); if (managerCompat.isHardwareDetected() && managerCompat.hasEnrolledFingerprints()) { showFingerPrintDialog(); } else { Toast.makeText(getApplicationContext(), "Fingerprint 不支持", Toast.LENGTH_SHORT).show(); } break; } } }
managerCompat = FingerprintManagerCompat.from(MainActivity.this);
初始化FingerprintCompat类对象。
isHardwareDetected()和hasEnrolledFingerprints()用于在显示对话框之前检查是否可以进行指纹认证。
FingerprintDialog.java的代码如下:
package com.theitroad.androidfingerprintapi; import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.support.v4.app.DialogFragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.Toast; 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 javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; public class FingerprintDialog extends DialogFragment implements FingerprintHelper.Callback { Button mCancelButton; public static final String DEFAULT_KEY_NAME = "default_key"; FingerprintManager mFingerprintManager; private FingerprintManager.CryptoObject mCryptoObject; private FingerprintHelper mFingerprintHelper; KeyStore mKeyStore = null; KeyGenerator mKeyGenerator = null; KeyguardManager mKeyguardManager; private Context mContext; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog); try { mKeyStore = KeyStore.getInstance("AndroidKeyStore"); } catch (KeyStoreException e) { e.printStackTrace(); } try { mKeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } 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("Failed to get an instance of Cipher", e); } mKeyguardManager = getContext().getSystemService(KeyguardManager.class); mFingerprintManager = getContext().getSystemService(FingerprintManager.class); mFingerprintHelper = new FingerprintHelper(mFingerprintManager, getContext(), this); if (!mKeyguardManager.isKeyguardSecure()) { Toast.makeText(getContext(), "Lock screen not set up.\n" + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint", Toast.LENGTH_LONG).show(); return; } createKey(DEFAULT_KEY_NAME); if (initCipher(defaultCipher, DEFAULT_KEY_NAME)) { mCryptoObject = new FingerprintManager.CryptoObject(defaultCipher); } } private boolean initCipher(Cipher cipher, String keyName) { try { mKeyStore.load(null); SecretKey key = (SecretKey) mKeyStore.getKey(keyName, null); cipher.init(Cipher.ENCRYPT_MODE, key); return true; } catch (KeyPermanentlyInvalidatedException e) { Toast.makeText(mContext, "Keys are invalidated after created. Retry the purchase\n" + e.getMessage(), Toast.LENGTH_LONG).show(); return false; } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) { Toast.makeText(mContext, "Failed to init cipher", Toast.LENGTH_LONG).show(); return false; } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.dialog_fingerprint, container, false); mCancelButton = v.findViewById(R.id.btnCancel); mCancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { dismiss(); } }); return v; } @Override public void onResume() { super.onResume(); if (mCryptoObject != null) { mFingerprintHelper.startAuthentication(mFingerprintManager, mCryptoObject); } } @Override public void onPause() { super.onPause(); mFingerprintHelper.stopListening(); } public void setContext(Context context) { mContext = context; } public void createKey(String keyName) { try { mKeyStore.load(null); KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setUserAuthenticationRequired(true) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7); mKeyGenerator.init(builder.build()); mKeyGenerator.generateKey(); } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | CertificateException | IOException e) { throw new RuntimeException(e); } } @Override public void onAuthenticated(boolean b) { if (b) { Toast.makeText(mContext.getApplicationContext(), "Auth success", Toast.LENGTH_LONG).show(); dismiss(); } else Toast.makeText(mContext.getApplicationContext(), "Auth failed", Toast.LENGTH_LONG).show(); } @Override public void onError(String s) { Toast.makeText(mContext.getApplicationContext(), s, Toast.LENGTH_LONG).show(); } @Override public void onHelp(String s) { Toast.makeText(mContext.getApplicationContext(), "Auth help message:" + s, Toast.LENGTH_LONG).show(); } }
setRetainInstance(true);用于防止配置更改时创建DialogFragment的多个实例。
当我使用正确的指纹进行身份验证时,手机上会显示相应的Toast消息。