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.
Related
I am getting exception for below code
try {
mkeyStore?.load(null)
val keyspec = mkeyStore?.getKey(KEY_ALIAS, null) as SecretKey
} catch {
try {
mkeyStore?.deleteEntry(KEY_ALIAS);
} catch (ex: java.lang.Exception) {
e.printStackTrace();
}
}
Scenario is as below:
fingerprint is added and user registered using fingerprint in app.
Now go to setting remove pin(this also removes already enrolled fingerprints).
Now in settings add pin and fingerprint.
Open app again. As user is already registered using fingerprint , I am initializing cipher with above code. It throws the exception in try as well as catch block. User can't use fingerprint in app at all here on-wards.
(Android 8 - Samsung J600G)
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.
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 cannot obtain a (private) key from KeyStore on Android. Problem occurs mainly
on Samsung devices (S6, S6 Edge) and Android 6.
android.security.KeyStoreException: Invalid key blob
is thrown when following line is called (where alias is name for store key).
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
The KeyStore itself is obtained by
KeyStore.getInstance("AndroidKeyStore");
And key is generated by the following method:
private static void createKey(String alias, String subject, KeyStore keyStore, BigInteger serialNumber, Date startDate, Date endDate, String algorithm, String keyStoreProvider, Context context)
throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
if (keyStore.containsAlias(alias)) {
// Key already exists.
return;
}
// Generate keys.
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(alias)
.setSubject(new X500Principal(subject))
.setSerialNumber(serialNumber)
.setStartDate(startDate)
.setEndDate(endDate)
.build();
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm, keyStoreProvider);
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
}
Where algorithm is "RSA" and keyStoreProvider is "AndroidKeyStore".
The part of the stacktrace:
android.security.KeyStoreException: Invalid key blob
at android.security.KeyStore.getKeyStoreException(KeyStore.java:939)
at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStorePublicKeyFromKeystore(AndroidKeyStoreProvider.java:216)
at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(AndroidKeyStoreProvider.java:252)
at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(AndroidKeyStoreProvider.java:263)
at android.security.keystore.AndroidKeyStoreSpi.engineGetKey(AndroidKeyStoreSpi.java:93)
at java.security.KeyStoreSpi.engineGetEntry(KeyStoreSpi.java:372)
at java.security.KeyStore.getEntry(KeyStore.java:645)
The exception causes java.security.UnrecoverableKeyException: Failed to obtain information about private key to be thrown.
I was not able to find any closer information about "Invalid key blob",
only that the message itself is defined here: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/security/keymaster/KeymasterDefs.java
This problem is occurred when user tries to UNLOCK from LOCK/UNINITIALIZED. It is by default defined as 30 secs for timing. This problem is it's API related implementation issue.
This error is generated from InvalidKeyException. By bypassing this exception and call the method again, you can get rid of this error.
You have to remove the InvalidKeyException class from the catch argument. This will still allow you to check for InvalidKeyException. After checking you have to try for second time with code so that the problem is not shown in eye but doing 2 times checking it may solve your issue. Code is given below.
try {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) this.keyStore
.getEntry("alias", null);
} catch (InvalidKeyException ex) {
ex.printStackTrace();
if (ex instanceof InvalidKeyException) { // bypass
// InvalidKeyException
// You can again call the method and make a counter for deadlock
// situation or implement your own code according to your
// situation
if (retry) {
keyStore.deleteEntry(keyName);
return getCypher(keyName, false);
} else {
throw ex;
}
}
} catch (final Exception e) {
e.printStackTrace();
throw e;
}
You can see my another answer that describes one by one occurring
issue and solution.
UPDATE from #Ankis:
As you solved the issue by changing InvalidKeyException to UnrecoverableKeyException. So I have updated as per your suggestion so that world can know the actual answer. Thanks for sharing :).
try {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) this.keyStore
.getEntry("alias", null);
} catch (UnrecoverableKeyException ex) {
ex.printStackTrace();
// You can again call the method and make a counter for deadlock
// situation or implement your own code according to your
// situation
if (retry) {
keyStore.deleteEntry(keyName);
return getCypher(keyName, false);
}
} catch (final Exception e) {
e.printStackTrace();
throw e;
}
I'm new to android and security.
My app uses an encrypted DB which is encrypted by a private key. I want to find a way to store this private key in a protected place, without adding any additional password/pin code.
From what I've read, Android's keystore is the place to do it, but from my understanding, if I'll use it, it demands that I'll set a pin code for the device (which I don't want to do!).
Any suggestions regarding where to store this key and how? (any keystore related solution is acceptable as long as I don't have to set a pin code)
My direction is using some external open source keystore (any suggestions?) which I'll compile as part of my app (and because android doesn't share information between apps it will be ok to use).
I'm aware that my last assumption isn't correct when using a rooted device, but for my case I use only non-rooted devices.
I've searched a lot (here and else where) and couldn't find what I was looking for...
Any help is highly appreciated!!
10x
One thing you need to keep in mind is that the KeyChain isn't available until API 14. If you intend on targeting earlier API versions you need another option. You could use SpongyCastle to create your own KeyStore.
If you are not going to ask the user for a password you should at the very least obscure the password.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
KeyStore ks = null;
try {
ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null,null);
// Add certs or keys
ks.store(new FileOutputStream(new File(getFilesDir(),"out.bks")),"password".toCharArray());
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
static {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
}
}