0

I am getting to many of below android security related exception/crash on my test enviourment

Device: Galaxy A52 5G Android: 11

android.security.KeyStore.getKeyStoreException KeyStore.java:1441
android.security.KeyStore.getInvalidKeyException KeyStore.java:1548
android.security.keystore.AndroidKeyStoreRSACipherSpi.initKey AndroidKeyStoreRSACipherSpi.java:420
android.security.keystore.AndroidKeyStoreCipherSpiBase.init AndroidKeyStoreCipherSpiBase.java:170
android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit AndroidKeyStoreCipherSpiBase.java:106
javax.crypto.Cipher.tryTransformWithProvider Cipher.java:2984
android.security.keystore.AndroidKeyStoreProvider.getKeyCharacteristics AndroidKeyStoreProvider.java:252
android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore AndroidKeyStoreProvider.java:378
android.security.keystore.AndroidKeyStoreSpi.engineGetKey AndroidKeyStoreSpi.java:120
java.security.KeyStore.getKey KeyStore.java:1062

Backgorund In my project we are using RSA to send Push notification from Firbase to devices, server uses client public key to encrypt the notification data and client uses its private key to decrypt the notification after receiving it before displaying to users.

Code

fun retrieveKey(keyAlias: String): KeyPair? {
 val keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore")
    keyStore.load(null)

    val privateKey: PrivateKey? = keyStore.getKey(keyAlias, null) as? PrivateKey
    val publicKey: PublicKey? = keyStore.getCertificate(keyAlias)?.publicKey

    // If key is already found, return it
    if (privateKey != null && publicKey != null) {
      return KeyPair(publicKey, privateKey)
    }

    // Even if a key is not found, any records of a key alias must be purged
    keyStore.deleteEntry(keyAlias)

    // Generate it with keystore set to the provider, so that the provider takes care of storing it
    val generator: KeyPairGenerator =
        KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore")

    val keySpec: KeyGenParameterSpec = KeyGenParameterSpec
        .Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
        .setKeySize(1024)
        .build()

    generator.initialize(keySpec)


    val keyPair: KeyPair? = try {
      generator.generateKeyPair()
    } catch (exception: Throwable) {
      Crashes.trackError(exception)
      null
    }

    return keyPair
  }

usage To register a user device and sending a public key to server

fun register(pushToken: String, userCredentials: UserCredentials): Result<Unit> {
    
    val keyPair: KeyPair? = keyProvider.retrieveKey(PUSH_NOTIFICATIONS_KEY_ALIAS)

    val publicKeySpecFromEncoded: X509EncodedKeySpec? =
      keyPair?.public?.encoded?.let { X509EncodedKeySpec(it) }

    val pushKey: String? =
      publicKeySpecFromEncoded?.encoded?.let { String(Base64.encode(it, Base64.DEFAULT)) }

    val encryptionInstructions: EncryptionInstructions? =
      pushKey?.let { EncryptionInstructions(key = it) }

    val registration = DeviceRegistration(
      pushKey = pushKey.orEmpty(),
      pushToken = pushToken,
      encryptionInstructions = encryptionInstructions,
      deviceUuid = userCredentials.deviceUuid
    )

    return authWebServices.registerDevice(registration)
  }

And here is the code for function to decrypt the notification

decrypt(data: String, privateKey: Key?): PushNotificationData? {
    val cipher: Cipher = Cipher.getInstance(ENCRYPTION_CIPHER)
    cipher.init(Cipher.DECRYPT_MODE, privateKey)

    val encrypted: ByteArray = Base64.decode(data, Base64.DEFAULT)

    // This should only deal with RSA keys
    if (privateKey !is RSAKey) return null

    // Block size will limit how much we can decrypt at once, and specific to cipher
    val blockSize: Int = DECRYPTION_BLOCK_SIZE
    val chunks: List<ByteArray> = encrypted.chunked(blockSize)

    // Catch any errors when decrypting
    val decrypted: ByteArray = try {
      chunks.fold(ByteArray(encrypted.size)) { acc, byteArray ->
        acc + cipher.doFinal(byteArray)
      }
    } catch (t: Throwable) {
      Crashes.trackError(t)
      return null
    }
......
......
return pushNotificationData
}

I had tried puting this in try catch but it will silently fail the notification to crash. the app is working properly on android 12 and above.

Sultan
  • 119
  • 1
  • 12
  • Your stacktrace is trimmed to exclude some of the most important pieces of information: the actual exceptions that're thrown and the description strings. It should include the final exception and any re-thrown exceptions, particularly the original exception. – President James K. Polk May 30 '23 at 14:48

0 Answers0