cipher.init() is slow on Motorola Edge 50 Ultra.
Description
I'm experiencing slow performance when retrieving values from the secure store using getInternetCredentialsForServer on my Motorola Edge 50 Ultra. It takes arround 1900 ms each time. Some of our customers are also reporting similar issues on other devices as well. The Motorola Edge 50 Ultra is running Android 15.
I have added logging to pinpoint where time is consumed, and it seems that the cipher.initit() in the CipherStorageKeystoreAesGcm class that takes arround 600ms each time. It is very consistent.
/** Initialization vector support. */
object IV {
const val IV_LENGTH = 12
const val TAG_LENGTH = 128
val encrypt = EncryptStringHandler { cipher, key, output ->
cipher.init(Cipher.ENCRYPT_MODE, key)
val iv = cipher.iv
output.write(iv, 0, iv.size)
}
val decrypt = DecryptBytesHandler { cipher, key, input ->
val iv = ByteArray(IV_LENGTH)
timeItNoSuspend(tag = "CustomTimer", name = "IV.read") {
val result = input.read(iv, 0, IV_LENGTH)
if (result != IV_LENGTH) throw IOException("Input stream has insufficient data.")
}
val spec = GCMParameterSpec(TAG_LENGTH, iv)
timeItNoSuspend(tag = "CustomTimer", name = "IV.cipherInit") {
cipher.init(Cipher.DECRYPT_MODE, key, spec)
}
}
}
12:36:00.688 10072 10394 D CustomTimer: getInternetCredentialsForServer ▶ start
12:36:00.707 10072 10401 D CustomTimer: extractGeneratedKey ▶ completed in 2ms
12:36:00.707 10072 10401 D CustomTimer: decryptBytes.getCachedInstance ▶ completed in 0ms
12:36:00.707 10072 10401 D CustomTimer: IV.read ▶ completed in 0ms
-> 12:36:01.291 10072 10401 D CustomTimer: IV.cipherInit ▶ completed in 584ms
12:36:01.291 10072 10401 D CustomTimer: decryptBytes.handlerInitialize ▶ completed in 584ms
12:36:01.292 10072 10401 D CustomTimer: decryptBytes.readBytes ▶ completed in 0ms
12:36:01.423 10072 10401 D CustomTimer: decryptBytes.cipherDoFinal ▶ completed in 131ms
12:36:01.423 10072 10401 D CustomTimer: decryptBytes.total ▶ completed in 716ms
12:36:01.423 10072 10401 D CustomTimer: decryptBytes.getCachedInstance ▶ completed in 0ms
12:36:01.423 10072 10401 D CustomTimer: IV.read ▶ completed in 0ms
-> 12:36:02.011 10072 10401 D CustomTimer: IV.cipherInit ▶ completed in 587ms
12:36:02.011 10072 10401 D CustomTimer: decryptBytes.handlerInitialize ▶ completed in 588ms
12:36:02.011 10072 10401 D CustomTimer: decryptBytes.readBytes ▶ completed in 0ms
12:36:02.538 10072 10401 D CustomTimer: decryptBytes.cipherDoFinal ▶ completed in 527ms
12:36:02.539 10072 10401 D CustomTimer: decryptBytes.total ▶ completed in 1115ms
12:36:02.539 10072 10401 D CustomTimer: DecryptionResult ▶ completed in 1831ms
12:36:02.539 10072 10401 D CustomTimer: decryptToResult ▶ completed in 1834ms
12:36:02.539 10072 10401 D CustomTimer: getInternetCredentialsForServer ✔ resolved in 1852ms
I have configured the setInternetCredentials as such:
await setInternetCredentials(key, key, value, {
accessible: ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
storage: STORAGE_TYPE.AES_GCM_NO_AUTH,
});
Is there anything I can do to make the cipher.initit() run faster, or is there some way to reuse / warm up / cache the the result of the cipher.initit() to improve performance?
Thanks in advance.
Steps to reproduce
Reproducable on some devices, Google Pixel 8a, but especially on Motorola Edge 50 Ultra.
Snack or a link to a repository
https://github.com/yberstad/react-native-keychain/tree/v9.2.2-fork
React Native Keychain version
9.2.2
React Native version
0.77.0
Platforms
Android
Workflow
Expo bare workflow
Architecture
Fabric (New Architecture)
I have testet setting the securityLevel to SECURE_SOFTWARE, to use the TEE, instead of the StrongBox, just to see if this would improve the performance in favour of security. I deleted the app, to be sure that the secure storage was recreated, but this did not imporve anything regarding performace. I thought this was a bit strange?
await setInternetCredentials(key, key, value, {
accessible: ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
storage: STORAGE_TYPE.AES_GCM_NO_AUTH,
securityLevel: SECURITY_LEVEL.SECURE_SOFTWARE,
});
I think it's actually related to strongbox backed keys. I'm doing some analysis of performance and noticed that disabling strongox backed keys improves performance a lot. My test was done on Motorola Razr 50 Ultra. It does improve on top devices as well, like S24, but very little.
Also, following playstore data, devices with particular CPU cores are highly impacted, for example the performance of StrongBox-backed cryptographic operations on devices with Arm Cortex-A53 and Cortex-A55 cores is very bad. I think it would be good to either exclude some devices when deciding if strongbox should be enabled or not on library level or provide a way to disable it from the apps that use rn-keychain.
As a simple improvement, should we actually increase the sdk level requirement for strongbox usage @DorianMazur ? Currently it's set to Android 9.
I read that there were quite a lot of improvements to strongbox in the recent versions and data also confirms that devices up to Android 14 are more impacted by cold app start issues.
@androideveloper Yeah, I think we could raise the minimum to Android 12/13. But the Motorola Edge 50 Ultra is a new phone, even though it's low-end and it's having issues
Yes, that won't cover all cases, but will improve a bit performance of apps on pre-android 12/13 devices. I think in case of Motorola it depends on implementation of strongbox on vendor side.
I've prepared PR for it https://github.com/oblador/react-native-keychain/pull/754