JDK-8072073 : Partially decrypting stream using Blowfish throws BadPaddingException on close
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.crypto
  • Affected Version: 7u71,8u31
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_8
  • CPU: x86
  • Submitted: 2015-01-23
  • Updated: 2015-02-02
  • Resolved: 2015-02-02
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.7.0_71"
Java(TM) SE Runtime Environment (build 1.7.0_71-b14)
Java HotSpot(TM) 64-Bit Server VM (build 24.71-b01, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.3.9600]

A DESCRIPTION OF THE PROBLEM :
Closing a cipher input stream early before reading all content will throw a BadPaddingException when closing the stream. The encryption used is Blowfish.

REGRESSION.  Last worked in version 7u67

ADDITIONAL REGRESSION INFORMATION: 
java version "1.7.0_67"
Java(TM) SE Runtime Environment (build 1.7.0_67-b01)
Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode)

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
See attached sample.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The stream should not throw an exception on close unless all data has been read.
ACTUAL -
The stream throws a BadPaddingException when calling close on the cipher input stream.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.io.IOException: javax.crypto.BadPaddingException: Given final block not properly padded
	at javax.crypto.CipherInputStream.close(CipherInputStream.java:321)
	at test.TestBlowfish.main(TestBlowfish.java:45)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded
	at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
	at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
	at com.sun.crypto.provider.BlowfishCipher.engineDoFinal(BlowfishCipher.java:319)
	at javax.crypto.Cipher.doFinal(Cipher.java:1970)
	at javax.crypto.CipherInputStream.close(CipherInputStream.java:314)
	... 6 more

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package test;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.util.Arrays;

public class TestBlowfish {
    private static final boolean ENABLE_BUG = true;

    public static void main(String[] args) throws Exception {
        final byte[] original = new byte[64 * 1024];
        for (int i = 0; i < original.length; i++) {
            original[i] = (byte) i;
        }
        final byte ivBytes[] = new byte[]{8, 7, 6, 5, 4, 3, 2, 1};
        final SecretKeySpec key = new SecretKeySpec("password".getBytes(), "Blowfish");
        final IvParameterSpec iv = new IvParameterSpec(ivBytes);
        final Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        try (OutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher)) {
            cipherOutputStream.write(original);
            cipherOutputStream.write(original);
        }

        final byte[] decrypt = new byte[64 * 1024];
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        try (InputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(outputStream.toByteArray()), cipher)) {
            readAll(cipherInputStream, decrypt);
            if (!Arrays.equals(original, decrypt)) {
                throw new IllegalStateException("decrypt error");
            }

            if (!ENABLE_BUG) {
                readAll(cipherInputStream, decrypt);
                if (!Arrays.equals(original, decrypt)) {
                    throw new IllegalStateException("decrypt error");
                }
            }
        }
    }

    private static void readAll(InputStream inputStream, byte[] buffer) throws IOException {
        int read = 0;
        while (read != buffer.length) {
            read += inputStream.read(buffer, read, buffer.length - read);
        }
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The code must be changed to handle this case explicitly and this is impossible for any deployed application without updating it.

The BadPaddingException must be explicitly caught and ignored when calling close on a cipher input stream.

SUPPORT :
YES


Comments
Closing this as a duplicate of JDK-8064546.
02-02-2015

This issue is reproducible with JDK 7u72, 7u76, 8u25 and 8u31. The test case run fine with JDK 7u67, 8u40ea and 9ea.
02-02-2015