Files
KeePassDX/app/src/main/java/com/keepassdroid/fingerprint/FingerPrintHelper.java
2017-10-31 20:21:35 -05:00

286 lines
9.9 KiB
Java

package com.keepassdroid.fingerprint;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.content.Context;
import android.os.Build;
import android.support.v4.os.CancellationSignal;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.util.Base64;
import com.keepassdroid.compat.BuildCompat;
import java.security.KeyStore;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
public class FingerPrintHelper {
private static final String ALIAS_KEY = "example-key";
private FingerprintManagerCompat fingerprintManager;
private KeyStore keyStore = null;
private KeyGenerator keyGenerator = null;
private Cipher cipher = null;
private KeyguardManager keyguardManager = null;
private FingerprintManagerCompat.CryptoObject cryptoObject = null;
private boolean initOk = false;
private FingerPrintCallback fingerPrintCallback;
private CancellationSignal cancellationSignal;
private FingerprintManagerCompat.AuthenticationCallback authenticationCallback;
public void setAuthenticationCallback(final FingerprintManagerCompat.AuthenticationCallback authenticationCallback) {
this.authenticationCallback = authenticationCallback;
}
@SuppressLint("NewApi")
public void startListening() {
// no need to start listening when not initialised
if (!isFingerprintInitialized()) {
if (fingerPrintCallback != null) {
fingerPrintCallback.onException();
}
return;
}
// starts listening for fingerprints with the initialised crypto object
cancellationSignal = new CancellationSignal();
fingerprintManager.authenticate(
cryptoObject,
0 /* flags */,
cancellationSignal,
authenticationCallback,
null);
}
@SuppressLint("NewApi")
public void stopListening() {
if (!isFingerprintInitialized()) {
return;
}
if (cancellationSignal != null) {
cancellationSignal.cancel();
cancellationSignal = null;
}
}
public interface FingerPrintCallback {
void handleEncryptedResult(String value, String ivSpec);
void handleDecryptedResult(String value);
void onInvalidKeyException();
void onException();
}
@TargetApi(BuildCompat.VERSION_CODE_M)
public FingerPrintHelper(
final Context context,
final FingerPrintCallback fingerPrintCallback) {
if (!isFingerprintSupported()) {
// really not much to do when no fingerprint support found
setInitOk(false);
return;
}
this.fingerprintManager = FingerprintManagerCompat.from(context);
this.keyguardManager = (KeyguardManager)context.getSystemService(Context.KEYGUARD_SERVICE);
this.fingerPrintCallback = fingerPrintCallback;
if (hasEnrolledFingerprints()) {
try {
this.keyStore = KeyStore.getInstance("AndroidKeyStore");
this.keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore");
this.cipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
this.cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
setInitOk(true);
} catch (final Exception e) {
setInitOk(false);
fingerPrintCallback.onException();
}
}
}
public boolean isFingerprintInitialized() {
return hasEnrolledFingerprints() && initOk;
}
@SuppressWarnings("NewApi")
public void initEncryptData() {
if (!isFingerprintInitialized()) {
if (fingerPrintCallback != null) {
fingerPrintCallback.onException();
}
return;
}
try {
createNewKeyIfNeeded(false); // no need to keep deleting existing keys
keyStore.load(null);
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
stopListening();
startListening();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
fingerPrintCallback.onInvalidKeyException();
} catch (final Exception e) {
fingerPrintCallback.onException();
}
}
@SuppressWarnings("NewApi")
public void encryptData(final String value) {
if (!isFingerprintInitialized()) {
if (fingerPrintCallback != null) {
fingerPrintCallback.onException();
}
return;
}
try {
// actual do encryption here
byte[] encrypted = cipher.doFinal(value.getBytes());
final String encryptedValue = Base64.encodeToString(encrypted, 0 /* flags */);
// passes updated iv spec on to callback so this can be stored for decryption
final IvParameterSpec spec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
final String ivSpecValue = Base64.encodeToString(spec.getIV(), Base64.DEFAULT);
fingerPrintCallback.handleEncryptedResult(encryptedValue, ivSpecValue);
} catch (final Exception e) {
fingerPrintCallback.onException();
}
}
@SuppressWarnings("NewApi")
public void initDecryptData(final String ivSpecValue) {
if (!isFingerprintInitialized()) {
if (fingerPrintCallback != null) {
fingerPrintCallback.onException();
}
return;
}
try {
createNewKeyIfNeeded(false);
keyStore.load(null);
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
// important to restore spec here that was used for decryption
final byte[] iv = Base64.decode(ivSpecValue, Base64.DEFAULT);
final IvParameterSpec spec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
stopListening();
startListening();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
fingerPrintCallback.onInvalidKeyException();
} catch (final Exception e) {
fingerPrintCallback.onException();
}
}
@SuppressWarnings("NewApi")
public void decryptData(final String encryptedValue) {
if (!isFingerprintInitialized()) {
if (fingerPrintCallback != null) {
fingerPrintCallback.onException();
}
return;
}
try {
// actual decryption here
final byte[] encrypted = Base64.decode(encryptedValue, 0);
byte[] decrypted = cipher.doFinal(encrypted);
final String decryptedString = new String(decrypted);
//final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */);
fingerPrintCallback.handleDecryptedResult(decryptedString);
} catch (final Exception e) {
fingerPrintCallback.onException();
}
}
@SuppressLint("NewApi")
private void createNewKeyIfNeeded(final boolean allowDeleteExisting) {
if (!isFingerprintInitialized()) {
return;
}
try {
keyStore.load(null);
if (allowDeleteExisting
&& keyStore.containsAlias(ALIAS_KEY)) {
keyStore.deleteEntry(ALIAS_KEY);
}
// Create new key if needed
if (!keyStore.containsAlias(ALIAS_KEY)) {
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
keyGenerator.init(
new KeyGenParameterSpec.Builder(
ALIAS_KEY,
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());
keyGenerator.generateKey();
}
} catch (final Exception e) {
fingerPrintCallback.onException();
}
}
@SuppressLint("NewApi")
public boolean isHardwareDetected() {
return isFingerprintSupported()
&& fingerprintManager != null
&& fingerprintManager.isHardwareDetected();
}
@SuppressLint("NewApi")
public boolean hasEnrolledFingerprints() {
// fingerprint hardware supported and api level OK
return isHardwareDetected()
// fingerprints enrolled
&& fingerprintManager != null
&& fingerprintManager.hasEnrolledFingerprints()
// and lockscreen configured
&& keyguardManager.isKeyguardSecure();
}
void setInitOk(final boolean initOk) {
this.initOk = initOk;
}
public boolean isFingerprintSupported() {
return Build.VERSION.SDK_INT >= BuildCompat.VERSION_CODE_M;
}
}