You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin AES GCM解密Tag mismatch问题求助

解决Kotlin AES-GCM解密Tag mismatch问题

我在使用Kotlin实现AES-GCM解密时遇到Tag mismatch错误,密钥、IV、Tag和密文均来自第三方API,其官方JavaScript解密示例代码如下:

for (var i = 0; i < validations.length; i++) {
  var decipher = forge.cipher.createDecipher("AES-GCM", sessionKey);
  decipher.start({
    iv: atob(validations[i].iv),
    tag: atob(validations[i].tag),
  });

  decipher.update(forge.util.createBuffer(atob(validations[i].data)));
  var pass = decipher.finish();

  if (pass) {
    dec_validations.push({
      data: btoa(decipher.output.data),
      id: validations[i].id,
    });
  }
}

我的Kotlin实现代码如下:

fun main() {
val myTestKey = "22+uMvw39JLTXeBJweORNRQPeVneGP0+oQgvwmjSx4Y="
val iv = "+7/pEeTMDH8uyShG"
val tag = "3sSY6JEEY8EdXMmrnhgN3Q=="
val cipherText = "jzgiE5bec+ty0xI3zhsXZVbCuUwSttOBpuH2Ts0R/CLVh9Vf4rw2HM7ZRWizXcQ1SElurXdNNFRyfXMF8N/WUjjPgaUAV9dvtB8wfDqkq5rsbvpUIi8OAVXvAa/rwhMZgfTcIOUbB2q3A+tft3T+K4aBSOTl1a8btmjMmlZ+lFYIXWLBSu0L9As6gAkSUkCWOQMs7Up9guOnP9r6E/lhO1gQnuPkebiKjJWkVYM+Ezwgnne0b4P6dD59aPc03eqxmMRdxk5/SSbPLysj2pHprsD01KsID+Ny5sJDS1gvXz8eegTySriKEIpMn8nUkK5yKru4aEgGFcz0amSlK1dmqmAdL6ZOPcT3EAcobY="
val decodedKey = Base64.getDecoder().decode(myTestKey)
val sessionKeySpec: Key = SecretKeySpec(decodedKey, "AES")

val ivBytes = Base64.getDecoder().decode(iv)
val tagBytes = Base64.getDecoder().decode(tag)
val gcmSpec: AlgorithmParameterSpec = GCMParameterSpec(128, ivBytes)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")

cipher.init(Cipher.DECRYPT_MODE, sessionKeySpec, gcmSpec)

val ciphertext = Base64.getDecoder().decode(cipherText)
val combined = ByteBuffer.allocate(ciphertext.size + tagBytes.size)
    .put(ciphertext)
    .put(tagBytes)
    .array()

val plaintext = cipher.doFinal(combined)

println(String(plaintext))
}

我已尝试以下两种方案,但问题仍未解决:

  • 添加cipher.updateAAD(tagBytes)
  • 将Tag字节与密文拼接

解决方法

问题核心是Java/Kotlin的Cipher与Forge的AES-GCM处理逻辑存在差异:Forge支持单独传入Tag做验证,而Java系的Cipher需要将Tag作为最后一段数据传入doFinal,而非和密文直接拼接后一次性传入。

修改后的Kotlin代码如下:

fun main() {
    val myTestKey = "22+uMvw39JLTXeBJweORNRQPeVneGP0+oQgvwmjSx4Y="
    val iv = "+7/pEeTMDH8uyShG"
    val tag = "3sSY6JEEY8EdXMmrnhgN3Q=="
    val cipherText = "jzgiE5bec+ty0xI3zhsXZVbCuUwSttOBpuH2Ts0R/CLVh9Vf4rw2HM7ZRWizXcQ1SElurXdNNFRyfXMF8N/WUjjPgaUAV9dvtB8wfDqkq5rsbvpUIi8OAVXvAa/rwhMZgfTcIOUbB2q3A+tft3T+K4aBSOTl1a8btmjMmlZ+lFYIXWLBSu0L9As6gAkSUkCWOQMs7Up9guOnP9r6E/lhO1gQnuPkebiKjJWkVYM+Ezwgnne0b4P6dD59aPc03eqxmMRdxk5/SSbPLysj2pHprsD01KsID+Ny5sJDS1gvXz8eegTySriKEIpMn8nUkK5yKru4aEgGFcz0amSlK1dmqmAdL6ZOPcT3EAcobY="
    
    // 解码密钥
    val decodedKey = Base64.getDecoder().decode(myTestKey)
    val sessionKeySpec: Key = SecretKeySpec(decodedKey, "AES")
    
    // 解码IV和Tag
    val ivBytes = Base64.getDecoder().decode(iv)
    val tagBytes = Base64.getDecoder().decode(tag)
    
    // 初始化GCM参数和Cipher
    val gcmSpec = GCMParameterSpec(128, ivBytes)
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(Cipher.DECRYPT_MODE, sessionKeySpec, gcmSpec)
    
    // 处理密文
    val ciphertext = Base64.getDecoder().decode(cipherText)
    cipher.update(ciphertext)
    
    // 传入Tag完成解密和验证
    val plaintext = cipher.doFinal(tagBytes)
    
    println(String(plaintext))
}

额外确认点:

  • 解码后的IV为12字节,符合GCM推荐的标准长度
  • 解码后的Tag为16字节,对应GCM默认的128位验证标签长度

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 03:24:54