Created
October 30, 2018 09:15
-
-
Save PsyGik/b246e05493804f02df1def7bbfc3b17e to your computer and use it in GitHub Desktop.
Android Fingerprint Authentication Helper Class
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.annotation.TargetApi; | |
import android.app.KeyguardManager; | |
import android.hardware.fingerprint.FingerprintManager; | |
import android.os.Build; | |
import android.os.CancellationSignal; | |
import android.security.keystore.KeyGenParameterSpec; | |
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 javax.crypto.Cipher; | |
import javax.crypto.KeyGenerator; | |
import javax.crypto.NoSuchPaddingException; | |
import javax.crypto.SecretKey; | |
@TargetApi(Build.VERSION_CODES.M) | |
public class FingerprintUIHelper extends FingerprintManager.AuthenticationCallback { | |
/** | |
* The timeout for the error to be displayed. Returns to the normal UI after this. | |
*/ | |
private static final long ERROR_TIMEOUT_MILLIS = 1600; | |
/** | |
* The timeout for the success to be displayed. Calls {@link Callback#onAuthenticated()} after this. | |
*/ | |
private static final long SUCCESS_DELAY_MILLIS = 1300; | |
/** | |
* Alias for our key in the Android Key Store | |
**/ | |
private static final String KEY_NAME = "my_key"; | |
/** | |
* The {@link Cipher} used to init {@link FingerprintManager} | |
*/ | |
private Cipher mCipher; | |
/** | |
* The {@link KeyStore} used to initiliaze the key {@link #KEY_NAME} | |
*/ | |
private KeyStore mKeyStore; | |
/** | |
* The {@link KeyGenerator} used to generate the key {@link #KEY_NAME} | |
*/ | |
private KeyGenerator mKeyGenerator; | |
/** | |
* The {@link android.hardware.fingerprint.FingerprintManager.CryptoObject} | |
*/ | |
private final FingerprintManager mFingerprintManager; | |
private final Callback mCallback; | |
/** | |
* The {@link CancellationSignal} used after an error happens | |
*/ | |
private CancellationSignal mCancellationSignal; | |
/** | |
* Used if the user cancelled the authentication by himself | |
*/ | |
private boolean mSelfCancelled; | |
/** | |
* Builder class for {@link FingerprintUIHelper} in which injected fields from Dagger | |
* holds its fields and takes other arguments in the {@link #build} method. | |
*/ | |
public static class FingerprintUiHelperBuilder { | |
private final FingerprintManager mFingerPrintManager; | |
public FingerprintUiHelperBuilder(FingerprintManager fingerprintManager) { | |
mFingerPrintManager = fingerprintManager; | |
} | |
public FingerprintUIHelper build(Callback callback) { | |
return new FingerprintUIHelper(mFingerPrintManager, | |
callback); | |
} | |
} | |
/** | |
* Constructor for {@link FingerprintUIHelper}. This method is expected to be called from | |
* only the {@link FingerprintUiHelperBuilder} class. | |
*/ | |
private FingerprintUIHelper(FingerprintManager fingerprintManager,Callback callback) { | |
mFingerprintManager = fingerprintManager; | |
mCallback = callback; | |
} | |
/** | |
* Starts listening to {@link FingerprintManager} | |
* | |
* @throws SecurityException If the hardware is not available, or the permission are not set | |
*/ | |
public void startListening() throws SecurityException { | |
if (initCipher()) { | |
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(mCipher); | |
if (!isFingerprintAuthAvailable()) { | |
return; | |
} | |
mCancellationSignal = new CancellationSignal(); | |
mSelfCancelled = false; | |
mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null); | |
} | |
} | |
/** | |
* Stops listening to {@link FingerprintManager} | |
*/ | |
public void stopListening() { | |
if (mCancellationSignal != null) { | |
mSelfCancelled = true; | |
mCancellationSignal.cancel(); | |
mCancellationSignal = null; | |
} | |
} | |
/** | |
* Called by {@link FingerprintManager} if the authentication threw an error. | |
*/ | |
@Override | |
public void onAuthenticationError(int errMsgId, CharSequence errString) { | |
if (!mSelfCancelled) { | |
mCallback.onError(); | |
} | |
} | |
/** | |
* Called by {@link FingerprintManager} if the user asked for help. | |
*/ | |
@Override | |
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { | |
} | |
/** | |
* Called by {@link FingerprintManager} if the authentication failed (bad finger etc...). | |
*/ | |
@Override | |
public void onAuthenticationFailed() { | |
mCallback.onAuthenticated(); | |
} | |
/** | |
* Called by {@link FingerprintManager} if the authentication succeeded. | |
*/ | |
@Override | |
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { | |
mCallback.onAuthenticated(); | |
} | |
/** | |
* Tells if the {@link FingerprintManager#isHardwareDetected()}, {@link FingerprintManager#hasEnrolledFingerprints()}, | |
* and {@link KeyguardManager#isDeviceSecure()} | |
* | |
* @return true if yes, false otherwise | |
* @throws SecurityException If the hardware is not available, or the permission are not set | |
*/ | |
public boolean isFingerprintAuthAvailable() throws SecurityException { | |
return mFingerprintManager.isHardwareDetected() | |
&& mFingerprintManager.hasEnrolledFingerprints(); | |
} | |
/** | |
* Initialize the {@link Cipher} instance with the created key in the {@link #createKey()} | |
* method. | |
* | |
* @return {@code true} if initialization is successful, {@code false} if the lock screen has | |
* been disabled or reset after the key was generated, or if a fingerprint got enrolled after | |
* the key was generated. | |
*/ | |
private boolean initCipher() { | |
try { | |
if (mKeyStore == null) { | |
mKeyStore = KeyStore.getInstance("AndroidKeyStore"); | |
} | |
createKey(); | |
mKeyStore.load(null); | |
SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null); | |
mCipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); | |
mCipher.init(Cipher.ENCRYPT_MODE, key); | |
return true; | |
} catch (NoSuchPaddingException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | |
| NoSuchAlgorithmException | InvalidKeyException e) { | |
return false; | |
} | |
} | |
/** | |
* Creates a symmetric key in the Android Key Store which can only be used after the user has | |
* authenticated with fingerprint. | |
*/ | |
public void createKey() { | |
// The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint | |
// for your flow. Use of keys is necessary if you need to know if the set of | |
// enrolled fingerprints has changed. | |
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 | |
mKeyGenerator = KeyGenerator.getInstance( | |
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); | |
mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, | |
KeyProperties.PURPOSE_ENCRYPT | | |
KeyProperties.PURPOSE_DECRYPT) | |
.setBlockModes(KeyProperties.BLOCK_MODE_CBC) | |
// Require the user to authenticate with a fingerprint to authorize every use | |
// of the key | |
.setUserAuthenticationRequired(true) | |
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) | |
.build()); | |
mKeyGenerator.generateKey(); | |
} catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
/** | |
* The interface used to call the original Activity/Fragment... that uses this helper. | |
*/ | |
public interface Callback { | |
void onAuthenticated(); | |
void onError(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment