Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 7 -> Java 8: AES Causes exception: "BadPaddingException: Given final block not properly padded" in conjunction with BufferedReader & ZipStreams

We instantiate the cipher with the following statement:

Cipher cipher = Cipher.getInstance("AES");
SecretKeySpec key = new SecretKeySpec(cipherKey, "AES");

This works in java 7 (1.7_45) but no longer works in Java 8 (1.8_25). We pass the cipher to a CipherInputStream and use the streams to read/write data. The actual exception occurs during close.

EDIT:

A quick look at the JDK code shows that the BadPaddingException is rethrown and in 7 it was ignored:

JDK 7: CipherInputStream.close:

 try {
  this.cipher.doFinal();
} catch (BadPaddingException var2) {
  ;
} catch (IllegalBlockSizeException var3) {
  ;
}

JDK 8: CipherInputStream.close:

try {
    if(!this.done) {
      this.cipher.doFinal();
    }
  } catch (IllegalBlockSizeException | BadPaddingException var2) {
    if(this.read) {
      throw new IOException(var2);
    }
  }

The question is how to avoid a BadPaddingException in the first place?

EDIT 2:

After doing some research and experimentation we came to the following test program:

public final class CipherSpike2 {

  private static final byte[] SECRET_KEY = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};

  public static void main(String[] args)
  throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IOException {
    encryptDecrypt(511);
    encryptDecrypt(512);
  }

  private static void encryptDecrypt(int i)
  throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException {

    byte[] clearText = generateClearText(i);
    System.out.println("Clear text length: " + clearText.length);

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    CipherOutputStream cos = new CipherOutputStream(bos, getCipher(Cipher.ENCRYPT_MODE));
    cos.write(clearText);
    cos.close();

    final byte[] content = bos.toByteArray();
    System.out.println("written bytes: " + content.length);

    CipherInputStream
    inputStream =
    new CipherInputStream(new ByteArrayInputStream(content), getCipher(Cipher.DECRYPT_MODE));

    inputStream.read();
    inputStream.close();
 }

 private static byte[] generateClearText(int size) {
    return new byte[size];
  }

  private static Cipher getCipher(int encryptMode)
  throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKeySpec key = new SecretKeySpec(SECRET_KEY, "AES");
    cipher.init(encryptMode, key);
    return cipher;
  }
}

First, the program writes 511 bytes which results in a 512 byte long "file content". We read exactly one byte. The CipherInputStream reads the data in a 512 byte chunk, so every byte is read and we can close the stream.

Next, we create a content of 512 bytes. This gets padded to 528. If we now read only one byte, we have some bytes left and if we close the stream now it crashes with the given exception.

Now this problem is especially problematic in conjunction with ZipStreams: The encrypted content is in a previous step zipped with a ZipOutputStream and read with a ZipInputStream. It seems, that the ZipInputStream does not consume the same amount of bytes as it has previously written.

It seems that the only solution is to catch the BadPaddingException in the close().(?) From an API view point it seems weird to me, that I cannot close a stream without exception regardless of the number of bytes I've read.

EDIT 3 ZipStream elaboration:

In our application we read a bunch of encrypted text files. So the consturction for the streams looks like this:

BufferedReader/Writer -> InputStreamReader/Writer -> ZipInputStream/Output -> CipherInputStream/Output -> Underlying File Stream

We read the content of the file with a "traditional" while (reader.readLine != null) loop until we reach EOF. After that we try to close the file. But sometimes this results in an exception in Java 8 - see above (-:. It seems, that the ZipOutputStream writes more bytes then the ZipInputStreams consumes, but I don't looked at the code yet.

like image 359
morpheus05 Avatar asked Nov 25 '14 11:11

morpheus05


2 Answers

Java 1.8 CipherInputStream throws a BadPaddingException if you don't completely consume the stream. This may be the case when using ZipInputStream, since consuming a zip in streaming fashion doesn't need to read the zip index at the end of the file.

I recommend wrapping the CipherInputStream in a facade implementation of InputStream that ignores BadPaddingException when delegating the close() method. Don't do this if authentication of the contents of the stream is required for your use case, of course, or if some kind of timing oracle attack is possible.

like image 134
Barry Kelly Avatar answered Oct 18 '22 01:10

Barry Kelly


Looks like you have hit https://bugs.openjdk.java.net/browse/JDK-8061619. There was a good reason for the change in behaviour (see http://blog.philippheckel.com/2014/03/01/cipherinputstream-for-aead-modes-is-broken-in-jdk7-gcm/)

You should explicitly specify the padding in your cipher instance (e.g. AES/GCM/NoPadding, but check the suitability for your application. Incorrect padding is behind a number of attacks on SSL)

like image 6
artbristol Avatar answered Oct 18 '22 00:10

artbristol