javax.crypto.AEADBadTagException:AES/GCM/No 填充 encryptor/decryptor 的标签不匹配
javax.crypto.AEADBadTagException: Tag mismatch for AES/GCM/No Padding encryptor/decryptor
几天来我一直在努力解决这个问题。加密方法工作正常,但在解密测试期间,我收到以下异常。特别是我正在使用: AES/GCM/NoPadding
。据我所知 T_LEN
应该是 IV_LENGTH*8
作为字节数组表示。错误真正显示在 ExampleCryptografer.java 解密方法:byte[] decryptedText = cipher.doFinal(decoded);
javax.crypto.AEADBadTagException: Tag mismatch!
at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623)
at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116)
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
at com.example.ExampleCryptografer.decrypt(ExampleCryptografer.java:61)
at com.example.ExampleCryptograferTest.decrypt_givenEncryptedExample_ShouldSucceed(ExampleCryptograferTest.java:21)
我的测试是这样的:
public class ExampleCryptographerTest {
private ExampleCryptographer objectUnderTest = new ExampleCryptographer("knownKeyForTest=");
@Test
public void decrypt_givenEncryptedExample_ShouldSucceed() {
String example = "afasfdafafa=";
String encodedExample = objectUnderTest.encrypt(example);
String result = objectUnderTest.decrypt(encodedExample);
assertThat(result).isNotNull();
assertThat(result.length()).isEqualTo(48);
}
@Test
public void encrypt_givenExample_ShouldSucceed() {
String example = "afasfdafafa=";
String result = objectUnderTest.encrypt(example);
assertThat(result).isNotNull();
assertThat(result.length()).isEqualTo(48);
}
@Test
public void decrypt_givenEncryptedExampleWithOtherKey_ShouldFail() {
String example = "afasfdafafa=";
String encodedExample = new ExampleCryptographer("otherKeyForTest=").encrypt(example);
Throwable throwable = catchThrowable(() -> objectUnderTest.decrypt(encodedExample));
assertThat(throwable)
.isInstanceOf(IllegalArgumentException.class);
}
@Test(expected = InvalidKeyException.class)
public void encrypt_givenInvalidKey_ShouldFail() {
new ExampleCryptographer("invalid").encrypt("test");
}
}
最后是实际代码:
public class ExampleCryptographer {
private static final String ALGORITHM = "AES";
private final Key key;
private static final int T_LEN = 96;
private static final int IV_LENGTH = 12;
private final Base64 base64 = new Base64(76, null, true);
@SneakyThrows
public ExampleCryptographer(@Value("${myKey}") String secretKey) {
this.key = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
}
@SneakyThrows
public String encrypt(@NonNull String text) {
byte[] iv = new byte[IV_LENGTH];
(new SecureRandom()).nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = cipher.doFinal(text.getBytes(UTF_8));
byte[] encrypted = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, encrypted, 0, iv.length);
System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length);
return base64.encodeAsString(encrypted);
}
@SneakyThrows
public String decrypt(@NonNull String encryptedText) {
byte[] decoded = base64.decode(encryptedText);
byte[] iv = Arrays.copyOfRange(decoded, 0, IV_LENGTH);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decryptedText = cipher.doFinal(decoded);
return new String(decryptedText);
}
}
有人可以帮忙吗?我已经阅读了很多,但仍然找不到任何错误。
T_LEN
is the size OF THE AUTHENTICATION TAG in bits。它应该足够大,以至于(成功)伪造的风险不超过您的数据所有者可以接受的风险,但与 IV 没有任何关系。如果您没有分析认为 less 就足够了,并且您不在资源受限的环境中(JavaSE 永远不会),只需使用最大值 128.
你的主要问题是在加密时合理地连接 IV+密文(Java 包括标签),但在解密时你使用第一个字节作为 IV 和 整个缓冲区 应该是 Arrays.copyOfRange(decoded,IV_LENGTH,decoded.length)
.
作为密文
此外,AES 密钥必须恰好为 16、24 或 32 字节,并且应该是随机位,不能直接在 Java String
中可靠地表示。通常,您应该使用 byte[]
,如果您需要将其作为字符串编码传递或存储为十六进制或 base64(并从中解码)。
最后,在加密时使用 getBytes()
编码为 UTF-8,但在解密时使用默认编码使用 new String
解码,默认编码因 JVM 的不同而不同,通常取决于环境并且通常不是 UTF-8,在这种情况下,这可能 return 'mojibake'(实际上是垃圾)不是您加密的数据。
哦,而且 AEADBadTagException
不是 IllegalArgumentException
的子类。
几天来我一直在努力解决这个问题。加密方法工作正常,但在解密测试期间,我收到以下异常。特别是我正在使用: AES/GCM/NoPadding
。据我所知 T_LEN
应该是 IV_LENGTH*8
作为字节数组表示。错误真正显示在 ExampleCryptografer.java 解密方法:byte[] decryptedText = cipher.doFinal(decoded);
javax.crypto.AEADBadTagException: Tag mismatch!
at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623)
at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116)
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
at com.example.ExampleCryptografer.decrypt(ExampleCryptografer.java:61)
at com.example.ExampleCryptograferTest.decrypt_givenEncryptedExample_ShouldSucceed(ExampleCryptograferTest.java:21)
我的测试是这样的:
public class ExampleCryptographerTest {
private ExampleCryptographer objectUnderTest = new ExampleCryptographer("knownKeyForTest=");
@Test
public void decrypt_givenEncryptedExample_ShouldSucceed() {
String example = "afasfdafafa=";
String encodedExample = objectUnderTest.encrypt(example);
String result = objectUnderTest.decrypt(encodedExample);
assertThat(result).isNotNull();
assertThat(result.length()).isEqualTo(48);
}
@Test
public void encrypt_givenExample_ShouldSucceed() {
String example = "afasfdafafa=";
String result = objectUnderTest.encrypt(example);
assertThat(result).isNotNull();
assertThat(result.length()).isEqualTo(48);
}
@Test
public void decrypt_givenEncryptedExampleWithOtherKey_ShouldFail() {
String example = "afasfdafafa=";
String encodedExample = new ExampleCryptographer("otherKeyForTest=").encrypt(example);
Throwable throwable = catchThrowable(() -> objectUnderTest.decrypt(encodedExample));
assertThat(throwable)
.isInstanceOf(IllegalArgumentException.class);
}
@Test(expected = InvalidKeyException.class)
public void encrypt_givenInvalidKey_ShouldFail() {
new ExampleCryptographer("invalid").encrypt("test");
}
}
最后是实际代码:
public class ExampleCryptographer {
private static final String ALGORITHM = "AES";
private final Key key;
private static final int T_LEN = 96;
private static final int IV_LENGTH = 12;
private final Base64 base64 = new Base64(76, null, true);
@SneakyThrows
public ExampleCryptographer(@Value("${myKey}") String secretKey) {
this.key = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
}
@SneakyThrows
public String encrypt(@NonNull String text) {
byte[] iv = new byte[IV_LENGTH];
(new SecureRandom()).nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = cipher.doFinal(text.getBytes(UTF_8));
byte[] encrypted = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, encrypted, 0, iv.length);
System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length);
return base64.encodeAsString(encrypted);
}
@SneakyThrows
public String decrypt(@NonNull String encryptedText) {
byte[] decoded = base64.decode(encryptedText);
byte[] iv = Arrays.copyOfRange(decoded, 0, IV_LENGTH);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decryptedText = cipher.doFinal(decoded);
return new String(decryptedText);
}
}
有人可以帮忙吗?我已经阅读了很多,但仍然找不到任何错误。
T_LEN
is the size OF THE AUTHENTICATION TAG in bits。它应该足够大,以至于(成功)伪造的风险不超过您的数据所有者可以接受的风险,但与 IV 没有任何关系。如果您没有分析认为 less 就足够了,并且您不在资源受限的环境中(JavaSE 永远不会),只需使用最大值 128.
你的主要问题是在加密时合理地连接 IV+密文(Java 包括标签),但在解密时你使用第一个字节作为 IV 和 整个缓冲区 应该是 Arrays.copyOfRange(decoded,IV_LENGTH,decoded.length)
.
此外,AES 密钥必须恰好为 16、24 或 32 字节,并且应该是随机位,不能直接在 Java String
中可靠地表示。通常,您应该使用 byte[]
,如果您需要将其作为字符串编码传递或存储为十六进制或 base64(并从中解码)。
最后,在加密时使用 getBytes()
编码为 UTF-8,但在解密时使用默认编码使用 new String
解码,默认编码因 JVM 的不同而不同,通常取决于环境并且通常不是 UTF-8,在这种情况下,这可能 return 'mojibake'(实际上是垃圾)不是您加密的数据。
哦,而且 AEADBadTagException
不是 IllegalArgumentException
的子类。