17

My app uses Android 6.0 Fingerprint API to protect AES key in the Android KeyStore. The stored key can be used only when user is authenticated by fingerprint sensor because the KeyGenParameterSpec is initialized with setUserAuthenticationRequired(true).

When the user touches the sensor I get the initialized Cipher from the callback onAuthenticationSucceeded(Cipher) and I use it for decryption.

This works perfectly except on Samsung phones with Android 6. When I try to use the returned Cipher, Samsung phones sometimes throw android.security.KeyStoreException: Key user not authenticated. So even though the Cipher is returned by the onAuthenticationSucceeded(Cipher) the Android KeyStore thinks user was NOT authenticated by the fingerprint sensor.

It seems that the crash happens rather when the app was not used for longer time. When the app is wormed up all is working correctly usually.

As this error happens randomly and only on Samsung phones... It seems it is caused by some internal timing issue inside the Samsung implementation of Android 6.0 KeyStore and FingerPrint API.

Edit: This issue was also experienced in OnePlus and Acer phones.

petrsyn
  • 4,678
  • 3
  • 38
  • 47

9 Answers9

19

Setting KeyGenParameterSpec.setUserAuthenticationRequired(false) can be a potential security issue. The above error should be handled similar to KeyPermanentlyInvalidatedException. KeyPermanentlyInvalidatedException is thrown on Cipher initialization if new fingerprints are added after your SecretKey is created. But, if the Cipher is initialized before the new fingerprints are added, you'll get the above KeyStoreException for Key User not authenticated, when you're trying to encrypt or decrypt with that Cipher.

It's easy to reproduce this error. While your app's fingerprint verification screen is in the background, try adding a new fingerprint. Now switch back to the app, and enter the fingerprint, the encryption or decryption methods would throw this error. I could resolve this issue by catching the exception and treating it the same way as KeyPermanentlyInvalidatedException.

NullPointer
  • 476
  • 6
  • 13
  • This error happens randomly on Samsung phones (and some others too) while on other phones like LG it never happens. It is not comfortable for users if they have to re-authenticate for every 5th or 10th access to the app to refresh the stored password. – petrsyn Jul 28 '16 at 22:22
  • Yeah, I see this on only Samsung devices too. But how often do people add a new fingerprint to their device? Samsung supports only 4 max. So unless somebody is playing around with their fingerprint settings a lot, it shouldn't be too bad. Do you see this error in any other case, other than adding a new fingerprint? – NullPointer Jul 28 '16 at 23:18
  • 16
    Unfortunately this bug is not related to adding new fingerprint. It happens randomly. – petrsyn Jul 28 '16 at 23:46
  • can you please explain how can we handle KeyPermanentlyInvalidatedException or provide a link where it has been handled. Thanks – Smit Davda Feb 07 '17 at 12:19
  • I've been treating KeyPermanentlyInvalidatedException similar to any other fatal errors, by falling back to using password for authentication. Once the user is authenticated, a new SecretKey is generated and the data is encrypted with a Cipher that uses the new SecretKey. I'm curious to see how others are handling it, to see if there are better ways to do this. – NullPointer Feb 07 '17 at 21:56
6

Do NOT listen to the "setUserAuthenticationRequired(false)"

I have been able to reproduce this on Samsung by listening twice. What I think is happening is, you listen twice, authenticate on the one call, but reference it through another.

add Logs and check that you only start listening for fingerprint once and once only.

Chris Merrick
  • 110
  • 1
  • 4
  • Please not that this happens occasionally and randomly. It doesn't happen on all Samsungs always. – petrsyn Jan 06 '17 at 12:34
  • 2
    Its definitely a race condition between listening too many times. and yes, it only happens on certain samsung phones. – Chris Merrick Jan 10 '17 at 15:41
  • I agree with some kind of race condition. With my code I'm not actually listening twice, but I had a method where I was calling initSign() to see if the signature had been invalidated, then calling initSign() again later when attempting to complete the login process. – Montwell Oct 16 '18 at 19:57
3

As I don't expect that the mentioned manufacturers will fix this issue soon, I've resolved it by setting the KeyGenParameterSpec.setUserAuthenticationRequired(false) for Samsung, OnePlus, Asus and some other devices.

