Java BadPaddingException:GCM中的mac签入失败

Java BadPaddingException:GCM中的mac签入失败,java,cryptography,bouncycastle,aes-gcm,badpaddingexception,Java,Cryptography,Bouncycastle,Aes Gcm,Badpaddingexception,我正在尝试使用AES-GCM和JDK 1.8 CipherOutputStream进行加密/解密,但在解密过程中遇到BadPaddingException。我在加密和解密期间使用相同的IV和密钥,但不确定出了什么问题。请参阅下面的代码: static String AES_GCM_MODE = "AES/GCM/NoPadding"; SecretKey secretKey; public SymmetricFileEncryption(){ Securi

我正在尝试使用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 read=cipherInputStream.read(数据)

  • 加密无法正常工作:在
    加密
    中,必须在
    文件输出流#关闭
    之前调用。这是因为
    CipherOutputStream#close
    调用生成标记并将其附加到密文。如果尚未调用
    FileOutputStream#close
    ,则此部分只能写入
    FileOutputStream
    实例。顺便说一下,不需要调用
    CipherOutputStream#flush

  • 解密也有一个问题:在
    decrypt
    中,
    outputStream.write(data)
    必须替换为
    outputStream.write(data,0,read)
    。否则,通常会将太多数据写入
    FileOutputStream
    -实例

  • 类和可能执行身份验证误报,因此不适用于GCM模式,例如来自
    CipherInputStream的文档(Java 12):

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

    因此,应按照文档中的建议直接使用Cipher API,或使用BouncyCastle实现,例如用于加密:

    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;
    }
    ...
    
    模拟解密

    请注意,即使身份验证失败,也会执行解密,因此开发人员必须确保丢弃结果,并且在这种情况下不使用结果


警告:您的代码完全不安全!您的代码使用静态“IV”(在本文中,IV是GCM nonce)。根据定义,不应重复使用nonce。通过重用它,AES-GCM的安全性完全崩溃!这只是出于测试目的,目标是获取key和IV作为用户参数good(如果您知道这一点),但是实际上,任何已发布的代码an SO都将在更早或更晚的某个地方使用。因此,发布带有安全问题的代码是相当危险的。顺便问一下:你为什么使用Bouncycastle?JCE有内置的GCM支持。非常感谢您帮助我们深入理解这个问题。
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;
}
...