Android KeyStoreException: - android

My app is using Android's keystore to encrypt some data after authenticating with a fingerprint. This seems to work on most devices but I have received error reports of OnePlus2 users with the exception
android.security.KeyStoreException: Signature/MAC verification failed
at android.security.KeyStore.getKeyStoreException(KeyStore.java:632)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.update(KeyStoreCryptoOperationChunkedStreamer.java:132)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:217)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:473)
at javax.crypto.Cipher.doFinal(Cipher.java:1502)
My code basically does this (Written in Mono for Android):
Cipher _cipher = Cipher.GetInstance(KeyProperties.KeyAlgorithmAes + "/"
+ KeyProperties.BlockModeCbc + "/"
+ KeyProperties.EncryptionPaddingPkcs7);
KeyStore _keystore = KeyStore.GetInstance("AndroidKeyStore");
FingerprintManager _fingerprintManager = (FingerprintManager) Context.GetSystemService(Context.FingerprintService);
_keystore.Load(null);
var key = _keystore.GetKey(_keyId, null);
_cipher.Init(CipherMode.EncryptMode, key);
_cryptoObject = new FingerprintManager.CryptoObject(_cipher);
_fingerprintManager.Authenticate(_cryptoObject, _cancellationSignal, 0 /* flags */, this, null);
//OnAuthSucceeded:
var mySecret = _cipher.DoFinal(System.Text.Encoding.UTF8.GetBytes(textToEncrypt));
Is there anything wrong with the code? What does the exception mean?

First, your code looks fine.
In my experience Android fingerprint tends to have a lot of weird edge case errors across various devices.. I can't answer exactly but it sounds like a HW or implementation issue with the FP api on oneplus's part. I know XiaoMi and even Google has acknowledged various weird issues with their implementation.
Tips:
make sure you are listening only once for fingerprint. if you listen twice, you can receive the incorrect cipher object, so the encryption won't match.
update your gradle/min sdk/support libraries, all that stuff
hold on to your butt

Related

Android keystore keys wiped regularly

I am using Android's keystore to implement fingerprint unlock of my Android app. I therefore use KeyGenerator to create a key using
var _keyGen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, "AndroidKeyStore")
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(GetAlias(_keyId),
KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
.SetBlockModes(KeyProperties.BlockModeCbc)
// Require the user to authenticate with biometry to authorize every use
// of the key
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingPkcs7)
.SetUserAuthenticationRequired(true);
_keyGen.Init(
builder
.Build());
_keyGen.GenerateKey();
When I later enumerate the aliases in the store I find the key I have created:
_keystore.Load(null);
var aliases = _keystore.Aliases();
if (aliases == null)
{
og("KS: no aliases");
}
else
{
while (aliases.HasMoreElements)
{
var o = aliases.NextElement();
Log("alias: " + o?.ToString());
}
}
While this is working reliably on most devices, some devices (e.g. Google Pixel 4a) seem to "lose" the keys in the Keystore quite regularly. When enumerating the aliases as above, no key is listed anymore. I can reproduce this behavior by updating my app using a debugger (settings are such that SharedPreferences and app data are kept and I do not have this behavior on another device).
Is there anything I can do to prevent losing the keys?

AndroidKeyStore no such provider

