java - BadPaddingException:GCM 中的 mac 检查失败

标签 java cryptography bouncycastle aes-gcm badpaddingexception

我正在尝试使用 AES-GCM 和 JDK 1.8 CipherOutputStream 进行加密/解密,但在解密过程中出现 BadPaddingException。我在加密和解密期间使用相同的 IV 和 key ,但不确定出了什么问题。请看下面的代码:

 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)

最佳答案

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

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

  • 类(class) javax.crypto.CipherInputStreamjavax.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.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/

相关文章:

java - Android SpongyCaSTLe ECDH secp384r1 key 大小不正确

java - 我在哪里可以看到程序中的日志

java - 修改 java 类以在编译时包含特定注释

java - 如何在MySQL数据库中保存的字符串中进行搜索

c# - XMLSigner 不再适用于 4.6.2 - 格式错误的引用元素

java - 如何使用 BouncyCaSTLe 从文件中检索公钥/私钥?

java - 除了 "dev"之外,Spring redirectAttributes 无法在其他配置文件中工作

java - Java 的 SecureRandom 如何知道系统上可用的 NativePRNG 的哪个实现?

Python 密码学导出 key 到 DER

Java 1.8 : TLSv1. 2 ClientHello 握手失败(缺少椭圆曲线扩展?)