我正在尝试使用AES-GCM和JDK 1.8 CipherOutputStream进行加密/解密,但是在解密过程中出现BadPaddingException。在加密和解密期间,我使用了相同的IV和秘密密钥,但不确定出了什么问题。请参见下面的代码:
static String AES_GCM_MODE = "AES/GCM/NoPadding";
SecretKey secretKey;
public SymmetricFileEncryption(){
Security.insertProviderAt( new BouncyCastleProvider(), 1);
setSecretKey();
}
public static void main(String[] args) throws Exception {
File inputFile = new File("test.txt");
File outputFile = new File("test-crypt.txt");
File out = new File("test-decrypt.txt");
SymmetricFileEncryption sym = new SymmetricFileEncryption();
sym.encrypt(inputFile, outputFile);
sym.decrypt(outputFile, out);
}
public Cipher getEncryptionCipher() throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException {
Cipher cipher = Cipher.getInstance(AES_GCM_MODE, "BC");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, getInitializationVector());
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(), new IvParameterSpec(getInitializationVector()) );
return cipher;
}
private Cipher getDecryptionCipher(File inputFile) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, IOException, NoSuchProviderException {
//initialize cipher
Cipher cipher = Cipher.getInstance(AES_GCM_MODE, "BC");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, getInitializationVector());
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(),new IvParameterSpec(getInitializationVector()) );
return cipher;
}
public void encrypt(File inputFile, File outputFile) throws Exception {
Cipher cipher = getEncryptionCipher();
FileOutputStream fos = null;
CipherOutputStream cos = null;
FileInputStream fis = null;
try {
fis = new FileInputStream(inputFile);
fos = new FileOutputStream(outputFile);
cos = new CipherOutputStream(fos, cipher);
byte[] data = new byte[16];
int read = fis.read(data);
while (read != -1) {
cos.write(data, 0, read);
read = fis.read(data);
}
cos.flush();
}catch (Exception e){
e.printStackTrace();
}
finally {
fos.close();
cos.close();
fis.close();
}
String iv = new String(cipher.getIV());
}
public void decrypt(File inputFile, File outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, IOException, NoSuchProviderException {
Cipher cipher = getDecryptionCipher(inputFile);
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
CipherInputStream cipherInputStream = null;
try{
inputStream = new FileInputStream(inputFile);
cipherInputStream = new CipherInputStream(inputStream, cipher);
outputStream = new FileOutputStream(outputFile);
byte[] data = new byte[16];
int read = cipherInputStream.read(data);
while(read != -1){
outputStream.write(data);
read = cipherInputStream.read(data);
}
outputStream.flush();
}catch (Exception e){
e.printStackTrace();
}
finally {
cipherInputStream.close();
inputStream.close();
outputStream.close();
}
}
public void setSecretKey(){
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
secretKey = new SecretKeySpec(key, "AES");
}
public SecretKey getSecretKey(){
return secretKey;
}
public byte[] getInitializationVector(){
String ivstr = "1234567890ab"; //12 bytes
byte[] iv = ivstr.getBytes();//new byte[12];
return iv;
}
上面的代码在行解密时导致以下错误
int读取= cipherInputStream.read(data);
javax.crypto.BadPaddingException: mac check in GCM failed
at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:128)
at javax.crypto.CipherInputStream.read(CipherInputStream.java:246)
at javax.crypto.CipherInputStream.read(CipherInputStream.java:222)
at com.rocketsoftware.abr.encryption.SymmetricFileEncryption.decrypt(SymmetricFileEncryption.java:107)
最佳答案
加密无法正常工作:在encrypt
中,必须在CipherOutputStream#close
之前调用FileOutputStream#close
。这是因为CipherOutputStream#close
调用Cipher#doFinal
生成标记并将其附加到密文。如果尚未调用FileOutputStream
,则只能将此部分写入FileOutputStream#close
-实例。顺便说一句,CipherOutputStream#flush
不需要被调用。
解密还有一个问题:在decrypt
中,必须用outputStream.write(data)
替换outputStream.write(data, 0, read)
。否则通常会将太多数据写入FileOutputStream
实例。
类javax.crypto.CipherInputStream
和javax.crypto.CipherOutputStream
可能执行身份验证误报,因此不适用于GCM模式,例如:来自CipherInputStream
的文档(Java 12):
此类可能会捕获BadPaddingException以及解密过程中完整性检查失败而引发的其他异常。这些异常不会重新抛出,因此可能不会通知客户端完整性检查失败。由于这种行为,此类可能不适用于经过身份验证的操作模式(例如GCM)中的解密。需要身份验证加密的应用程序可以直接使用Cipher API作为使用此类的替代方法。
因此,应该按照文档中的建议直接使用Cipher API,或者使用BouncyCastle实现org.bouncycastle.crypto.io.CipherInputStream
和org.bouncycastle.crypto.io.CipherOutputStream
例如用于加密:
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.io.CipherOutputStream;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.AEADBlockCipher;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
...
public void encrypt(File inputFile, File outputFile) throws Exception {
AEADBlockCipher cipher = getEncryptionCipher();
// Following code as before (but with fixes described above)
...
}
public AEADBlockCipher getEncryptionCipher() throws Exception {
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
cipher.init(true, // encryption
new AEADParameters(
new KeyParameter(getSecretKey().getEncoded()),
128, // tag length
getInitializationVector(),
"Optional Associated Data".getBytes()));
return cipher;
}
...
和模拟解密。
请注意,即使身份验证失败,也会执行解密,因此开发人员必须确保在这种情况下丢弃结果并且不使用结果。
关于java - BadPaddingException:GCM中的mac检查失败,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58059932/