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

SpringBoot中AES/GCM/NoPadding解密报AEADBadTagException标签不匹配

AES/GCM/NoPadding解密抛出AEADBadTagException: Tag mismatch!的问题解决

异常信息

Exception Occurred javax.crypto.AEADBadTagException: Tag mismatch!
java.base/com.sun.crypto.provider.GaloisCounterMode$GCMDecrypt.doFinal(GaloisCounterMode.java:1395)
java.base/com.sun.crypto.provider.GaloisCounterMode.engineDoFinal(GaloisCounterMode.java:406)
java.base/javax.crypto.Cipher.doFinal(Cipher.java:2205)
cmi.util.CMIUtil.decrypt(CMIUtil.java:802)
cmi.util.AnalyticsUtil.getBUUserCookieInformation(AnalyticsUtil.java:82)
cmi.util.RequestEncodeFilter.sessionServer(RequestEncodeFilter.java:143)
cmi.util.RequestEncodeFilter.doFilter(RequestEncodeFilter.java:57)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)

当前加解密代码

public static String decrypt(String encrypted) {
    try {
        log.info("In decrypt: "+ encrypted);
      String strPassword = "";
      SecretKeySpec skeySpec = new SecretKeySpec(strPassword.getBytes(), "AES");
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
      GCMParameterSpec spec = new GCMParameterSpec(128, strPassword.getBytes());
      cipher.init(Cipher.DECRYPT_MODE, skeySpec, spec);
      byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
      return new String(original);
    } catch (Exception ex) {
        log.error("Exception Occurred", ex);
    }
    return null;
}

public static String encrypt(String value) {
    log.info("In encrypt: "+ value);
    try {
      String strPassword = "";
      SecretKeySpec skeySpec = new SecretKeySpec(strPassword.getBytes(), "AES");
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
      GCMParameterSpec spec = new GCMParameterSpec(128, strPassword.getBytes());
      cipher.init(Cipher.ENCRYPT_MODE, skeySpec, spec);
      byte[] encrypted = cipher.doFinal(value.getBytes());
      return Base64.getEncoder().encodeToString(encrypted);
    } catch (Exception ex) {
        log.error("Exception Occurred", ex);
    }
    return null;
}

问题根源分析

当前代码存在3个致命错误,直接导致解密时标签不匹配:

  1. 使用空密钥:strPassword是空字符串,生成的密钥是空字节数组,完全不符合AES密钥要求(AES密钥长度必须是16/24/32字节,对应128/192/256位)。
  2. IV重复且无效:GCM模式要求每次加密必须使用唯一的初始化向量(IV),不能重复使用相同IV和密钥的组合。当前代码用空字符串的字节数组作为固定IV,既不符合安全规范,也会导致解密验证失败。
  3. 密钥与IV混用:直接把密钥当作IV使用,完全违背GCM模式的设计逻辑,IV是随机生成的非敏感值,密钥是需要严格保密的核心凭据,二者不能混淆。

修正后的实现方案

核心要点

  • 生成符合要求的AES密钥,安全存储(禁止硬编码,可使用配置中心或密钥管理服务)。
  • 每次加密生成12字节(GCM推荐长度)的随机IV,加密后将IV与密文(包含GCM认证标签)拼接后再Base64编码传递。
  • 解密时先拆分IV和密文,再用相同密钥和IV完成解密。

修正代码示例

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class AESGCMUtil {
    // AES密钥长度:16字节=128位
    private static final int AES_KEY_SIZE = 16;
    // GCM推荐IV长度:12字节=96位
    private static final int GCM_IV_LENGTH = 12;
    // GCM认证标签长度:128位
    private static final int GCM_TAG_LENGTH = 128;

    // 生成AES密钥(仅需生成一次,保存到安全位置)
    public static String generateAESKey() {
        SecureRandom secureRandom = new SecureRandom();
        byte[] keyBytes = new byte[AES_KEY_SIZE];
        secureRandom.nextBytes(keyBytes);
        return Base64.getEncoder().encodeToString(keyBytes);
    }

    // 加密方法
    public static String encrypt(String plainText, String base64Key) {
        try {
            // 解码密钥
            byte[] keyBytes = Base64.getDecoder().decode(base64Key);
            SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");

            // 生成随机IV
            byte[] iv = new byte[GCM_IV_LENGTH];
            SecureRandom secureRandom = new SecureRandom();
            secureRandom.nextBytes(iv);

            // 初始化加密Cipher
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);

            // 执行加密,得到包含标签的密文
            byte[] cipherText = cipher.doFinal(plainText.getBytes());

            // 拼接IV和密文:IV在前,密文在后
            byte[] result = new byte[GCM_IV_LENGTH + cipherText.length];
            System.arraycopy(iv, 0, result, 0, GCM_IV_LENGTH);
            System.arraycopy(cipherText, 0, result, GCM_IV_LENGTH, cipherText.length);

            // Base64编码返回
            return Base64.getEncoder().encodeToString(result);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // 解密方法
    public static String decrypt(String encryptedText, String base64Key) {
        try {
            // 解码加密后的字节数组
            byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);

            // 拆分IV和密文:前12字节是IV,剩余是密文(包含标签)
            byte[] iv = new byte[GCM_IV_LENGTH];
            byte[] cipherText = new byte[encryptedBytes.length - GCM_IV_LENGTH];
            System.arraycopy(encryptedBytes, 0, iv, 0, GCM_IV_LENGTH);
            System.arraycopy(encryptedBytes, GCM_IV_LENGTH, cipherText, 0, cipherText.length);

            // 解码密钥
            byte[] keyBytes = Base64.getDecoder().decode(base64Key);
            SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");

            // 初始化解密Cipher
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);

            // 执行解密
            byte[] plainTextBytes = cipher.doFinal(cipherText);
            return new String(plainTextBytes);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

使用说明

  1. 先调用generateAESKey()生成密钥,保存到安全配置中(比如Spring Boot的application.yml,生产环境建议用密钥管理服务)。
  2. 加密时传入明文和Base64编码的密钥,得到加密后的Base64字符串。
  3. 解密时传入加密后的Base64字符串和相同的密钥,得到明文。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:50:03