package de.devland.masterpassword.util; import android.Manifest; import android.app.KeyguardManager; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.support.annotation.RequiresApi; import android.support.v4.app.ActivityCompat; import android.support.v4.util.Pair; import android.util.Base64; import android.util.Log; 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.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.spec.InvalidParameterSpecException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import de.devland.esperandro.Esperandro; import de.devland.masterpassword.App; import de.devland.masterpassword.R; import de.devland.masterpassword.base.ui.BaseActivity; import de.devland.masterpassword.base.util.SnackbarUtil; import de.devland.masterpassword.prefs.DefaultPrefs; import static android.content.Context.KEYGUARD_SERVICE; /** * Created by David Kunzler on 16.04.2017. */ public class FingerprintUtil { private static final String TAG = "FingerprintUtil"; private static final String KEY_NAME = "de.devland.masterpassword.key"; public static boolean canUseFingerprint(boolean doSnackbar) { BaseActivity activity = App.get().getCurrentForegroundActivity(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //Get an instance of KeyguardManager and FingerprintManager// KeyguardManager keyguardManager = (KeyguardManager) App.get().getSystemService(KEYGUARD_SERVICE); FingerprintManager fingerprintManager = (FingerprintManager) App.get().getSystemService(Context.FINGERPRINT_SERVICE); //Check whether the device has a fingerprint sensor// if (!fingerprintManager.isHardwareDetected()) { // If a fingerprint sensor isn’t available, then inform the user that they’ll be unable to use your app’s fingerprint functionality// if (doSnackbar) SnackbarUtil.showShort(activity, R.string.fingerprint_device_unsupported); return false; } //Check whether the user has granted your app the USE_FINGERPRINT permission// if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { // If your app doesn't have this permission, then display the following text// if (doSnackbar) SnackbarUtil.showShort(activity, R.string.fingerprint_permission); return false; } //Check that the user has registered at least one fingerprint// if (!fingerprintManager.hasEnrolledFingerprints()) { // If the user hasn’t configured any fingerprints, then display the following message// if (doSnackbar) SnackbarUtil.showShort(activity, R.string.fingerprint_unconfigured); return false; } //Check that the lockscreen is secured// if (!keyguardManager.isKeyguardSecure()) { // If the user hasn’t secured their lockscreen with a PIN password or pattern, then display the following text// if (doSnackbar) SnackbarUtil.showShort(activity, R.string.fingerprint_noLock); return false; } return true; } else { if (doSnackbar) SnackbarUtil.showShort(activity, R.string.fingerprint_device_unsupported); return false; } } @RequiresApi(api = Build.VERSION_CODES.M) public static Cipher initEncryptCipher() throws FingerprintException { Cipher encryptCipher = getCipher(Cipher.ENCRYPT_MODE, getKeyStore(), null); if (encryptCipher == null) { // try again after recreating the keystore createKey(); encryptCipher = getCipher(Cipher.ENCRYPT_MODE, getKeyStore(), null); } return encryptCipher; } private static KeyStore getKeyStore() { try { return KeyStore.getInstance("AndroidKeyStore"); } catch (KeyStoreException e) { throw new RuntimeException("Failed to get an instance of KeyStore", e); } } @RequiresApi(api = Build.VERSION_CODES.M) public static Cipher initDecryptCipher(String iv) throws FingerprintException { return getCipher(Cipher.DECRYPT_MODE, getKeyStore(), iv); } /** * Tries to encrypt some data with the generated key in {@link #createKey} which is * only works if the user has just authenticated via fingerprint. */ public static Pair<String, String> tryEncrypt(Cipher encryptCipher, String password, String login) { try { String secret = Base64.encodeToString(password.getBytes(), Base64.DEFAULT) + ":" + Base64.encodeToString(login.getBytes(), Base64.DEFAULT); byte[] encrypted = encryptCipher.doFinal(secret.getBytes()); IvParameterSpec ivParams = encryptCipher.getParameters().getParameterSpec(IvParameterSpec.class); String iv = Base64.encodeToString(ivParams.getIV(), Base64.DEFAULT); return new Pair<>(Base64.encodeToString(encrypted, Base64.DEFAULT), iv); } catch (BadPaddingException | IllegalBlockSizeException e) { SnackbarUtil.showLong(App.get().getCurrentForegroundActivity(), "Failed to encrypt the data with the generated key."); Log.e(TAG, "Failed to encrypt the data with the generated key." + e.getMessage()); } catch (InvalidParameterSpecException e) { e.printStackTrace(); } return null; } /** * Tries to decrypt some data with the generated key in {@link #createKey} which is * only works if the user has just authenticated via fingerprint. */ public static Pair<String, String> tryDecrypt(Cipher decryptCipher, String payload) { try { byte[] encodedData = Base64.decode(payload, Base64.DEFAULT); byte[] decodedData = decryptCipher.doFinal(encodedData); String[] parts = new String(decodedData).split(":"); return new Pair<>(new String(Base64.decode(parts[0], Base64.DEFAULT)), new String(Base64.decode(parts[1], Base64.DEFAULT))); } catch (BadPaddingException | IllegalBlockSizeException | ArrayIndexOutOfBoundsException e) { SnackbarUtil.showLong(App.get().getCurrentForegroundActivity(), "Failed to decrypt the data with the generated key."); Log.e(TAG, "Failed to decrypt the data with the generated key." + e.getMessage()); e.printStackTrace(); } return null; } @RequiresApi(api = Build.VERSION_CODES.M) private static SecretKey getKey(KeyStore keyStore) throws FingerprintException { try { keyStore.load(null); SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null); if (key != null) return key; return createKey(); } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException | UnrecoverableKeyException e) { throw new FingerprintException(App.get().getString(R.string.fingeprint_secretKeyUnrecoverable), e); } } @RequiresApi(api = Build.VERSION_CODES.M) private static SecretKey createKey() throws FingerprintException { try { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); // 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(KEY_NAME, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT) .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()); return keyGenerator.generateKey(); } catch (Exception e) { throw new FingerprintException(App.get().getString(R.string.fingeprint_secretKeyGenerationFailed), e); } } @RequiresApi(api = Build.VERSION_CODES.M) private static Cipher getCipher(int mode, KeyStore keyStore, String ivString) throws FingerprintException { Cipher cipher; try { keyStore.load(null); byte[] iv; cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); IvParameterSpec ivParams; if (mode == Cipher.ENCRYPT_MODE) { cipher.init(mode, getKey(keyStore)); } else { SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null); iv = Base64.decode(ivString, Base64.DEFAULT); ivParams = new IvParameterSpec(iv); cipher.init(mode, key, ivParams); } return cipher; } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnrecoverableKeyException | InvalidKeyException | KeyStoreException | InvalidAlgorithmParameterException | IOException | CertificateException e) { throw new FingerprintException(App.get().getString(R.string.fingeprint_cipherUnrecoverable), e); } } public static void resetFingerprintSettings() { DefaultPrefs defaultPrefs = Esperandro.getPreferences(DefaultPrefs.class, App.get()); defaultPrefs.fingerprintEnabled(false); defaultPrefs.encrypted(null); defaultPrefs.encryptionIV(null); try { KeyStore keyStore = getKeyStore(); keyStore.load(null); keyStore.deleteEntry(KEY_NAME); } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { Log.e(TAG, "error deleting key", e); } } }