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个致命错误,直接导致解密时标签不匹配:
- 使用空密钥:
strPassword是空字符串,生成的密钥是空字节数组,完全不符合AES密钥要求(AES密钥长度必须是16/24/32字节,对应128/192/256位)。 - IV重复且无效:GCM模式要求每次加密必须使用唯一的初始化向量(IV),不能重复使用相同IV和密钥的组合。当前代码用空字符串的字节数组作为固定IV,既不符合安全规范,也会导致解密验证失败。
- 密钥与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; } } }
使用说明
- 先调用
generateAESKey()生成密钥,保存到安全配置中(比如Spring Boot的application.yml,生产环境建议用密钥管理服务)。 - 加密时传入明文和Base64编码的密钥,得到加密后的Base64字符串。
- 解密时传入加密后的Base64字符串和相同的密钥,得到明文。
内容的提问来源于stack exchange,提问作者Anudeep T
相关产品推荐
相关产品推荐

