【问题标题】:GCM encryption built in MAC check failureGCM 加密内置 MAC 检查失败
【发布时间】:2025-11-30 19:10:01
【问题描述】:

我正在尝试编写几个函数,这些函数可以使用 AES/GCM 加密和 PBKDF2 密钥生成来加密和解密文本。我正在从 CTR (SIC) 加密转换我的代码,而当所有其他方法都正常工作时,MAC 检查失败正在杀死我。

fun encryptAESBasic(input: String, password: String): String {
    val masterpw = getKey(password)
    val random = SecureRandom()
    val salt = ByteArray(16)
    random.nextBytes(salt)

    val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
    val spec: KeySpec = PBEKeySpec(masterpw.toString().toCharArray(), salt, 100, 256)
    val tmp: SecretKey = factory.generateSecret(spec)
    val cipher = Cipher.getInstance("AES/CTR/NoPadding")

    val iv = ByteArray(16)
    SecureRandom().nextBytes(iv)

    cipher.init(Cipher.ENCRYPT_MODE, tmp, IvParameterSpec(iv))
    val cipherText: ByteArray = cipher.doFinal(input.toByteArray(Charset.forName("UTF-8")))

    val ivstring: String = Base64.encodeToString(iv, Base64.NO_WRAP)
    val saltystring: String = Base64.encodeToString(salt, Base64.NO_WRAP)
    val cipherstring: String = Base64.encodeToString(cipherText, Base64.NO_WRAP)
    val returnstring: String = ivstring + "-" + saltystring + "-" + cipherstring

    return returnstring
}

fun decryptAESBasic(text: String, password: String): String {

    val arr = text.split("-")
    val iv = Base64.decode(arr[0].toByteArray(Charset.forName("UTF-8")), Base64.NO_WRAP)
    val salt = Base64.decode(arr[1].toByteArray(Charset.forName("UTF-8")), Base64.NO_WRAP)
    val data = arr[2].toByteArray(Charset.forName("UTF-8"))

    val masterpw = getKey(password)
    val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
    val spec: KeySpec = PBEKeySpec(masterpw.toString().toCharArray(), salt, 100, 256)
    val tmp: SecretKey = factory.generateSecret(spec)
    val key: ByteArray = tmp.getEncoded()

    val cipher = Cipher.getInstance("AES/CTR/NoPadding")
    cipher.init(Cipher.DECRYPT_MODE, tmp, IvParameterSpec(iv))
    val credential: ByteArray = cipher.doFinal(Base64.decode(data, Base64.NO_WRAP))
    return credential.toString(Charset.forName("UTF-8"))
}

fun getKey(masterPass: String): ByteArray {
    return masterPass.padEnd(32, '.').toByteArray(Charset.forName("UTF-8"))
}

同样,此代码有效,但我想将其从 CTR 更改为 GCM,但每次我这样做时都会遇到“mac check in GCM failed”错误。任何帮助解释如何/为什么会发生这种情况将不胜感激。

E/AndroidRuntime( 6461): Caused by: javax.crypto.AEADBadTagException: mac check in GCM failed
E/AndroidRuntime( 6461):        at java.lang.reflect.Constructor.newInstance0(Native Method)
E/AndroidRuntime( 6461):        at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
E/AndroidRuntime( 6461):        at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$AEADGenericBlockCipher.doFinal(BaseBlockCipher.java:1485)
E/AndroidRuntime( 6461):        at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:1217)
E/AndroidRuntime( 6461):        at javax.crypto.Cipher.doFinal(Cipher.java:2055)
E/AndroidRuntime( 6461):        at design.codeux.autofill_service.FlutterMyAutofillServiceKt.decryptAESBasic(FlutterMyAutofillService.kt:1003)

【问题讨论】:

  • 尝试在解码密码时指定编码,在加密和解密过程中:masterpw.toString(Charset.forName("UTF-8")。如果没有这个规范,CTR 和 GCM 都不能在我的机器上运行。根据规范,CTR 和 GCM 都可以工作。不同的行为可能是由于不同的默认编码。顺便说一句,GCM 使用 12 字节的 nonce/IV。
  • 此外,通常组件(盐、IV、密文)在字节级别连接,结果是 Base64 编码的。不需要分隔符,因为盐和 IV 的长度在两边都是已知的。但这更像是一种约定,而不是必须的。
  • 非常感谢!您对另一个加密问题的回答实际上是促使我从 CTR 更改为 GCM 的原因
  • 为了完整性:ByteArray.toString(charset: Charset) 使用指定的字符集执行解码,而ByteArray.toString() 像在 Java 中一样返回 object's classname@hashcode(例如 [B@481e504)。

标签: kotlin encryption cryptography pbkdf2 aes-gcm


【解决方案1】:

不要在cipher.init() 中使用IvParameterSpec,而是使用GCMParameterSpec

【讨论】: