react-native-keychain
react-native-keychain copied to clipboard
`E_CRYPTO_FAILED` on Android 13 and Pixel phones. AES crashing out while decrypting.
Steps to reproduce:
- Get a pixel device (since android 13 is only available on that).
- Update it to android 13.
- Interact with keychain, especially
Keychain.getGenericPassword({ service: KEY })
Version info:
{
"react": "17.0.2",
"react-native": "0.66.4",
"react-native-keychain": "8.0.0"
}
Stack trace:
{
"nativeStackAndroid": [
{
"lineNumber": 150,
"file": "CipherStorageKeystoreAesCbc.java",
"methodName": "decrypt",
"class": "com.oblador.keychain.cipherStorage.CipherStorageKeystoreAesCbc"
},
{
"lineNumber": 165,
"file": "CipherStorageKeystoreAesCbc.java",
"methodName": "decrypt",
"class": "com.oblador.keychain.cipherStorage.CipherStorageKeystoreAesCbc"
},
{
"lineNumber": 679,
"file": "KeychainModule.java",
"methodName": "decryptToResult",
"class": "com.oblador.keychain.KeychainModule"
},
{
"lineNumber": 646,
"file": "KeychainModule.java",
"methodName": "decryptCredentials",
"class": "com.oblador.keychain.KeychainModule"
},
{
"lineNumber": 306,
"file": "KeychainModule.java",
"methodName": "getGenericPassword",
"class": "com.oblador.keychain.KeychainModule"
},
{
"lineNumber": 367,
"file": "KeychainModule.java",
"methodName": "getGenericPasswordForOptions",
"class": "com.oblador.keychain.KeychainModule"
},
{
"lineNumber": -2,
"file": "Method.java",
"methodName": "invoke",
"class": "java.lang.reflect.Method"
},
{
"lineNumber": 372,
"file": "JavaMethodWrapper.java",
"methodName": "invoke",
"class": "com.facebook.react.bridge.JavaMethodWrapper"
},
{
"lineNumber": 188,
"file": "JavaModuleWrapper.java",
"methodName": "invoke",
"class": "com.facebook.react.bridge.JavaModuleWrapper"
},
{
"lineNumber": -2,
"file": "NativeRunnable.java",
"methodName": "run",
"class": "com.facebook.react.bridge.queue.NativeRunnable"
},
{
"lineNumber": 942,
"file": "Handler.java",
"methodName": "handleCallback",
"class": "android.os.Handler"
},
{
"lineNumber": 99,
"file": "Handler.java",
"methodName": "dispatchMessage",
"class": "android.os.Handler"
},
{
"lineNumber": 27,
"file": "MessageQueueThreadHandler.java",
"methodName": "dispatchMessage",
"class": "com.facebook.react.bridge.queue.MessageQueueThreadHandler"
},
{
"lineNumber": 201,
"file": "Looper.java",
"methodName": "loopOnce",
"class": "android.os.Looper"
},
{
"lineNumber": 288,
"file": "Looper.java",
"methodName": "loop",
"class": "android.os.Looper"
},
{
"lineNumber": 226,
"file": "MessageQueueThreadImpl.java",
"methodName": "run",
"class": "com.facebook.react.bridge.queue.MessageQueueThreadImpl$4"
},
{
"lineNumber": 1012,
"file": "Thread.java",
"methodName": "run",
"class": "java.lang.Thread"
}
],
"userInfo": null,
"message": "Could not decrypt data with alias: TOKEN_IDENTIFIER",
"code": "E_CRYPTO_FAILED",
"line": LINE_NUMBER,
"column": COLUMN_NUMBER,
"sourceURL": "http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=APP_NAME&modulesOnly=false&runModule=true"
}
I am also having this issue.
{
"react": "^18.0.0",
"react-native": "^0.69.3",
"react-native-keychain": "^8.1.1",
}
Using Keychain.STORAGE_TYPE.AES:
-
Error: Could not encrypt data with alias:
Using Keychain.STORAGE_TYPE.RSA:
-
Error: I/O error: javax.crypto.IllegalBlockSizeException: input must be under 384 bytes
The RSA error is understandable as I am trying to store an RSA-2048 Private Key in Base64 PKCS8 format, which is too large to encrypt with RSA encryption.
However I think the AES solution should work.
I get this error both on the Emulator (Pixel 4) and on my physical device (Galaxy S20)
I could fix it by using Facebook Conceal (STORAGE_TYPE.FB
) and adding the proguard rules to enable FB
. This is a stop-gap solution and we'd still need to find the real issue behind the error.
A quick update after further investigation:
await Keychain.setInternetCredentials('test', 'test', JSON.stringify(keys), {
accessControl: Keychain.ACCESS_CONTROL.DEVICE_PASSCODE,
storage: Keychain.STORAGE_TYPE.RSA,
})
// The below prompts for a user fingerprint instead of pass-code
const res = await Keychain.getGenericPassword({ service: 'test })
The above doesn't work/throws an exception if the device has a screen-lock/pass-code set but no fingerprint configured.
After I added a fingerprint, the above prompts me for a fingerprint instead of a pass-code.
i thought Keychain.ACCESS_CONTROL.DEVICE_PASSCODE
prompted for a pass-code only / no bio-metrics?
Any ideas?
Any timeline on when this might be fixed?
It is occuring on my end too - on different devices! I can't reproduce it in development so far. A workaround would be much appreciated!
"react-native-keychain": "^8.1.1",
"react-native": "0.69.2",
Android Version: 12
Model: Samsung SM-M536B
or
Android Version: 10
Model: OPPO CPH2185
or
Android Version: 12
Model: Samsung Galaxy A53 5G
or
Android version: 11
Model: Nokia G11
or
Android version: 11
Model: Moto g10
or
Android version: 12
Model: Pixel 6 Pro
etc.
code: E_CRYPTO_FAILED,
message: Could not decrypt data with alias: ,
name: Error,
Occuring for all these calls:
const savedCredentials = await RNKeychain.getGenericPassword({
storage: STORAGE_TYPE.AES,
rules: SECURITY_RULES.AUTOMATIC_UPGRADE,
});
const savedCredentials = await RNKeychain.getGenericPassword();
const result = await RNKeychain.getGenericPassword({
storage: STORAGE_TYPE.AES,
});
Is STORAGE_TYPE.FB working on all devices?
Seeing this error on Android 13 (pixel) but not a lot of other places. We can move to FB, but that'd require all of our users to either sign back in, or delete the app and reinstall.
Still unsure of why tho
@ilyagru
To fix your issue on single device
I believe the device MUST have some biometrics or PIN configured to be able to use RNKeychain, even with AES.
To reproduce issue
To reproduce your issue. Reset your emulator and make sure no pin or fingerprint or face id
is configured at all. Then you will see it start failing to encrypt with AES.
Error: Could not encrypt data with alias:
Interesting. Our one user who can reproduce it has bio enabled (finger). (we have a load of customers with the problem, but only one test device we can reproduce it on)
FWIW, this all worked on Android 12, and started failing once 13 came out :( At least on our ones, tho I think it might be a slightly wider issue.
I've FINALLY managed to get the device we had failing (as well as a bunch of customer devices).
Pixel 5 + Android 13. I also have a Pixel 6a + Android 13 which is fine, so... FML.
But here's a full stacktrace from the native side
E [email protected]: FinishOperation : device response error code: INVALID_ARGUMENT
E keystore2: keystore2::error: In KeystoreOperation::finish
E keystore2:
E keystore2: Caused by:
E keystore2: 0: In finish: KeyMint::finish failed.
E keystore2: 1: Error::Km(ErrorCode(-38))
W CipherStorageBase: null
W CipherStorageBase: javax.crypto.BadPaddingException
W CipherStorageBase: at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:609)
W CipherStorageBase: at javax.crypto.Cipher.doFinal(Cipher.java:2114)
W CipherStorageBase: at com.oblador.keychain.cipherStorage.CipherStorageKeystoreAesCbc.decryptBytes(CipherStorageKeystoreAesCbc.java:247)
W CipherStorageBase: at com.oblador.keychain.cipherStorage.CipherStorageKeystoreAesCbc.decryptBytes(CipherStorageKeystoreAesCbc.java:270)
W CipherStorageBase: at com.oblador.keychain.cipherStorage.CipherStorageKeystoreAesCbc.decrypt(CipherStorageKeystoreAesCbc.java:147)
W CipherStorageBase: at com.oblador.keychain.cipherStorage.CipherStorageKeystoreAesCbc.decrypt(CipherStorageKeystoreAesCbc.java:165)
W CipherStorageBase: at com.oblador.keychain.KeychainModule.decryptToResult(KeychainModule.java:679)
W CipherStorageBase: at com.oblador.keychain.KeychainModule.decryptCredentials(KeychainModule.java:646)
W CipherStorageBase: at com.oblador.keychain.KeychainModule.getGenericPassword(KeychainModule.java:306)
W CipherStorageBase: at com.oblador.keychain.KeychainModule.getInternetCredentialsForServer(KeychainModule.java:438)
W CipherStorageBase: at java.lang.reflect.Method.invoke(Native Method)
W CipherStorageBase: at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
W CipherStorageBase: at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:188)
W CipherStorageBase: at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
W CipherStorageBase: at android.os.Handler.handleCallback(Handler.java:942)
W CipherStorageBase: at android.os.Handler.dispatchMessage(Handler.java:99)
W CipherStorageBase: at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
W CipherStorageBase: at android.os.Looper.loopOnce(Looper.java:201)
W CipherStorageBase: at android.os.Looper.loop(Looper.java:288)
W CipherStorageBase: at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:226)
W CipherStorageBase: at java.lang.Thread.run(Thread.java:1012)
W CipherStorageBase: Caused by: android.security.KeyStoreException: Invalid argument (internal Keystore code: -38 message: In KeystoreOperation::finish
W CipherStorageBase:
W CipherStorageBase: Caused by:
W CipherStorageBase: 0: In finish: KeyMint::finish failed.
W CipherStorageBase: 1: Error::Km(ErrorCode(-38))) (public error code: 10 internal Keystore code: -38)
W CipherStorageBase: at android.security.KeyStore2.getKeyStoreException(KeyStore2.java:369)
W CipherStorageBase: at android.security.KeyStoreOperation.handleExceptions(KeyStoreOperation.java:78)
W CipherStorageBase: at android.security.KeyStoreOperation.finish(KeyStoreOperation.java:128)
W CipherStorageBase: at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer$MainDataStream.finish(KeyStoreCryptoOperationChunkedStreamer.java:228)
W CipherStorageBase: at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:181)
W CipherStorageBase: at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:603)
W CipherStorageBase: ... 20 more
E RNKeychainManager: Could not decrypt data with alias: @TendAuthStorage:CognitoIdentityServiceProvider.6odfecgm4grcov3r3ct7qvaobv.nic.wise+msg@tend.nz.accessToken
I ReactNativeJS: CognitoClient.getIdToken: Error: Could not decrypt data with alias: @TendAuthStorage:CognitoIdentityServiceProvider.6odfecgm4grcov3r3ct7qvaobv.nic.wise+msg@xxxxx.nz.accessToken
I ReactNativeJS: CognitoClient.getIdToken: CATASTROPHIC_FAILURE
Still trying to work out if it's something with how RNKeystore is driving the Android API's, or if it's a bug in THIS Android device
@oblador
oh the irony :)
// decrypt the bytes using cipher.doFinal(). Using a CipherInputStream for decryption has historically led to issues
// on the Pixel family of devices.
// see https://github.com/oblador/react-native-keychain/issues/383
OK, I'm 99% sure I've found the problem.
On this device, we fail to generate an RSA key cos reasons:
RNKeychainManager: warming up started at 1053735047449122
RNKeychainManager: Probe cipher storage: CipherStorageFacebookConceal
RNKeychainManager: Probe cipher storage: CipherStorageKeystoreAesCbc
RNKeychainManager: Probe cipher storage: CipherStorageKeystoreRsaEcb
RNKeychainManager: Selected storage: CipherStorageKeystoreRsaEcb
CipherStorageBase: StrongBox security storage is not available.
CipherStorageBase: java.security.ProviderException: Failed to generate key pair.
CipherStorageBase: at android.security.keystore2.AndroidKeyStoreKeyPairGeneratorSpi.generateKeyPairHelper(AndroidKeyStoreKeyPairGeneratorSpi.java:717)
CipherStorageBase: at android.security.keystore2.AndroidKeyStoreKeyPairGeneratorSpi.generateKeyPair(AndroidKeyStoreKeyPairGeneratorSpi.java:627)
CipherStorageBase: at java.security.KeyPairGenerator$Delegate.generateKeyPair(KeyPairGenerator.java:746)
CipherStorageBase: at com.oblador.keychain.cipherStorage.CipherStorageKeystoreRsaEcb.generateKey(CipherStorageKeystoreRsaEcb.java:269)
CipherStorageBase: at com.oblador.keychain.cipherStorage.CipherStorageBase.tryGenerateStrongBoxSecurityKey(CipherStorageBase.java:476)
CipherStorageBase: at com.oblador.keychain.cipherStorage.CipherStorageBase.tryGenerateStrongBoxSecurityKey(CipherStorageBase.java:461)
CipherStorageBase: at com.oblador.keychain.cipherStorage.CipherStorageBase.generateKeyAndStoreUnderAlias(CipherStorageBase.java:414)
CipherStorageBase: at com.oblador.keychain.KeychainModule.internalWarmingBestCipher(KeychainModule.java:174)
CipherStorageBase: at com.oblador.keychain.KeychainModule.lambda$DYujhqpjRgfFQ_gyuwMwyxxqDlk(Unknown Source:0)
CipherStorageBase: at com.oblador.keychain.-$$Lambda$KeychainModule$DYujhqpjRgfFQ_gyuwMwyxxqDlk.run(Unknown Source:2)
CipherStorageBase: at java.lang.Thread.run(Thread.java:1012)
CipherStorageBase: Caused by: android.security.KeyStoreException: Unsupported key size (internal Keystore code: -6 message: In generate_key.
CipherStorageBase:
CipherStorageBase: Caused by:
CipherStorageBase: 0: While generating Key without explicit attestation key.
CipherStorageBase: 1: Error::Km(ErrorCode(-6))) (public error code: 12 internal Keystore code: -6)
CipherStorageBase: at android.security.KeyStore2.getKeyStoreException(KeyStore2.java:369)
CipherStorageBase: at android.security.KeyStoreSecurityLevel.handleExceptions(KeyStoreSecurityLevel.java:57)
CipherStorageBase: at android.security.KeyStoreSecurityLevel.generateKey(KeyStoreSecurityLevel.java:145)
CipherStorageBase: at android.security.keystore2.AndroidKeyStoreKeyPairGeneratorSpi.generateKeyPairHelper(AndroidKeyStoreKeyPairGeneratorSpi.java:690)
CipherStorageBase: ... 10 more
RNKeychainManager: warming up takes: 255 ms
If we do this we have a broken key, and nothing works at all. So we go to store a bunch of stuff, the key failed to generate (and if we stored it, it should also fail to LOAD), so we get errors like this:
E [email protected]: FinishOperation : device response error code: INVALID_ARGUMENT
E keystore2: keystore2::error: In KeystoreOperation::finish
E keystore2:
E keystore2: Caused by:
E keystore2: 0: In finish: KeyMint::finish failed.
E keystore2: 1: Error::Km(ErrorCode(-38))
W CipherStorageBase: null
W CipherStorageBase: javax.crypto.BadPaddingException
cos, well, we gave it a broken key.
if I remove RSA (cos even if I had a choice, I do not want to use it):
//KeychainModule.java line 138
/** Default constructor. */
public KeychainModule(@NonNull final ReactApplicationContext reactContext) {
super(reactContext);
prefsStorage = new PrefsStorage(reactContext);
addCipherStorageToMap(new CipherStorageFacebookConceal(reactContext));
addCipherStorageToMap(new CipherStorageKeystoreAesCbc());
// we have a references to newer api that will fail load of app classes in old androids OS
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// addCipherStorageToMap(new CipherStorageKeystoreRsaEcb());
// }
}
then... EVERYTHING WORKS. I can leave it alone and not tell it to use FB Conceal.
Now I need to work out how I can do this in a way where the initial setup can be "no, I really don't want RSA please" cos I guess a lot of folks are using it.
~Not sure why my other Android 13 device WILL generate a key tho. Going to go look at the logs for that now.~ It doesn't. See below. But it defaults to AES not RSA
Further to this.
2 devices. Pixel 6a + 13, Pixel 5 + 13
Both fail to generate the RSA key. Same error around keysize.
However, the 6a (which largely works) defaults to AES when storage is probed. The 5 defaults to RSA still (and fails loudly above, basically doesn't work unless I pick a specific cypher, and even then....).
RNKeychainManager: warming up started at 1053735047449122
RNKeychainManager: Probe cipher storage: CipherStorageFacebookConceal
RNKeychainManager: Probe cipher storage: CipherStorageKeystoreAesCbc
RNKeychainManager: Probe cipher storage: CipherStorageKeystoreRsaEcb
RNKeychainManager: Selected storage: CipherStorageKeystoreRsaEcb <----- AES for 6a, RSA for 5
Removing RSA works, but only cos the 5 just stops looking at it. Not great. The pixel5 fails cos it can't generate a key, but still uses RSA by default. Which is very odd given the code flows, which should look like they fail in a sane way if we can't generate a key.
I have NO idea why the 6a picks AES and not RSA where the 5 picks RSA. That bit makes no sense to me
Having to fall back to the facebook one, tho if I can find a good way to force AES, I might switch to that with the automatic upgrade thing.
@nicwise Were you able to find out anything else about this problem? I'm running into the same issue. Also wondering if nothing else has been discovered yet if you think it would be a viable solution to check the users platform and if its a pixel to comment out the code above
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // addCipherStorageToMap(new CipherStorageKeystoreRsaEcb()); // }
That way we wouldn't disrupt the usability of the plugin for other devices/sign existing users out on the next update, etc...
Still experiencing the same issue as well, we still haven't been able to reproduce it but it's actively happening to a bunch of users on Pixels 4, 4a, 5, 6, 6a, 6 Pro, 7 and 7 Pro.
@CaptainJeff no, I thought I'd fixed it here by forcing it to use the Facebook crypto thing (which I don't trust, but sadly need to). But we are getting reports now of it failing again, so....
Biggest problem is I can't reproduce it on anything except customer devices. We have a Pixel 5 and 6a and neither of them fail anymore :(
I'd switch to another library but I'm not sure there are any.
@Thaisagathem yeah, this is exactly how we are seeing it too :(
We're having the same issue and we seriously struggle to reproduce it as well.
We're throwing a few theories to the wall at the moment to see what sticks: From my colleague @jemise111: Would it be possible that the issue is specific to apps which had the Keychain setup while the OS was on Android 12 or earlier, and that it now fails since the device got updated to Android 13? It could explain the difficulty to reproduce when we all fresh install the app on our device.
We also started experiencing this issue in our production app with android 13 google pixel 7
We have that issue too "since 2 years" now ... But it is not only Android 12->13 because we have it also on Android 8.1, 10, 12 and several different device types. So we are also still trying to find out.
we have the same issue on some Pixels with Android 13
We are seeing this here as well across the board of different Pixel devices on Android 13 but not all. Any idea how to work around this issue?
A workaround seems to be to switch to the FB cypher for Android. This way my Pixel was able to retrieve the auth token after a restart of the app. Is there anyone else that is using this workaround ?
await Keychain.setGenericPassword(key, value, {
service: servuce,
storage: isIOS ? Keychain.STORAGE_TYPE.KC : Keychain.STORAGE_TYPE.FB,
})
@nicwise Have you managed to fix it?
I've encountered this issue on a project I've been working on, so have spent a bit of time investigating.
This stack overflow (https://stackoverflow.com/a/65299208/5909648) prompted me to take a look at the code and there are a number of places in the codebase where strings are being converted to byte arrays, and vice versa.
I'm inclined to agree with the stack overflow post, and that the issue is likely to be caused by loss of information in these conversions. It's a pretty big change to remove this conversion as it will have potential knock on impact on the JS -> native interface.
Unrelated to React Native in itself, but I can confirm this issue happens on Pixel 4a / Android 13 with GCM-AES.
Both biometrics and PIN are set. Such a puzzling issue.
Same issue on Pixels and Android 13. Some news about a solution?
same issue with Google Pixel 6a Android 13. With version 6.0.0 it works! With version 8.0.0 doesn't!
I'm seeing this issue on Samsung devices from our logs in production. I do not have any Samsung devices, but I have a Google Pixel 6a myself, unfortunately I'm unable to reproduce the error on this device. Are you @alessioemireni doing anything special to reproduce the error?
Hi @yberstad. I reproduce the error on my Dev Pixel 6a with Android 13 installing the version 8.0.0 of the library and with Fingerprint Enabled.
BTW, I thought of using the workaround that @taschik suggested, but according to this https://github.com/oblador/react-native-keychain/issues/314#issuecomment-705581503, STORAGE_TYPE.FB is just an "obfuscation". That might be good enough in some cases, I was hoping to go for a more secure approach...