I am facing issue with Fingerprint authentication, actually, I have integrated Fingerprint Authentication in my app and working fine except one case.
I have set up two Fingerprint in my device, after initializing KeyGenerator with Fingerprint KEY, I have removed one Fingerprint from device and come back to my app and performing Fingerprint Auth it is working fine. I don't know why it is not triggering InvalidKeyException as like adding Fingerprint works. Is this expected behavior or any bug with OS?
Device Details are below,
Device : Pixel
OS: Android 8.0
Code of my implementation follows,
protected void generateKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (Exception e) {
e.printStackTrace();
}
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
throw new RuntimeException("Failed to get KeyGenerator instance", e);
}
try {
keyStore.load(null);
keyGenerator.init(new
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException |
InvalidAlgorithmParameterException
| CertificateException | IOException e) {
throw new RuntimeException(e);
}
}
public boolean cipherInit() {
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("Failed to get Cipher", e);
}
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,
null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
return false;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}
}
}
I have already tried with few posts(How to detect the removal fingerprint in Android?, Android Fingerprint API and Private/Public keys, Android Key Invalidation when Fingerprints removed), nothing helped me to get rid of it.
Your code is not strictly tied to fingerprints. All it has that is somewhat related is:
setUserAuthenticationRequired(true)
That says that the key that you are creating in the AndroidKeyStore requires user authentication. It does not say anything about fingerprints.
If the user has fingerprints registered, the user could use a fingerprint to authenticate for the purposes of using this key. That would be for any fingerprint registered by the user, and in your case, you still have a registered fingerprint (even after the user removed the second fingerprint). Also, the user does not have to use a fingerprint to authenticate — they could use their passphrase, PIN, or pattern, if they choose.
Is this expected behavior or any bug with OS?
This is expected behavior.
Related
I recently implemented fingerprint authentication into one of my apps. Everything works as expected except for this one problem that I discovered when testing. Some background: I generated a key with the following key gen parameters:
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(80)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
so the user will remain authenticated for 80 seconds. Any time the app needs to re-verify the user, it will check if the key is still valid, and otherwise createConfirmDeviceCredentialIntent if the key timed out, which brings up a system screen prompting the user to authenticate their fingerprint or enter a password/pin/pattern.
The problem: Now this screen should be un-cancelable, however if I tap the back button or top arrow button multiple times in a row, the user gets into the app. Has anyone had this experience?
EDIT:
This seems to only be happening on Samsung devices I tested, it works well on the Nexus and Google Pixel.
Here is the code being used, it's from the Google sample project on using createConfirmDeviceCredentialIntent.
This is the method to check the if the key is still valid.
protected boolean tryEncrypt() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
mKeyStore = KeyStore.getInstance("AndroidKeyStore");
mKeyStore.load(null);
SecretKey secretKey = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
mCipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
mCipher.init(Cipher.ENCRYPT_MODE, secretKey);
mCipher.doFinal(SECRET_BYTE_ARRAY);
return true;
} catch (UserNotAuthenticatedException e) {
// User is not authenticated, let's authenticate with device credentials.
showAuthenticationScreen();
return false;
} catch (KeyPermanentlyInvalidatedException e) {
return false;
} catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException |
CertificateException | UnrecoverableKeyException | IOException
| NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
return false;
}
Here is the showEncryption method that is called if the key is timed out:
protected void showAuthenticationScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent intent = getKeyguardManager().createConfirmDeviceCredentialIntent(null, null);
if (intent != null) {
startActivityForResult(intent, 1);
}
}
}
I'm playing around with the Fingerprint demo for Android, in particular the invalidation scenarios, but need a little help to translate this into production worthy logic.
I've tested the app and get the initCipher to fail due to invalidation after adding a fingerprint, but the app has to be running and the key generated while you change the settings. This is because the demo generates a new key each time the app starts. In reality you wouldn't want to do this, but instead generate the key if doesn't exist and reuse it if it does to enforce proper invalidation whether the app is running or not.
How can you modify the app so that the key isn't generated each time, but instead a check to see if one exists first is performed, then that key loaded subsequently? Can you then remove the key once invalidated so the previous logic and enrolment cycle applies?
Found the answer on my own by looking at the KeyStore class a bit more and modifying initCipher(). Not the best implementation, but good enough to test out stuff:
private boolean initCipher(Cipher cipher, String keyName) {
try {
mKeyStore.load(null);
// ADDED: Check is keystore contains my key name
if(!mKeyStore.containsAlias(DEFAULT_KEY_NAME)) {
// ADDED: Create if it doesn't
createKey(DEFAULT_KEY_NAME, true);
}
SecretKey key = (SecretKey) mKeyStore.getKey(keyName, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
// ADDED: Remove the key if it is invalidated so
// it can be created fresh next time
try {
mKeyStore.deleteEntry(keyName);
} catch (KeyStoreException e1) {
e1.printStackTrace();
return false;
}
return false;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
| NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}
}
Also need to remove the createKey() call from onCreate() too obviously.
Am Using Fingerprint Authentication for App. Want to Support for below API 23 also. For that am Using FingerprintManagerCompat. I don't know how to Generate Key and Chiper initiation in Pre-Android API 23.
Below Code am Used for API 23 - Generate Key
protected void generateKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (Exception e) {
e.printStackTrace();
}
try {
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
} catch (NoSuchAlgorithmException |
NoSuchProviderException e) {
throw new RuntimeException("Failed to get KeyGenerator instance", e);
}
try {
keyStore.load(null);
keyGenerator.init(new
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException |
InvalidAlgorithmParameterException
| CertificateException | IOException e) {
throw new RuntimeException(e);
}
}
Below Code am Used for API 23 - Chiper initiation
public boolean cipherInit() {
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException |
NoSuchPaddingException e) {
throw new RuntimeException("Failed to get Cipher", e);
}
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyStoreException | CertificateException
| UnrecoverableKeyException | IOException
| NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}
}
I don't know how initiate these two things in Pre API 23 to access FingerprintManagerCompat, Help me to solve this issue.
FingerprintManager is not available in pre-marshmallow devices. They have added this API in Marshamallow which is specified here
No.You can't Generate Key and cipher below API 23(Marshmallow 6.0).
Some android device have fingerprint sensor below API 21 but android only supports for API 23 and above.You have to use their sdk for fingerprint authentication.Samsung provide their own sdk for fingerprint authentication i.e Pass Sdk.
You can see this link.Sample project for Finger Print Authentication here
I have a project on Android with minSDK=17 and targetSDK=23. We have a fingerprint authentication in this project made using FingerprintManager class (it was added in SDK23). We added SDK version check, so we are not using anything related to fingerprint if SDK<23. But in older SDK versions app behaviour is unpredictable: on some versions app just crashing, on other -- fingerprint not working (so, it's ok).
My question:
1) Is it any good and easy-to-implement libraries for minSDK=17, that can recognize fingerprints?
2) How can I avoid app crashing in devices with SDK<23?
Crash error:
E/dalvikvm: Could not find class 'android.hardware.fingerprint.FingerprintManager', referenced from method nl.intratuin.LoginActivity.loginByFingerprint
E/AndroidRuntime: FATAL EXCEPTION: main java.lang.VerifyError:
LoginActivity at java.lang.Class.newInstanceImpl(Native Method)
Some new info: created HelloWorld fingerprint project using this tutorial:
http://www.techotopia.com/index.php/An_Android_Fingerprint_Authentication_Tutorial
Found the root of the problem:
FingerprintDemoActivity->cipherInit:
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,
null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
return false;
} catch (KeyStoreException | CertificateException
| UnrecoverableKeyException | IOException
| NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}
First catch block breacking whole app with error I mentioned above. Of course, I can just remove this catch (this exception extends InvalidKeyException, so it will be handled), and return false in case of any exceptions. Is it any better way?
I think, I found acceptable solution: catch not KeyPermanentlyInvalidatedException, but InvalidKeyException. Everything working fine this way. Still have no idea how this exception crashed whole app...
It happened to me also..even when i used : if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M).. my app crashes on android 4.4- kitkat. so eventually the problem was in the initCipher method in the catches part - see the following code (even though i m not suppose to get there as it targeted to M and above... very strange behaviour..) :
#TargetApi(Build.VERSION_CODES.M)
private boolean initCipher() {
try {
mKeyStore.load(null);
SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
mCipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
| NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to init Cipher", e);
} catch (InvalidKeyException e) {
e.printStackTrace();
return false;
}
}
apparently the order off the catches matter..so just make sure to write it as i mentioned.
Reason for Crash:
The FingerprintManager class works with Android version 23 and Higher.
If your app is using FingerprintManager class and runs on older version of Android then you will encounter this exception.
Supporting older versions of Android:
Use FingerprintManagerCompat instead of FingerprintManager if you are planning to support Android <23. The FingerprintManagerCompat class internally checks for the Android version and handle Authentication part with ease.
How to Use it:
Replace android.hardware.fingerprint.FingerprintManager With android.support.v4.hardware.fingerprint.FingerprintManagerCompat
Replace android.os.CancellationSignal With android.support.v4.os.CancellationSignal
See Sample Code
https://github.com/hiteshsahu/FingerPrint-Authentication-With-React-Native-Android/blob/master/android/app/src/main/java/com/aproject/view/Fragments/FingerprintAuthenticationDialogFragment.java
Have a look at a library created by afollestad called digitus.
This library can fall back to a password if fingerprints are not available.
Any devices prior to SDK 23 need to use their own separate device manufacturer based sdk.
just follow android studio hint, it will be OK.
try {
mKeyStore.load(null);
SecretKey key = (SecretKey) mKeyStore.getKey(keyName, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (IOException | NoSuchAlgorithmException | CertificateException
| UnrecoverableKeyException | KeyStoreException | InvalidKeyException e) {
e.printStackTrace();
throw new RuntimeException("Failed to init Cipher", e);
}
To answer the second part of the question
How can I avoid app crashing in devices with SDK<23?
This simplistic logic check will suffice:
if (Build.VERSION.SDK_INT < 23) {
// Handle the mechanism where the SDK is older.
}else{
// Handle the mechanism where the SDK is 23 or later.
}
I solved this by moving all the fingerprint code to a helper class so that the classes related to the fingerprint code are not imported in the activity and by instantiating the helper class only when the SDK_INT is greater than 23 (In my case, as I'm supporting only Android 6+)
I also had this problem.Even when i used : if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M).. my app crashes on lower API's. I solved that as follow:
Replaced these codes:
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,
null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
return false;
} catch (KeyStoreException | CertificateException
| UnrecoverableKeyException | IOException
| NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}
with:
try {
keyStore.load(null);
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
SecretKey key = null;
try {
key = (SecretKey) keyStore.getKey(KEY_NAME,
null);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
}
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return true;
I am trying to encode and decode Strings on Android using a Private Key generated and stored using the Android Key Store Provider that was introduced in Android 4.3
I can successfully generate and get the private key using the following code:
private void generatePrivateKey(Activity context, String alias){
/** Generate a new entry in the KeyStore by using the * KeyPairGenerator API. We have to specify the attributes for a * self-signed X.509 certificate here so the KeyStore can attach * the public key part to it. It can be replaced later with a * certificate signed by a Certificate Authority (CA) if needed. */
Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
cal.add(Calendar.YEAR, 1);
Date end = cal.getTime();
KeyPairGenerator kpg = null;
try {
kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
try {
kpg.initialize(new KeyPairGeneratorSpec.Builder(context)
.setAlias(alias)
.setStartDate(now)
.setEndDate(end)
.setSerialNumber(BigInteger.valueOf(1))
.setSubject(new X500Principal("CN=" + alias))
.build());
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
KeyPair kp = kpg.generateKeyPair();
/*
* Load the Android KeyStore instance using the the
* "AndroidKeyStore" provider to list out what entries are
* currently stored.
*/
KeyStore ks = null;
try {
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
/*
* Use a PrivateKey in the KeyStore to create a signature over
* some data.
*/
KeyStore.Entry entry = null;
try {
entry = ks.getEntry(alias, null);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnrecoverableEntryException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
Log.w("E", "Not an instance of a PrivateKeyEntry");
}
else{
Log.w("E", "Got Key!");
privateKeyEntry = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
}
}
And here is the code I am using for encrypt (encode) and decrypt (decode):
private String encryptString(String value){
byte[] encodedBytes = null;
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
cipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry );
encodedBytes = cipher.doFinal(value.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return Base64.encodeToString(encodedBytes, Base64.DEFAULT);
}
private String decryptString(String value){
byte[] decodedBytes = null;
try {
Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
c.init(Cipher.DECRYPT_MODE, privateKeyEntry );
decodedBytes = c.doFinal(Base64.decode(value, Base64.DEFAULT));
} catch (Exception e) {
e.printStackTrace();
}
return new String(decodedBytes);
}
The Encryption appears to work fine but when I try to decrypt it I get the following error:
javax.crypto.BadPaddingException: error:0407106B:rsa routines:RSA_padding_check_PKCS1_type_2:block type is not 02
Googling this seems to suggest that the private key used for decryption is different to the one used for decryption but in my code I use the exact same private key for both. I have also seen it suggested to set the key size manually but doing this in the KeyPairGenerator builder like this:
.setKeySize(1024);
Did not work and seems to be only available on API 19, I need to target API 18.
Can anyone help point me in the right direction as to a solution?
You are not using the public key for encryption.
When you are using asymmetric encryption algorithms, you need to use the public key to encrypt your data, and the private key only to decrypt it again.
Besides encryption, you can also use the private key for signing, but that's not what you want here, so let's forget about that for the moment.
If you take the public key from the generated pair, when you encrypt your string, and the private key when decrypting, you should get the desired result. The public key you can extract by accessing the certificate from the keystore-object that holds your private key.
Alternatively you could also use a symmetric algorithm like AES and by that make your work a lot easier. Plus, symmetric algorithms are usually much faster, which is why asymmetric algorithms are never used purely, but in conjunction with symmetric algorithms, building so-called hybrid algorithms.
Signature generation is not the same thing as encryption. You need to encrypt with the public key and decrypt with the private key if you want encryption. If you want signature generation, you need to sign with the private key and verify with the public key. This order cannot be reversed nor can it be mixed (securely).