BadPaddingException:GCM中的Mac检查失败

时间:2019-09-23 10:00:48

标签: 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(){

        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(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)

1 个答案:

答案 0 :(得分:1)

  • 加密无法正常工作:在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模式,例如来自文档(Java 12)中的CipherInputStream

      

    此类可能会捕获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;
    }
    ...
    

    和用于解密的模拟。

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