diff --git a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/AesCipherProvider.java b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/AesCipherProvider.java index c822751..d8c8f4d 100644 --- a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/AesCipherProvider.java +++ b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/AesCipherProvider.java @@ -19,7 +19,6 @@ import android.annotation.TargetApi; import android.content.Context; import android.os.Build; -import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -43,32 +42,32 @@ class AesCipherProvider extends CipherProvider { private static final int AES_KEY_SIZE = 256; AesCipherProvider(@NonNull Context context, @Nullable String keyName) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { - super(context, keyName); - } + this(context, keyName, true); + } + + AesCipherProvider(@NonNull Context context, @Nullable String keyName, boolean keyInvalidatedByBiometricEnrollment) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { + super(context, keyName, keyInvalidatedByBiometricEnrollment); + } private SecretKey findOrCreateKey(String keyName) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, UnrecoverableKeyException, CertificateException, KeyStoreException, IOException { if (keyExists(keyName)) { return getKey(keyName); } - return createKey(keyName); - } + return createKey(keyName, invalidatedByBiometricEnrollment); + } private SecretKey getKey(String keyName) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { return (SecretKey) keyStore.getKey(keyName, null); } @TargetApi(Build.VERSION_CODES.M) - private static SecretKey createKey(String keyName) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { - KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); - keyGenerator.init(new KeyGenParameterSpec.Builder(keyName, - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setKeySize(AES_KEY_SIZE) - .setBlockModes(KeyProperties.BLOCK_MODE_CBC) - .setUserAuthenticationRequired(true) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) - .build()); - return keyGenerator.generateKey(); - } + private static SecretKey createKey(String keyName, boolean invalidatedByBiometricEnrollment) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); + keyGenerator.init(getKeyGenParameterSpecBuilder(keyName, KeyProperties.BLOCK_MODE_CBC, KeyProperties.ENCRYPTION_PADDING_PKCS7, invalidatedByBiometricEnrollment) + .setKeySize(AES_KEY_SIZE) + .build()); + return keyGenerator.generateKey(); + } @Override Cipher cipherForEncryption() throws NoSuchAlgorithmException, NoSuchPaddingException, CertificateException, UnrecoverableKeyException, KeyStoreException, NoSuchProviderException, InvalidAlgorithmParameterException, IOException, InvalidKeyException { diff --git a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/AesEncryptionObservable.java b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/AesEncryptionObservable.java index 54ce440..01cec66 100644 --- a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/AesEncryptionObservable.java +++ b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/AesEncryptionObservable.java @@ -51,10 +51,10 @@ class AesEncryptionObservable extends FingerprintObservable create(Context context, String keyName, String toEncrypt) { + static Observable create(Context context, String keyName, String toEncrypt, boolean keyInvalidatedByBiometricEnrollment) { try { return Observable.create(new AesEncryptionObservable(new FingerprintApiWrapper(context), - new AesCipherProvider(context, keyName), + new AesCipherProvider(context, keyName, keyInvalidatedByBiometricEnrollment), toEncrypt, new Base64Provider())); } catch (Exception e) { diff --git a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/CipherProvider.java b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/CipherProvider.java index 2328fec..bdefe79 100644 --- a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/CipherProvider.java +++ b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/CipherProvider.java @@ -19,7 +19,9 @@ import android.annotation.TargetApi; import android.content.Context; import android.os.Build; +import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -40,14 +42,15 @@ abstract class CipherProvider { final String keyName; final KeyStore keyStore; + final boolean invalidatedByBiometricEnrollment; - CipherProvider(@NonNull Context context, @Nullable String keyName) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { + CipherProvider(@NonNull Context context, @Nullable String keyName, boolean keyInvalidatedByBiometricEnrollment) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { if (keyName == null) { this.keyName = ContextUtils.getPackageName(context) + "." + DEFAULT_KEY_NAME; } else { this.keyName = keyName; } - + invalidatedByBiometricEnrollment = keyInvalidatedByBiometricEnrollment; keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); keyStore.load(null); } @@ -60,6 +63,20 @@ abstract class CipherProvider { @TargetApi(Build.VERSION_CODES.M) abstract Cipher createCipher() throws NoSuchPaddingException, NoSuchAlgorithmException; + @NonNull + @TargetApi(Build.VERSION_CODES.M) + static KeyGenParameterSpec.Builder getKeyGenParameterSpecBuilder(String keyName, String blockModes, String encryptionPaddings, boolean invalidatedByBiometricEnrollment) { + KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(blockModes) + .setUserAuthenticationRequired(true) + .setEncryptionPaddings(encryptionPaddings); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + builder.setInvalidatedByBiometricEnrollment(invalidatedByBiometricEnrollment); + } + return builder; + } + @TargetApi(Build.VERSION_CODES.M) Cipher getCipherForEncryption() throws IOException, GeneralSecurityException { try { diff --git a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RsaCipherProvider.java b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RsaCipherProvider.java index 7302a93..24141f8 100644 --- a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RsaCipherProvider.java +++ b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RsaCipherProvider.java @@ -19,7 +19,6 @@ import android.annotation.TargetApi; import android.content.Context; import android.os.Build; -import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -42,19 +41,18 @@ class RsaCipherProvider extends CipherProvider { RsaCipherProvider(@NonNull Context context, @Nullable String keyName) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { - super(context, keyName); + this(context, keyName, true); } + RsaCipherProvider(@NonNull Context context, @Nullable String keyName, boolean keyInvalidatedByBiometricEnrollment) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { + super(context, keyName, keyInvalidatedByBiometricEnrollment); + } @Override @TargetApi(Build.VERSION_CODES.M) Cipher cipherForEncryption() throws GeneralSecurityException, IOException { KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE); - keyGenerator.initialize(new KeyGenParameterSpec.Builder(keyName, - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_ECB) - .setUserAuthenticationRequired(true) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) + keyGenerator.initialize(getKeyGenParameterSpecBuilder(keyName, KeyProperties.BLOCK_MODE_ECB, KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, invalidatedByBiometricEnrollment) .build()); keyGenerator.generateKeyPair(); diff --git a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RsaEncryptionObservable.java b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RsaEncryptionObservable.java index b761c5d..6703de0 100644 --- a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RsaEncryptionObservable.java +++ b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RsaEncryptionObservable.java @@ -44,13 +44,13 @@ class RsaEncryptionObservable implements ObservableOnSubscribe create(Context context, String keyName, String toEncrypt) { + static Observable create(Context context, String keyName, String toEncrypt, boolean keyInvalidatedByBiometricEnrollment) { if (toEncrypt == null) { return Observable.error(new IllegalArgumentException("String to be encrypted is null. Can only encrypt valid strings")); } try { return Observable.create(new RsaEncryptionObservable(new FingerprintApiWrapper(context), - new RsaCipherProvider(context, keyName), + new RsaCipherProvider(context, keyName, keyInvalidatedByBiometricEnrollment), toEncrypt, new Base64Provider())); } catch (Exception e) { diff --git a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RxFingerprint.java b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RxFingerprint.java index 7825116..1957356 100644 --- a/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RxFingerprint.java +++ b/rxfingerprint/src/main/java/com/mtramin/rxfingerprint/RxFingerprint.java @@ -85,9 +85,38 @@ public static Observable authenticate(@NonNull * Will complete once the authentication and encryption were successful or have failed entirely. */ public static Observable encrypt(@NonNull Context context, @NonNull String toEncrypt) { - return encrypt(EncryptionMethod.AES, context, null, toEncrypt); + return encrypt(EncryptionMethod.AES, context, null, toEncrypt, true); } + /** + * Encrypt data and authenticate the user with his fingerprint. The encrypted data can only be + * accessed again by calling {@link #decrypt(Context, String)}. Will use a default keyName in + * the Android keystore unique to this applications package name. + * If you want to provide a custom key name use {@link #encrypt(Context, String, String)} + * instead. + *

+ * Encrypted data is only accessible after the user has authenticated with + * fingerprint authentication. + *

+ * Encryption uses AES encryption with CBC blocksize and PKCS7 padding. + * The key-length for AES encryption is set to 265 bits by default. + *

+ * The resulting {@link FingerprintEncryptionResult} will contain the encrypted data as a String + * and is accessible via {@link FingerprintEncryptionResult#getEncrypted()} if the + * authentication was successful. Save this data where you please, but don't change it if you + * want to decrypt it again! + * + * @param context context to use + * @param toEncrypt data to encrypt + * @param keyInvalidatedByBiometricEnrollment whether or not the key will be invalidated when fingerprints are added + * or changed. Works only on Android N(API 24) and above. + * @return Observable {@link FingerprintEncryptionResult} that will contain the encrypted data. + * Will complete once the authentication and encryption were successful or have failed entirely. + */ + public static Observable encrypt(@NonNull Context context, @NonNull String toEncrypt, boolean keyInvalidatedByBiometricEnrollment) { + return encrypt(EncryptionMethod.AES, context, null, toEncrypt, keyInvalidatedByBiometricEnrollment); + } + /** * Decrypt data previously encrypted with {@link #encrypt(Context, String)}. *

@@ -132,7 +161,7 @@ public static Observable decrypt(@NonNull Context c * Will complete once the authentication and encryption were successful or have failed entirely. */ public static Observable encrypt(@NonNull Context context, @Nullable String keyName, @NonNull String toEncrypt) { - return encrypt(EncryptionMethod.AES, context, keyName, toEncrypt); + return encrypt(EncryptionMethod.AES, context, keyName, toEncrypt, true); } /** @@ -184,11 +213,42 @@ public static Observable encrypt(@NonNull Encryptio @NonNull Context context, @Nullable String keyName, @NonNull String toEncrypt) { + return encrypt(method, context, keyName, toEncrypt, true); + } + + /** + * Encrypt data with the given {@link EncryptionMethod}. Depending on the given method, the + * fingerprint sensor might be enabled and waiting for the user to authenticate before the + * encryption step. All encrypted data can only be accessed again by calling + * {@link #decrypt(EncryptionMethod, Context, String, String)} with the same + * {@link EncryptionMethod} that was used for encryption of the given value. + *

+ * Take more details about the encryption method and how they behave from {@link EncryptionMethod} + *

+ * The resulting {@link FingerprintEncryptionResult} will contain the encrypted data as a String + * and is accessible via {@link FingerprintEncryptionResult#getEncrypted()} if the + * operation was successful. Save this data where you please, but don't change it if you + * want to decrypt it again! + * + * @param method the encryption method to use + * @param context context to use + * @param keyName name of the key to store in the Android {@link java.security.KeyStore} + * @param toEncrypt data to encrypt + * @param keyInvalidatedByBiometricEnrollment whether or not the key will be invalidated when fingerprints are added + * or changed. Works only on Android N(API 24) and above. + * @return Observable {@link FingerprintEncryptionResult} that will contain the encrypted data. + * Will complete once the operation was successful or failed entirely. + */ + public static Observable encrypt(@NonNull EncryptionMethod method, + @NonNull Context context, + @Nullable String keyName, + @NonNull String toEncrypt, + boolean keyInvalidatedByBiometricEnrollment) { switch (method) { case AES: - return AesEncryptionObservable.create(context, keyName, toEncrypt); + return AesEncryptionObservable.create(context, keyName, toEncrypt, keyInvalidatedByBiometricEnrollment); case RSA: - return RsaEncryptionObservable.create(context, keyName, toEncrypt); + return RsaEncryptionObservable.create(context, keyName, toEncrypt, keyInvalidatedByBiometricEnrollment); default: return Observable.error(new IllegalArgumentException("Unknown encryption method: " + method)); }