有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java BadPaddingException:GCM中的mac签入失败

我正在尝试使用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。读取(数据)

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) 个答案

  1. # 1 楼答案

    • 加密无法正常工作:在encrypt中,必须在FileOutputStream#close之前调用^{}。这是因为CipherOutputStream#close调用^{}生成标记并将其附加到密文。只有在尚未调用FileOutputStream#close时,才能将此部分写入FileOutputStream-实例。顺便说一下,CipherOutputStream#flush不需要被调用

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

    • ^{}^{}可以执行认证假阳性,因此适合于GCM模式,例如来自^{的文档(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.

      因此,按照文档中的建议,要么直接使用密码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;
      }
      ...
      

      模拟解密

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