javax.crypto.AEADBadTagException:AES/GCM/无填充加密器/解密器的标签不匹配

问题描述

这几天我一直在努力解决这个问题。加密方法工作正常,但在解密测试期间,我收到以下异常。特别是我正在使用: 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,encrypted,iv.length);
    System.arraycopy(ciphertext,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,IV_LENGTH);

    Cipher cipher = Cipher.getInstance("AES/GCM/nopadding");
    GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN,iv);
    cipher.init(Cipher.DECRYPT_MODE,ivSpec);

    byte[] decryptedText = cipher.doFinal(decoded);

    return new String(decryptedText);
}

}

有人可以帮忙吗?我已经阅读了很多关于,但仍然没有发现任何错误

解决方法

T_LEN is the size OF THE AUTHENTICATION TAG in bits。它应该足够大,以至于(成功)伪造的风险不会超过您的数据所有者可接受的范围,但与 IV 没有任何关系。如果您的分析不是更少就足够了,并且您不在资源受限的环境中(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,在这种情况下,这可能会返回“mojibake”(实际上是垃圾)而不是您加密的数据。

哦,而且 AEADBadTagException 不是 IllegalArgumentException 的子类。