javax.crypto.aeadbadtagexception:aes/gcm/no padding encryptor/decryptor的标记不匹配

55ooxyrt  于 2021-07-09  发布在  Java
关注(0)|答案(1)|浏览(1117)

我已经想了好几天了。加密方法工作正常,但在解密测试期间,我得到了下面的异常。尤其是我在使用: 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);

}

}
有人能帮忙吗?我已经读了很多关于这个的书,但仍然没有发现任何错误。
yc0p9oo0

yc0p9oo01#

T_LEN 是身份验证标记的大小(以位为单位)。它应该足够大,使(成功)伪造的风险不超过您的数据所有者可以接受的范围,但与iv没有任何关系。如果你没有一个足够的分析,并且你不是在一个资源受限的环境中(javase从来都不是),那么就使用最大值128。
您的主要问题是在encrypt中,您合理地连接iv+密文(对于java来说,它包括标记),但是在decrypt中,您使用第一个字节作为iv,在应该使用的时候使用整个缓冲区作为密文 Arrays.copyOfRange(decoded,IV_LENGTH,decoded.length) .
而且,aes密钥必须正好是16、24或32字节,并且应该是随机位,这在java中不能可靠地直接表示 String . 通常你应该使用 byte[] 如果需要将其作为字符串传递或存储,请将其编码到(并从)hex或base64。
最后,在加密时使用 getBytes() 作为utf-8,但在解密时使用 new String 使用默认编码,这种编码因jvm而异,通常取决于环境,通常不是utf-8,在这种情况下,它可能返回“mojibake”(实际上是垃圾)而不是加密的数据。
哦,还有 AEADBadTagException 不是的子类 IllegalArgumentException .

相关问题