Java tutorial
/* * Copyright 2017 Hans Cappelle * * This file is part of KeePassDroid. * * KeePassDroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * KeePassDroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>. * */ package com.keepassdroid.fingerprint; import android.app.KeyguardManager; import android.content.Context; import android.os.Build; import android.support.v4.os.CancellationSignal; import android.security.keystore.KeyProperties; import android.support.v4.hardware.fingerprint.FingerprintManagerCompat; import com.keepassdroid.compat.KeyGenParameterSpecCompat; import com.keepassdroid.compat.KeyguardManagerCompat; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.UnrecoverableEntryException; import java.security.UnrecoverableKeyException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import biz.source_code.base64Coder.Base64Coder; 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 boolean cryptoInitOk = false; private FingerPrintCallback fingerPrintCallback; private CancellationSignal cancellationSignal; private FingerprintManagerCompat.AuthenticationCallback authenticationCallback; public void setAuthenticationCallback( final FingerprintManagerCompat.AuthenticationCallback authenticationCallback) { this.authenticationCallback = authenticationCallback; } public void startListening() { // no need to start listening when not initialised if (!isFingerprintInitialized()) { if (fingerPrintCallback != null) { fingerPrintCallback.onException(); } return; } if (!cryptoInitOk) { // Crypto key didn't initialize correctly, don't start listening return; } // starts listening for fingerprints with the initialised crypto object cancellationSignal = new CancellationSignal(); fingerprintManager.authenticate(cryptoObject, 0 /* flags */, cancellationSignal, authenticationCallback, null); } 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(); void onException(boolean showWarningMessage); void onException(CharSequence message); void onException(int resId); void onKeyInvalidated(); } 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; } public void initEncryptData() { cryptoInitOk = false; if (!isFingerprintInitialized()) { if (fingerPrintCallback != null && hasEnrolledFingerprints()) { fingerPrintCallback.onException(); } return; } try { initEncryptKey(false); } catch (final InvalidKeyException invalidKeyException) { try { fingerPrintCallback.onKeyInvalidated(); initEncryptKey(true); } catch (InvalidKeyException e) { fingerPrintCallback.onInvalidKeyException(); } catch (Exception e) { fingerPrintCallback.onException(); ; } } catch (final Exception e) { fingerPrintCallback.onException(); } } private void initEncryptKey(boolean deleteExistingKey) throws Exception { createNewKeyIfNeeded(deleteExistingKey); keyStore.load(null); final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null); cipher.init(Cipher.ENCRYPT_MODE, key); cryptoInitOk = true; stopListening(); startListening(); } public void encryptData(final String value) { if (!isFingerprintInitialized()) { if (fingerPrintCallback != null && hasEnrolledFingerprints()) { fingerPrintCallback.onException(); } return; } try { // actual do encryption here byte[] encrypted = cipher.doFinal(value.getBytes()); final String encryptedValue = new String(Base64Coder.encode(encrypted)); // 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 = new String(Base64Coder.encode(spec.getIV())); fingerPrintCallback.handleEncryptedResult(encryptedValue, ivSpecValue); } catch (final Exception e) { fingerPrintCallback.onException(); } } public void initDecryptData(final String ivSpecValue) { cryptoInitOk = false; if (!isFingerprintInitialized()) { if (fingerPrintCallback != null) { fingerPrintCallback.onException(false); } return; } else if (!hasEnrolledFingerprints()) { return; } try { initDecryptKey(ivSpecValue, false); } catch (final InvalidKeyException invalidKeyException) { // Key was invalidated (maybe all registered fingerprints were changed) // Retry with new key try { fingerPrintCallback.onKeyInvalidated(); initDecryptKey(ivSpecValue, true); } catch (InvalidKeyException e) { fingerPrintCallback.onInvalidKeyException(); } catch (Exception e) { fingerPrintCallback.onException(); } } catch (final Exception e) { fingerPrintCallback.onException(); } } private void initDecryptKey(final String ivSpecValue, boolean deleteExistingKey) throws Exception { createNewKeyIfNeeded(deleteExistingKey); 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 = Base64Coder.decode(ivSpecValue); final IvParameterSpec spec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, key, spec); cryptoInitOk = true; stopListening(); startListening(); } public void decryptData(final String encryptedValue) { if (!isFingerprintInitialized()) { if (fingerPrintCallback != null) { fingerPrintCallback.onException(); } return; } try { // actual decryption here final byte[] encrypted = Base64Coder.decode(encryptedValue); byte[] decrypted = cipher.doFinal(encrypted); final String decryptedString = new String(decrypted); //final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */); fingerPrintCallback.handleDecryptedResult(decryptedString); } catch (BadPaddingException | IllegalBlockSizeException e) { fingerPrintCallback.onKeyInvalidated(); } catch (final Exception e) { fingerPrintCallback.onException(); } } 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 AlgorithmParameterSpec algSpec = KeyGenParameterSpecCompat.build(ALIAS_KEY, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT, KeyProperties.BLOCK_MODE_CBC, true, KeyProperties.ENCRYPTION_PADDING_PKCS7); keyGenerator.init(algSpec); keyGenerator.generateKey(); } } catch (final Exception e) { fingerPrintCallback.onException(); } } public boolean isHardwareDetected() { return isFingerprintSupported() && fingerprintManager != null && fingerprintManager.isHardwareDetected(); } public boolean hasEnrolledFingerprints() { // fingerprint hardware supported and api level OK return isHardwareDetected() // fingerprints enrolled && fingerprintManager != null && fingerprintManager.hasEnrolledFingerprints() // and lockscreen configured && KeyguardManagerCompat.isKeyguardSecure(keyguardManager); } void setInitOk(final boolean initOk) { this.initOk = initOk; } public boolean isFingerprintSupported() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; } }