petrsyn
  • 4,678
  • 3
  • 38
  • 47
  • 24
    This answer is implying that one should set user authentication requirements because it is or is not a Samsung phone. This is bad on so many levels. `setUserAuthenticationRequired` is a question for your security plan, not for fixing bugs. – Matt Quigley Sep 06 '16 at 23:57
  • Actually supporting fingerprint unlock on Samsung phones with Android 5 was done without the setUserAuthenticationRequired because this option is available since Android 6. And it is bad user experience when the app crashes randomly. Fix from Samsung can't be expecting soon. If you have some idea how this can be fixed write it here. I'll gladly make it the accepted solution. – petrsyn Sep 07 '16 at 16:44
  • I don't know the answer myself, unfortunately, but I suspect it's not Samsung given that this happens on many device types. What I do know is that when encountering this error, my team found that they weren't accommodating for the fact that the devices require device authentication in some circumstances - such as if it's been 24 hours, you must enter PIN. – Matt Quigley Sep 07 '16 at 20:42
  • In fact - if that's the issue - then the question to answer may be, how does one get the user authentication when you only are dealing with fingerprints? – Matt Quigley Sep 07 '16 at 20:44
  • Last comment - I'd look over https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder.html#setUserAuthenticationRequired(boolean) carefully, and it has some links to follow with more info. – Matt Quigley Sep 07 '16 at 20:57
  • If your team solved this issue, can anyone of them publish solution here? I would appreciate it and will make it accepted solution. The error happens within few minutes and happens randomly. Nothing related to e.g. 24h interval. – petrsyn Sep 07 '16 at 23:49
  • 4
    Well funny story, it turns out that this was a problem with Samsung 6.0.1 devices, and that we ended up just doing `setUserAuthenticationRequired(false)`. We even had this audited by an outside security firm and there is no actual security problem unless the phone is rooted. – Matt Quigley Feb 22 '17 at 23:01
  • Somebody filed a bug over here: https://code.google.com/p/android/issues/detail?id=227919#makechanges. The bug should be fixed in the latest Android N security patch, however somebody else mentioned that the bug still occurs. This also doesn't fix the problem for devices that don't receive the update, so it's probably best to handle te exception. – Wirling Feb 28 '17 at 08:16
  • 1
    @Wirling how would you handle the exception while still allowing things to proceed? I mean will I still get an initialized Cipher? – John Ernest Guadalupe Apr 28 '17 at 06:32
  • @JohnErnestGuadalupe I just do some default error handling and let the user try again. I think there is no other way to recover from this. – Wirling Apr 28 '17 at 06:51
3

I experienced this issue too. In my case, it was due to the fact that I was accidentally starting two concurrent fingerprint authentications by calling FingerprintManager.authenticate() twice. The error disappeared once I removed the second call.

Venator85
  • 9,577
  • 7
  • 39
  • 57
  • Yes, this. Since I couldn't figure out why it was set twice. I left it as is and deployed. Funny thing, Samsung S8 crashes, Pixel2 just acts all weird on this bug. It's important to make sure you're actually correctly canceling the authentication, in my case passing around CancellationSignal object I forgot to pass the object to another class, thus was left with deaf CancellationSignal – ochitos May 07 '18 at 15:45
2

UPDATE: This is a known issue with Android 8.0 https://issuetracker.google.com/issues/65578763

I'm just now seeing this error on Samsung and appears to be caused when adding a new fingerprint. In our code we expect KeyPermenantlyInvalidatedException to be thrown during signature.initSign(). This doesn't occur and the initialized signature is successfully passed inside the CryptoObject to the FingerprintManager. The fingerprint is then successfully verified and onAuthenticationSucceeded is called. The error occurs when attempting to call signature.update(byte[] bytes).

Expected behavior I would believe is that KeyInvalidatedException is actually thrown, but I'm not sure we can ever expect this to be resolved. My solution is to catch it in the onAuthenticationSucceeded side.

@Override
    public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
        Log.d(LOG_TAG, "Device Authentication Succeeded");
        try {
            Signature signature = result.getCryptoObject().getSignature();
            String authData = getAuthData();
            signature.update(authData.getBytes());
            // do something with signature

        } catch (SignatureException e) {
            Log.d(LOG_TAG, e.getMessage());
            if(e.getMessage() != null && e.getMessage().contains("Key user not authenticated")) {
                // handle as if were KeyPermanentlyInvalidatedException
            } else {
                Log.d(LOG_TAG, e.getMessage());
                // handle as regular error
            }
        }
    }
Montwell
  • 427
  • 3
  • 12
2

I also had this issue when using Samsung Galaxy S8 with android 8. For solve this problem I just remove key from keystore and generate new after that use new key for encrypt and decrypt data

try {
    keyStore.deleteEntry(KEY_ALIAS);
} catch (KeyStoreException e) {
    e.printStackTrace();
}
fuliozor
  • 91
  • 1
  • 3
1

I also had this issue when using RSA and could solve it by creating a copy of the public key as soon as i want to encrypt some data.

// create a copy of the public key -> workaround for android.security.KeyStoreException: Key user not authenticated
val publicKey = KeyFactory
    .getInstance("RSA")
    .generatePublic(X509EncodedKeySpec(keyPair.public.encoded))

// encrypt with the public key
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val encryptedData = cipher.doFinal(data)
BauerMitFackel
  • 372
  • 5
  • 14
0

It works on my Samsung Galaxy S8 by explicitly setting the authenticated key's validity duration:

setUserAuthenticationValidityDurationSeconds(10);

This however makes it technically possible to use the key multiple times within that timespan without requiring further user authentication.

Personally I don't think it's such a big risk.

I have not tested encrypting large streams that may take several seconds to complete using these protection measures. I wonder what happens if the encryption task takes longer than what the validity duration allows.

whitebrow
  • 1,610
  • 16
  • 21
0

This seems to be a bug in Android that arises after an update.

I had it on a OnePlus. I removed the device lock from the settings and set it again. After that, the issue was gone.

Derlin
  • 8,518
  • 2
  • 22
  • 42