我正在尝试使用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.CipherInputStreamjavax.crypto.CipherOutputStream可能执行身份验证误报,因此不适用于GCM模式,例如:来自CipherInputStream的文档(Java 12):


  此类可能会捕获BadPaddingException以及解密过程中完整性检查失败而引发的其他异常。这些异常不会重新抛出,因此可能不会通知客户端完整性检查失败。由于这种行为,此类可能不适用于经过身份验证的操作模式(例如GCM)中的解密。需要身份验证加密的应用程序可以直接使用Cipher API作为使用此类的替代方法。


因此,应该按照文档中的建议直接使用Cipher API,或者使用BouncyCastle实现org.bouncycastle.crypto.io.CipherInputStreamorg.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/

10-10 15:58