19

I am trying to create a simple Kotlin object that wraps access to the app's shared preferences, by encrypting content before saving it.

Encrypting seems to work OK, but when I try to decrypt, I get an javax.crypto.AEADBadTagException, which triggers from a 'android.security.KeyStoreException: Signature/MAC verification failed'.

I have tried debugging to see what's the underlying issue, but I can't find anything. No search has given me any clue, I seem to follow a few guides to the letter without success.

private val context: Context?
    get() = this.application?.applicationContext
private var application: Application? = null

private val transformation = "AES/GCM/NoPadding"
private val androidKeyStore = "AndroidKeyStore"
private val ivPrefix = "_iv"
private val keyStore by lazy { this.createKeyStore() }

private fun createKeyStore(): KeyStore {
    val keyStore = KeyStore.getInstance(this.androidKeyStore)
    keyStore.load(null)
    return keyStore
}

private fun createSecretKey(alias: String): SecretKey {
    val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, this.androidKeyStore)

    keyGenerator.init(
        KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .build()
    )

    return keyGenerator.generateKey()
}

private fun getSecretKey(alias: String): SecretKey {
    return if (this.keyStore.containsAlias(alias)) {
        (this.keyStore.getEntry(alias, null) as KeyStore.SecretKeyEntry).secretKey
    } else {
        this.createSecretKey(alias)
    }
}

private fun removeSecretKey(alias: String) {
    this.keyStore.deleteEntry(alias)
}

private fun encryptText(alias: String, textToEncrypt: String): String {
    val cipher = Cipher.getInstance(this.transformation)
    cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(alias))

    val ivString = Base64.encodeToString(cipher.iv, Base64.DEFAULT)
    this.storeInSharedPrefs(alias + this.ivPrefix, ivString)

    val byteArray = cipher.doFinal(textToEncrypt.toByteArray(charset("UTF-8")))
    return String(byteArray)
}

private fun decryptText(alias: String, textToDecrypt: String): String? {
    val ivString = this.retrieveFromSharedPrefs(alias + this.ivPrefix) ?: return null

    val iv = Base64.decode(ivString, Base64.DEFAULT)
    val spec = GCMParameterSpec(iv.count() * 8, iv)
    val cipher = Cipher.getInstance(this.transformation)
    cipher.init(Cipher.DECRYPT_MODE, getSecretKey(alias), spec)

    try {
        val byteArray = cipher.doFinal(textToDecrypt.toByteArray(charset("UTF-8")))
        return String(byteArray)
    } catch (e: Exception) {
        e.printStackTrace()
        return null
    }
}

private fun storeInSharedPrefs(key: String, value: String) {
    this.context?.let {
        PreferenceManager.getDefaultSharedPreferences(it).edit()?.putString(key, value)?.apply()
    }
}

private fun retrieveFromSharedPrefs(key: String): String? {
    val validContext = this.context ?: return null
    return PreferenceManager.getDefaultSharedPreferences(validContext).getString(key, null)
}

Can anyone point me in the right direction ?

  • have you found the solution? – Ravi Kanasagra Jun 18 '19 at 08:42
  • No, I just do without for now as it’s not a hard requirement for me. – Thomas Debouverie Jun 22 '19 at 09:02
  • Could it be a device-specific problem? I have seen this exception on a "HUAWEI P smart 2020" with Android 9. Other devices seem to work. – Stan Aug 25 '20 at 09:28
  • Could be related https://androidforums.com/threads/aeadbadtagexception-caused-by-keystoreexception-signature-mac-verification-failed.1327080/ – Stan Aug 25 '20 at 11:29
  • you inconsitently initiate cipher, with spec in decrypt and without in encrypt. Is your explicit spec identical with the default one used in encrypt? – Stachu Oct 05 '20 at 22:04

2 Answers2

1

I had similar issue. Its was all about android:allowBackup="true"

Issue:

This issue will occur while uninstalling the app and then re-installing it again. KeyStore will get cleared on uninstall but the preferences not getting removed, so will end up trying to decrypt with a new key thus exception thrown.

Solution:

Try disabling android:allowBackup

<application android:allowBackup="false" ... >
Santhosh Joseph
  • 624
  • 6
  • 16
0

When you change your authentiation tag length from iv.count() to 128 it will work.

Hylke
  • 261
  • 1
  • 2
  • 13
  • 1
    I've tried 128. It is still throwing the same exception. – Srikar Reddy Feb 18 '20 at 09:53
  • 1
    I think you should do 128 precisely, not 128 * 8. Not sure if that is whwat you are doing. Also i checked with my code, and the differences are very minor: everytime i access the keyStore i repeat the KeyStore.getInstance(androidkeystore).also { it.load(null) }, i dont do that lazily. Also validate that the IV bytearray you receive from encryption is exactly the same as the IV bytearray you use in decryption, perhaps the base64 encoding/decoding does some weird stuff. – Hylke Feb 18 '20 at 15:34
  • GCMParameterSpec(128, iv) did not helped for me as well. – Yazon2006 Aug 11 '20 at 08:18