I'm just trying to get a basic example of the Android Key Store system to generate a symmetric key. I followed the example in the tutorial (using Kotlin) but I get an error like so:
java.security.NoSuchProviderException: no such provider: AndroidKeyStore
Below is my code where the compiler is throwing an error:
val kpg: KeyGenerator = KeyGenerator.getInstance("DES", "AndroidKeyStore")
On my Gradle, I am using compileSdkVersion and targetSdkVersion to 28. I'm also have a minSdkVersion of 25. All of which should satisfy the Android's doc on using the AndroidKeyStore (min API level 18).
If I remove the provider, everything works like planned, since I'm assuming it goes to the default provider. The same goes for the KeyPairGenerator and KeyStore classes when I try the AndroidKeyStore provider.
Am I using the wrong provider keyword? Or is there some additional setup that I'm supposed to be doing?
Thanks,
Update 1 - So I kept searching and found that you can get a list of available providers on my system. Here's my code below:
for (p in Security.getProviders()) {
//Log.d(TAG, String.format("== %s ==", p.getName()))
println(String.format("== %s ==", p.getName()))
println(String.format("%s", p.info))
// for (s in p.getServices()) {
// //Log.d(TAG, String.format("- %s", s.getAlgorithm()))
// println(String.format("- %s", s.getAlgorithm()))
// }
}
Also, the results match what's in my $JAVA_HOME/jre/lib/security.java.security file:
security.provider.1=sun.security.provider.Sun
security.provider.2=sun.security.rsa.SunRsaSign
security.provider.3=sun.security.ec.SunEC
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider
security.provider.7=com.sun.security.sasl.Provider
security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI
security.provider.9=sun.security.smartcardio.SunPCSC
security.provider.10=apple.security.AppleProvider
So I guess my option is to add the AndroidKeyStore provider to this. I'll update this when I've tried that.
I found out that there's a bug at the moment with using the AndroidKeyStore with unit tests, which I didn't mention in my original question.
See here:
https://github.com/robolectric/robolectric/issues/1518

AndroidKeystore secretkey Can't init cipher

