【问题标题】:KeyStoreException: Signature/MAC verification failed when trying to decryptKeyStoreException:尝试解密时签名/MAC 验证失败
【发布时间】:2019-04-13 12:17:28
【问题描述】:

我正在尝试创建一个简单的 Kotlin 对象,该对象通过在保存内容之前加密内容来包装对应用程序共享首选项的访问。

加密似乎工作正常,但是当我尝试解密时,我得到一个 javax.crypto.AEADBadTagException,它从“android.security.KeyStoreException:签名/MAC 验证失败”触发。

我已尝试调试以查看根本问题是什么,但我找不到任何东西。没有搜索给我任何线索,我似乎遵循了一些指南,但没有成功。

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)
}

谁能指出我正确的方向?

【问题讨论】:

  • 你找到解决办法了吗?
  • 不,我暂时不用,因为这对我来说不是硬性要求。
  • 可能是特定于设备的问题吗?我在装有 Android 9 的“HUAWEI P smart 2020”上看到了这个异常。其他设备似乎可以工作。
  • 您不定期地启动密码,在解密时使用规范,而在未加密时使用规范。您的显式规范是否与加密中使用的默认规范相同?

标签: android encryption kotlin keystore android-keystore


【解决方案1】:

我有类似的问题。都是关于android:allowBackup="true"

问题:

卸载应用程序然后重新安装时会出现此问题。 KeyStore 将在卸载时被清除,但首选项不会被删除,因此最终会尝试使用新密钥解密,从而引发异常。

解决方案:

尝试禁用 android:allowBackup

<application android:allowBackup="false" ... >

【讨论】:

    【解决方案2】:

    我在密码加密“AES/GCM/NoPadding”上遇到了相同的异常/问题“android.security.KeyStoreException:签名/MAC 验证失败”。

    就我而言,帮助解决此问题的方法是首先创建一个字节数组持有者,其大小通过调用 Cipher.getOutputSize(int inputLen) 获得,然后调用 doFinal 重载 Cipher.doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) 以在字节数组持有者中设置密文.

        private var iv: ByteArray? = null
    
        fun doEncryptionOperation() {
            val keyStore = KeyStore.getInstance(PROVIDER_ANDROID_KEYSTORE).apply {
                load(null)
            }
            // Assumption: key with alias 'secret_key' has already been stored
            val entry = keyStore.getEntry("secret_key", null)
            val secretKeyEntry = entry as KeyStore.SecretKeyEntry
            val key secretKeyEntry.secretKey
    
            val plainText = "Sample plain text"
            val cipherText = encryptSymmetric(key, plainText.toByteArray())
            val decrypted = decryptSymmetric(key, cipherText)
    
            val decryptedStr = String(decrypted)
            val same = decryptedStr == plainText
            Log.d("SampleTag", "Is plaintext same from decrypted text? $same")
        }
    
        fun encryptSymmetric(key: SecretKey, plainText: ByteArray): ByteArray? {
            val cipher = Cipher.getInstance("AES/GCM/NoPadding")
            cipher.init(Cipher.ENCRYPT_MODE, key)
            iv = cipher.iv
            val len = plainText.size
            val outLen = cipher.getOutputSize(len) // get expected cipher output size
            val result = ByteArray(outLen) // create byte array with outLen
            cipher.doFinal(plainText, 0, len, result,0) // doFinal passing plaintext data and result array
            return result
        }
    
        fun decryptSymmetric(key: SecretKey, cipherText: ByteArray): ByteArray? {
            val cipher = Cipher.getInstance("AES/GCM/NoPadding")
            val tagLen = 128 // default GCM tag length
            cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(tagLen,iv))
            cipher.update(input.data)
            val result = cipher.doFinal()
            return result
        }
    

    另外,使用 AEAD 时,不要忘记在 ENCRYPT_MODE 中调用 Cipher.updateAAD(),并在 DECRYPT_MODE 中设置相同的 AEAD 标签。否则,您将遇到相同的 javax.crypto.AEADBadTagException。

    【讨论】:

      【解决方案3】:

      当您将身份验证标签长度从 iv.count() 更改为 128 时,它将起作用。

      【讨论】:

      • 我试过 128。它仍然抛出同样的异常。
      • 我认为你应该精确地做 128,而不是 128 * 8。不确定这是否是你正在做的事情。我还检查了我的代码,差异非常小:每次访问 keyStore 时,我都会重复 KeyStore.getInstance(androidkeystore).also { it.load(null) },我不会懒惰地这样做。还要验证您从加密中收到的 IV 字节数组与您在解密中使用的 IV 字节数组完全相同,也许 base64 编码/解码会做一些奇怪的事情。
      • GCMParameterSpec(128, iv) 对我也没有帮助。
      猜你喜欢
      • 1970-01-01
      • 2017-12-31
      • 2021-06-19
      • 2021-07-21
      • 1970-01-01
      • 2014-06-03
      • 2019-04-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多