我对使用加密开发内容相对较新。现在我正在尝试使用带有AES-GCM的BouncyCastle编写一个加密和解密字符串的类。我读到了实施加密时必须考虑的事项。其中之一是你应该总是使用随机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)
我正在使用以下方法来加密和解密我的数据。
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);
}
如果我从cipher.init(...)中删除“generateIV(cipher)”,一切都会完美无缺。但据我所知,它极大地削弱了加密。
正确知道我无法弄清楚这是代码中的一个小错误还是我一无所知的其他内容。
非常感谢您的帮助和非常感谢!
答案 0 :(得分:9)
您必须使用相同的IV进行加密和解密。它不一定是秘密,但只有唯一用于AES-GCM(技术上是 nonce )。一种常见的方法是将IV添加到密文并在解密之前将其删除。
使用消息计数器而不是随机生成IV也很常见。
AES-GCM指定用于96位的IV,这意味着您可以安全地在单个消息中加密64GB。如果使用完整块大小的IV,则IV +计数器可能会重叠多个消息,这将导致这些重叠块的多次填充。
答案 1 :(得分:1)
这是我在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;
}
}