I am using AndroidKeyStore in my app to store a secret key and I am generating that key using KeyGenParameterSpec class. Here's the code:
final SecretKey secretKey;
final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(<validityInSeconds>)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setRandomizedEncryptionRequired(false)
.build();
keyGenerator.init(keyGenParameterSpec);
secretKey = keyGenerator.generateKey();
And after that, I am trying to init the cipher with this key:
final Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
Now, AndroidKeyStore seems to have a weird issue. cipher.init is failing in some cases and the weird part is, I am keeping everything same/constant except trying out different values for validityInSecs. When it fails, it fails with this error:
Caused by: java.lang.NullPointerException: Attempt to get length of null array
at org.spongycastle.crypto.params.KeyParameter.<init>(KeyParameter.java:13)
at org.spongycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(BaseBlockCipher.java:615)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2659)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2570)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
at javax.crypto.Cipher.init(Cipher.java:973)
at javax.crypto.Cipher.init(Cipher.java:908)
I've tried with different values for validityInSec and it fails from some (for values which are quite low) and succeeds for some (when I increase the value). Here are some data points:
10s -> fail 1800s -> success 100s -> fail 750s ->
success
Another interesting thing is that for the same value, it's failing sometimes and succeeding sometimes. Examples of these values are 750s and 625s.
I have no clue what's going on here.
Device: One plus 3t
OS: 8.0.0
Please help.
Edit:
The stack trace is a little misleading here, as it has traces from Spongycastle library.
That's bug in android framework classes. For a cipher, it tries with all the different providers one by one and if it fails, it stores the exception in a single variable. The problem is, it doesn't update that exception variable. So if the cipher failed for all the providers, the exception that it throws is from the first provider. So, it gives you this expression that only that provider was tried.
Here is the relevant code:
http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/javax/crypto/Cipher.java#2546
For my case, after debugging class-by-class over the success case, I found out that it IS trying other providers too apart from SC. Relevant screenshot:
UPDATE 2:
I tried final Cipher cipher = Cipher.getInstance(<transformation>,"AndroidKeyStoreBCWorkaround");.
Now, when I pass duration as 1800s, it succeeds. And when I pass 40s, it fails with the following error:
06-28 20:06:55.462 624-624/in.zeta.android E/EncryptedStoreService: android.security.keystore.UserNotAuthenticatedException: User not authenticated
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:741)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:777)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:109)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2663)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2556)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
at javax.crypto.Cipher.init(Cipher.java:830)
at javax.crypto.Cipher.init(Cipher.java:771)
I am gonna try authenticating user just before trying to init the cipher and see if it works
Although I do wanna solve this, In my production env, my time would anyways be 1800s. Is it okay to forget that it fails at lower duration?
I tried
final Cipher cipher = Cipher.getInstance(<transformation>,"AndroidKeyStoreBCWorkaround");.
By putting the breakpoints, I figured out that this is the provider that actually works.
Now, when I passed duration as 1800s, it succeeded. And when I pass 40s, it failed with the following error:
06-28 20:06:55.462 624-624/in.zeta.android E/EncryptedStoreService: android.security.keystore.UserNotAuthenticatedException: User not authenticated
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:741)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:777)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:109)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2663)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2556)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
at javax.crypto.Cipher.init(Cipher.java:830)
at javax.crypto.Cipher.init(Cipher.java:771)
Relevant source code links from the stack trace:
https://android.googlesource.com/platform/frameworks/base/+/508e665/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java#215
https://android.googlesource.com/platform/frameworks/base/+/83a86c5/keystore/java/android/security/KeyStoreCryptoOperationUtils.java#42
https://android.googlesource.com/platform/frameworks/base/+/master/keystore/java/android/security/KeyStore.java#707
Following the second link, it's not going into KeyStore.OP_AUTH_NEEDED, otherwise it'd have returned null from there. So mostly, it's in LOCKED state.
(https://android.googlesource.com/platform/frameworks/base/+/master/keystore/java/android/security/KeyStore.java#58)
I understand if it needs user authentication while initing the cipher, but was curious why it's successful for the duration of 1800s. I left my app open for > 1800s and then tried with this duration, it failed with the same exception. And then I passed 18000s and it worked. So, turns out it checks if user was authenticated within past keyvalidity secs, which perfectly makes sense, is obvious and if I am not wrong, present in docs too.
So the whole confusion was there due to the wrong exception thrown by Cipher class >.<
I am gonna try authenticating user just before trying to init the cipher and see if it works
I do not know how the validityInSeconds can affect the code, but the exception does not have to do with this
Caused by: java.lang.NullPointerException: Attempt to get length of null array
at org.spongycastle.crypto.params.KeyParameter.<init>(KeyParameter.java:13)
the exception is caused because the key is managed by the AndroidKeyStore but the Cipher is using the cryptographic provider spongycastle. The AES key is not extractable and spongycastle needs it to be able to encrypt
Remove spongycastle from your project or force to use AndroidKeyStore when you init the Cipher
Cipher cipher = Cipher.getInstance(algorithm, "AndroidKeyStore");
The cryptographic providers are selected in order of installation in the operating system. If a cryptographic provider is not explicitly indicated, Android will select spongycastle or AndroidKeyStore according to the one above. I do not know how validityInSeconds affects, but there may be some internal Android behavior that changes the order

UnrecoverableKeyException Failed to obtain information about private key, KeyStoreException: Invalid key blob

In our app we've been having issues with data in the Android Keystore suddenly becoming inaccessible. The specific exception we're seeing is here:
java.security.UnrecoverableKeyException: Failed to obtain information about private key
at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStorePublicKeyFromKeystore(AndroidKeyStoreProvider.java:223)
at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(AndroidKeyStoreProvider.java:259)
at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(AndroidKeyStoreProvider.java:269)
at android.security.keystore.AndroidKeyStoreSpi.engineGetKey(AndroidKeyStoreSpi.java:94)
at java.security.KeyStoreSpi.engineGetEntry(KeyStoreSpi.java:474)
at java.security.KeyStore.getEntry(KeyStore.java:1560)
at <PACKAGE_NAME>.EncryptionInteractor.generateKeys(EncryptionInteractor.java:104)
at <PACKAGE_NAME>.EncryptionInteractor.generateKeys(EncryptionInteractor.java:100)
at <PACKAGE_NAME>.EncryptionInteractor.init(EncryptionInteractor.java:93)
at <PACKAGE_NAME>.EncryptionInteractor.<init>(EncryptionInteractor.java:80)
at <PACKAGE_NAME>.EncryptionInteractor.init(EncryptionInteractor.java:65)
at <PACKAGE_NAME>.<APPLICATION_CLASS>.onCreate(APPLICATION_CLASS.java:17)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1118)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5791)
at android.app.ActivityThread.-wrap1(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1661)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: android.security.KeyStoreException: Invalid key blob
at android.security.KeyStore.getKeyStoreException(KeyStore.java:695)
at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStorePublicKeyFromKeystore(AndroidKeyStoreProvider.java:224)
... 21 more
We have not been able to come up with a reliable way of reproducing the issue. Several articles mention possible states that can cause the Keystore to "forget" a Key or become locked such as here. However, as far as I can tell we have not fallen into any of these edge cases. It seems to happen after letting the device sit for a while after the first setup of the key. We have seen this happen across multiple emulators and devices, ranging from 21 to 26. Additionally these devices have used either swipe to unlock or a PIN. Changing the PIN or security method does not seem to cause the issue. Again, this issue seems to occur after the device has been sitting unused for several days.
I have found two other SOs here and here as well as one Google issue. If I'm understanding correctly, the answer linked in both seems to rely upon the premise that the caller has called setUserAuthenticationValidityDurationSeconds when creating the Key, and we have not done so. Additionally the given solution seems to rely upon just deleting the key and generating a new one.
Below is our setup for the key on for versions >= API 23. I've left out our key generation for versions older than 23, since we've primarily seen this on APIs >= 23.
private static final int RSA_KEY_SIZE = 2048;
private static final String CERT_SUBJECT_STRING = "CN=<COMPANY_NAME> Android App O=<COMPANY_NAME>";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
try {
String alias = KEY_NAME;
KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE);
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(RSA_KEY_SIZE, RSAKeyGenParameterSpec.F4))
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setCertificateNotAfter(end.getTime())
.setCertificateNotBefore(start.getTime())
.setCertificateSerialNumber(BigInteger.ONE)
.setCertificateSubject(new X500Principal(CERT_SUBJECT_STRING))
.build();
generator.initialize(spec);
generator.generateKeyPair();
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
We then attempt to access the keys later via keyStore.getEntry(KEY_NAME, null). Again, this works for a while, but then will begin to throw the above exception.
I also faced issues with stability in KeyStore.
The solution was to use for private key
PrivateKey privKey = ks.getKey(alias, password)
And this to public key
PublicKey pubKey = ks.getCertificate(alias).getPublicKey();
instead of getEntry
ks.getEntry(alias, password)
The problem wasn't in the way you're creating the key but the way you're reading it.
Over more than a year never saw this issue again.

Android create RSA 1024 .NET compatible keys

I am developing an Android Application and I need to generate some RSA private and public keys to use for secure communication with web services. To do this I need to have the public key in a .NET compatible form.
Like:
<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>
So far I managed to to this:
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
keypair = keyGen.genKeyPair();
privateKey = keypair.getPrivate();
publicKey = keypair.getPublic();
// Get the bytes of the public and private keys
byte[] privateKeyBytes = privateKey.getEncoded();
byte[] publicKeyBytes = publicKey.getEncoded();
I've got no clue how to continue. Could you please provide some help ?
For anybody else interested, a very good tutorial can be found in here
http://www.codeproject.com/KB/security/porting_java_public_key.aspx?msg=3407475
If you need Base64 encoding/decoding, because it's not included in Android (at least in API 4) you could use the class from here: iharder.sourceforge.net/current/java/base64/
You don't show the type publicKey. If is not already, you should cast to an RSAPublicKey, then use the getPublicExponent() and getModulus() methods to extract the BigInteger. Then simply use standard Java IO, e.q. PrintStream.println() or printf() to generate the XML components.

Categories

Resources