BadPaddingException: mac 检查 GCM 失败
BadPaddingException: mac check in GCM failed
我正在尝试 encrypt/decrypt 使用 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(数据);
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
must be called before FileOutputStream#close
. This is because CipherOutputStream#close
calls Cipher#doFinal
生成标签并将其附加到密文。如果尚未调用 FileOutputStream#close
,则此部分只能写入 FileOutputStream
实例。顺便说一下,CipherOutputStream#flush
不需要调用。
解密也有问题:在decrypt
中,outputStream.write(data)
必须换成outputStream.write(data, 0, read)
。否则通常会将太多数据写入 FileOutputStream
-实例。
类javax.crypto.CipherInputStream
and javax.crypto.CipherOutputStream
可能会执行身份验证误报,因此不是 适用于 GCM 模式,例如来自 CipherInputStream
的文档 (Java 12):
This class may catch BadPaddingException and other exceptions thrown by failed integrity checks during decryption. These exceptions are not re-thrown, so the client may not be informed that integrity checks failed. Because of this behavior, this class may not be suitable for use with decryption in an authenticated mode of operation (e.g. GCM). Applications that require authenticated encryption can use the Cipher API directly as an alternative to using this class.
因此,要么按照文档中的建议直接使用 Cipher API,要么使用 BouncyCastle 实现 org.bouncycastle.crypto.io.CipherInputStream
and 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;
}
...
和模拟解密。
请注意,即使认证失败,也会进行解密,因此开发者必须确保在这种情况下丢弃和不使用结果。
我正在尝试 encrypt/decrypt 使用 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(数据);
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
must be called beforeFileOutputStream#close
. This is becauseCipherOutputStream#close
callsCipher#doFinal
生成标签并将其附加到密文。如果尚未调用FileOutputStream#close
,则此部分只能写入FileOutputStream
实例。顺便说一下,CipherOutputStream#flush
不需要调用。解密也有问题:在
decrypt
中,outputStream.write(data)
必须换成outputStream.write(data, 0, read)
。否则通常会将太多数据写入FileOutputStream
-实例。类
javax.crypto.CipherInputStream
andjavax.crypto.CipherOutputStream
可能会执行身份验证误报,因此不是 适用于 GCM 模式,例如来自CipherInputStream
的文档 (Java 12):This class may catch BadPaddingException and other exceptions thrown by failed integrity checks during decryption. These exceptions are not re-thrown, so the client may not be informed that integrity checks failed. Because of this behavior, this class may not be suitable for use with decryption in an authenticated mode of operation (e.g. GCM). Applications that require authenticated encryption can use the Cipher API directly as an alternative to using this class.
因此,要么按照文档中的建议直接使用 Cipher API,要么使用 BouncyCastle 实现
org.bouncycastle.crypto.io.CipherInputStream
andorg.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; } ...
和模拟解密。
请注意,即使认证失败,也会进行解密,因此开发者必须确保在这种情况下丢弃和不使用结果。