java 当与 IV 一起使用时,带有 BouncyCastle 的 AES-GCM 会抛出“GCM 中的 mac 检查失败”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35558249/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 00:17:14  来源:igfitidea点击:

AES-GCM with BouncyCastle throws "mac check in GCM failed" when used with IV

javaencryptionbouncycastleinitialization-vectoraes-gcm

提问by thaasoph

I'm relatively new to developing something with encryption. Right now I'm trying to write a class which encrypts and decrypts Strings using BouncyCastle with AES-GCM. I read about the things you have to consider when implementing encryption. One of them was that you should always use a randomized IV. The problem is, everytime I try to initialize my Cipher with an IV it won't decrypt my text properly.
It just throws the following exception:

我对开发加密的东西比较陌生。现在我正在尝试编写一个类,该类使用 BouncyCastle 和 AES-GCM 来加密和解密字符串。我阅读了有关实施加密时必须考虑的事项。其中之一是您应该始终使用随机 IV。问题是,每次我尝试用 IV 初始化我的密码时,它都无法正确解密我的文本。
它只是抛出以下异常:

javax.crypto.AEADBadTagException: mac check in GCM failed
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$AEADGenericBlockCipher.doFinal(Unknown Source)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at BouncyCastleEX.decrypt(BouncyCastleEX.java:78)
at BouncyCastleEX.main(BouncyCastleEX.java:43)

I'm using the following methods to encrypt and decrypt my data.

我正在使用以下方法来加密和解密我的数据。

private static final String fallbackSalt = "ajefa6tc73t6raiw7tr63wi3r7citrawcirtcdg78o2vawri7t";
private static final int iterations = 2000;
private static final int keyLength = 256;
private static final SecureRandom random = new SecureRandom();

public byte[] encrypt(String plaintext, String passphrase, String salt)
        throws Exception {
    SecretKey key = generateKey(passphrase, salt);
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
    cipher.init(Cipher.ENCRYPT_MODE, key, generateIV(cipher),random);
    return cipher.doFinal(plaintext.getBytes());
}

public String decrypt(byte[] encrypted, String passphrase, String salt)
        throws Exception {
    SecretKey key = generateKey(passphrase, salt);
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
    cipher.init(Cipher.DECRYPT_MODE, key, generateIV(cipher),random);
    return new String(cipher.doFinal(encrypted));
}

private SecretKey generateKey(String passphrase, String salt)
        throws Exception {
    PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(),
            salt.getBytes(), iterations, keyLength);
    SecretKeyFactory keyFactory = SecretKeyFactory
            .getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
    return keyFactory.generateSecret(keySpec);
}

private IvParameterSpec generateIV(Cipher cipher) throws Exception {
    byte[] ivBytes = new byte[cipher.getBlockSize()];
    random.nextBytes(ivBytes);
    return new IvParameterSpec(ivBytes);
}

If I remove the "generateIV(cipher)" from my cipher.init(...) everything works flawlessly. But as far as I know it weakens the encryption tremendously.

如果我从我的 cipher.init(...) 中删除“generateIV(cipher)”,一切都会完美无缺。但据我所知,它极大地削弱了加密。

Right know I'm unable to figure out whether this is a small mistake in the code or something else I know nothing about.

知道我无法弄清楚这是代码中的一个小错误还是其他我一无所知的错误。

I really appreciate your help and thanks a lot!

我真的很感谢你的帮助,非常感谢!

回答by Artjom B.

You have to use the same IV for encryption and decryption. It doesn't have to be secret, but only uniquefor AES-GCM (it's technically a nonce). A common way is to prepend the IV to the ciphertext and remove it before decryption.

您必须使用相同的 IV 进行加密和解密。它不必是秘密的,但仅对AES-GCM 来说是唯一的(从技术上讲,它是随机数)。一种常见的方法是在密文前添加 IV 并在解密之前将其删除。

It's also common to use a message counter instead of randomly generating an IV. If the key is changed then you should reset the IV to an initial value and start counting again. At some number of messages, you need a new key, because the security guarantees of AES-GCM break down. That number is somewhere between 248and 264messages.

使用消息计数器而不是随机生成 IV 也很常见。如果更改了密钥,则应将 IV 重置为初始值并重新开始计数。对于一定数量的消息,您需要一个新密钥,因为 AES-GCM 的安全保证失效。该数字介于 2 48和 2 64消息之间。

回答by thaasoph

Here is the final version of my code which I wrote with the help of Artjom. It seems to work great but if you find any mistakes or things that weaken the security, please let me know.

这是我在 Artjom 的帮助下编写的代码的最终版本。它似乎工作得很好,但如果您发现任何错误或削弱安全性的事情,请告诉我。

import java.security.SecureRandom;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;
import com.sun.org.apache.xml.internal.security.utils.Base64;

public class BouncyCastleEX {

private static final int iterations = 2000;
private static final int keyLength = 256;
private static final SecureRandom random = new SecureRandom();
private static BouncyCastleEX instance = null;

public String encryptString(String plaintext, String passphrase, String salt)
        throws Exception {
    return Base64.encode(encrypt(plaintext, passphrase, salt));
}

public String decryptString(String encrypted, String passphrase, String salt)
        throws Exception {
    return decrypt(Base64.decode(encrypted), passphrase, salt);
}

private BouncyCastleEX() {
    Security.addProvider(new BouncyCastleProvider());
}

public static BouncyCastleEX getInstance() {
    if (instance == null) {
        instance = new BouncyCastleEX();
    }
    return instance;
}

private byte[] encrypt(String plaintext, String passphrase, String salt)
        throws Exception {
    SecretKey key = generateKey(passphrase, salt);
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
    byte[] ivBytes = generateIVBytes(cipher);
    cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivBytes),
            random);
    return Arrays
            .concatenate(ivBytes, cipher.doFinal(plaintext.getBytes()));
}

private String decrypt(byte[] encrypted, String passphrase, String salt)
        throws Exception {
    SecretKey key = generateKey(passphrase, salt);
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
    cipher.init(Cipher.DECRYPT_MODE, key,
            new IvParameterSpec(Arrays.copyOfRange(encrypted, 0, 12)),
            random);
    return new String(cipher.doFinal(Arrays.copyOfRange(encrypted, 12,
            encrypted.length)));
}

private SecretKey generateKey(String passphrase, String salt)
        throws Exception {
    PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(),
            salt.getBytes(), iterations, keyLength);
    SecretKeyFactory keyFactory = SecretKeyFactory
            .getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
    return keyFactory.generateSecret(keySpec);
}

private byte[] generateIVBytes(Cipher cipher) throws Exception {
    byte[] ivBytes = new byte[12];
    random.nextBytes(ivBytes);

    return ivBytes;
}

}