-
Bug
-
Resolution: Duplicate
-
P3
-
None
-
7u17, 8
-
os_x
FULL PRODUCT VERSION :
java version " 1.7.0_17 "
Java(TM) SE Runtime Environment (build 1.7.0_17-b02)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
java version " 1.8.0-ea "
Java(TM) SE Runtime Environment (build 1.8.0-ea-b91)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b33, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Darwin duckbookii.orion.internal 12.3.0 Darwin Kernel Version 12.3.0: Sun Jan 6 22:37:10 PST 2013; root:xnu-2050.22.13~1/RELEASE_X86_64 x86_64
A DESCRIPTION OF THE PROBLEM :
When a javax.crypto.CipherInputStream is used with an AEAD cipher in DECRYPT_MODE (i.e. the contents of the underlying stream are a cipher text with an appended tag), the CipherInputStream.close() methods causes Exceptions to be thrown on many AEAD cipher implementations.
The root cause here is that CipherInputStream preemptively finalises the Cipher (via Cipher.doFinal() in getMoreData() and sets the done flag, and then in close() it blindly finalises the Cipher again by calling Cipher.doFinal() without checking the done flag.
In AEAD ciphers, invoking Cipher.doFinal() without supplying any input is illegal, since they expect at least a tag to be in the ciphertext (i.e. a zero length ciphertext is illegal). The result of this second invocation of Cipher.doFinal() causes an exception to be thrown in implementations of these ciphers.
In some AEAD cipher implementations a BadPaddingException is thrown, which is silently caught in the CipherInputStream.close() method.
Other implementations throw different RuntimeExceptions (typically unintended ArrayIndexOutOfBounds/NegativeArraySizeExceptions), which are not masked and cause use of the CipherInputStream to fail.
AEAD cipher implementations that are known to fail are:
- AES/GCM in JDK8 build 1.8.0-ea-b91
- */CCM and */EAX in BouncyCastle 1.47
AEAD cipher implementations that throw BadPaddingException and thus 'work' are:
- */GCM and */OCB in BouncyCastle 1.47
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a CipherInputStream over a valid AES/GCM ciphertext with a Cipher in DECRYPT_MODE
2. Read bytes from the input stream until they are exhausted
3. Close the input stream
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Execution should complete normally.
ACTUAL -
Invocation of the CipherInputStream.close() method causes an exception to be thrown from Cipher.doFinal(). For some cipher implementations, this is one of the handled types, but for others it is an unhandled RuntimeException.
e.g.
The AES/GCM implementation in OpenJDK 8 fails with a RuntimeException.
The BouncyCastle AES/EAX implementation fails with an ArrayIndexOutOfBoundsException.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
The AES/GCM implementation in OpenJDK 8 fails with an exception as below:
Exception in thread " main " java.lang.RuntimeException: Input buffer too short - need tag
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:465)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1022)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:959)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:827)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:1980)
at javax.crypto.CipherInputStream.close(CipherInputStream.java:299)
at org.bouncycastle.jce.provider.test.CipherInputStreamAEAD.main(CipherInputStreamAEAD.java:36)
--------
The BouncyCastle AES/EAX implementation fails with an exception as below:
Exception in thread " main " java.lang.ArrayIndexOutOfBoundsException: -8
at org.bouncycastle.crypto.modes.EAXBlockCipher.verifyMac(EAXBlockCipher.java:363)
at org.bouncycastle.crypto.modes.EAXBlockCipher.doFinal(EAXBlockCipher.java:278)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$AEADGenericBlockCipher.doFinal(BaseBlockCipher.java:925)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:714)
at javax.crypto.Cipher.doFinal(Cipher.java:1980)
at javax.crypto.CipherInputStream.close(CipherInputStream.java:299)
at org.bouncycastle.jce.provider.test.CipherInputStreamAEAD.main(CipherInputStreamAEAD.java:37)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
The following code run in an OpenJDK 8 early access build demonstrates the failure with the AES/GCM implementation in OpenJDK 8.
public class CipherInputStreamAEAD
{
public static void main(String[] args) throws Exception
{
Cipher c = Cipher.getInstance( " AES/GCM/NoPadding " , " SunJCE " );
Key key = new SecretKeySpec(new byte[16], " AES " );
byte[] iv = new byte[16];
c.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] pt = new byte[1000];
byte[] ct = c.doFinal(pt);
c.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
CipherInputStream cin = new CipherInputStream(new ByteArrayInputStream(ct), c);
// Read until no more data, forces Cipher.doFinal() in getMoreData()
while (cin.read() != -1)
{
}
// Close, blindly performs additional doFinal() and fails because no tag in buffered ciphertext
cin.close();
}
}
The following code will fail in Java SE 7 when run with the BouncyCastle 1.47 JCE provider.
public class CipherInputStreamAEAD
{
public static void main(String[] args) throws Exception
{
Security.addProvider(new BouncyCastleProvider());
Cipher c = Cipher.getInstance( " AES/EAX/NoPadding " , " BC " );
Key key = new SecretKeySpec(new byte[16], " AES " );
byte[] iv = new byte[16];
c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] pt = new byte[1000];
byte[] ct = c.doFinal(pt);
c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
CipherInputStream cin = new CipherInputStream(new ByteArrayInputStream(ct), c);
// Read until no more data, forces Cipher.doFinal() in getMoreData()
while (cin.read() != -1)
{
}
// Close, blindly performs additional doFinal() and fails because no tag in buffered ciphertext
cin.close();
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
One workaround is to avoid calling CipherInputStream.close(), resetting the cipher manually.
This does not match well with expected usage of the CipherInputStream, and prevents use of try(Closeable) {} blocks.
BouncyCastle implementations can be modified to throw BadPaddingException (and may have to for compatibility with previous Java SE releases).
OpenJDK 8 AES/GCM implementation could be modified to throw BadPaddingException.
The BadPaddingException approach would be in my opinion simply masking the error in CipherInputStream - in the case of AEAD ciphers it is not legal to call Cipher.doFinal() on a reset Cipher instance.
java version " 1.7.0_17 "
Java(TM) SE Runtime Environment (build 1.7.0_17-b02)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
java version " 1.8.0-ea "
Java(TM) SE Runtime Environment (build 1.8.0-ea-b91)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b33, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Darwin duckbookii.orion.internal 12.3.0 Darwin Kernel Version 12.3.0: Sun Jan 6 22:37:10 PST 2013; root:xnu-2050.22.13~1/RELEASE_X86_64 x86_64
A DESCRIPTION OF THE PROBLEM :
When a javax.crypto.CipherInputStream is used with an AEAD cipher in DECRYPT_MODE (i.e. the contents of the underlying stream are a cipher text with an appended tag), the CipherInputStream.close() methods causes Exceptions to be thrown on many AEAD cipher implementations.
The root cause here is that CipherInputStream preemptively finalises the Cipher (via Cipher.doFinal() in getMoreData() and sets the done flag, and then in close() it blindly finalises the Cipher again by calling Cipher.doFinal() without checking the done flag.
In AEAD ciphers, invoking Cipher.doFinal() without supplying any input is illegal, since they expect at least a tag to be in the ciphertext (i.e. a zero length ciphertext is illegal). The result of this second invocation of Cipher.doFinal() causes an exception to be thrown in implementations of these ciphers.
In some AEAD cipher implementations a BadPaddingException is thrown, which is silently caught in the CipherInputStream.close() method.
Other implementations throw different RuntimeExceptions (typically unintended ArrayIndexOutOfBounds/NegativeArraySizeExceptions), which are not masked and cause use of the CipherInputStream to fail.
AEAD cipher implementations that are known to fail are:
- AES/GCM in JDK8 build 1.8.0-ea-b91
- */CCM and */EAX in BouncyCastle 1.47
AEAD cipher implementations that throw BadPaddingException and thus 'work' are:
- */GCM and */OCB in BouncyCastle 1.47
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a CipherInputStream over a valid AES/GCM ciphertext with a Cipher in DECRYPT_MODE
2. Read bytes from the input stream until they are exhausted
3. Close the input stream
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Execution should complete normally.
ACTUAL -
Invocation of the CipherInputStream.close() method causes an exception to be thrown from Cipher.doFinal(). For some cipher implementations, this is one of the handled types, but for others it is an unhandled RuntimeException.
e.g.
The AES/GCM implementation in OpenJDK 8 fails with a RuntimeException.
The BouncyCastle AES/EAX implementation fails with an ArrayIndexOutOfBoundsException.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
The AES/GCM implementation in OpenJDK 8 fails with an exception as below:
Exception in thread " main " java.lang.RuntimeException: Input buffer too short - need tag
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:465)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1022)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:959)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:827)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:1980)
at javax.crypto.CipherInputStream.close(CipherInputStream.java:299)
at org.bouncycastle.jce.provider.test.CipherInputStreamAEAD.main(CipherInputStreamAEAD.java:36)
--------
The BouncyCastle AES/EAX implementation fails with an exception as below:
Exception in thread " main " java.lang.ArrayIndexOutOfBoundsException: -8
at org.bouncycastle.crypto.modes.EAXBlockCipher.verifyMac(EAXBlockCipher.java:363)
at org.bouncycastle.crypto.modes.EAXBlockCipher.doFinal(EAXBlockCipher.java:278)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$AEADGenericBlockCipher.doFinal(BaseBlockCipher.java:925)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:714)
at javax.crypto.Cipher.doFinal(Cipher.java:1980)
at javax.crypto.CipherInputStream.close(CipherInputStream.java:299)
at org.bouncycastle.jce.provider.test.CipherInputStreamAEAD.main(CipherInputStreamAEAD.java:37)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
The following code run in an OpenJDK 8 early access build demonstrates the failure with the AES/GCM implementation in OpenJDK 8.
public class CipherInputStreamAEAD
{
public static void main(String[] args) throws Exception
{
Cipher c = Cipher.getInstance( " AES/GCM/NoPadding " , " SunJCE " );
Key key = new SecretKeySpec(new byte[16], " AES " );
byte[] iv = new byte[16];
c.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] pt = new byte[1000];
byte[] ct = c.doFinal(pt);
c.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
CipherInputStream cin = new CipherInputStream(new ByteArrayInputStream(ct), c);
// Read until no more data, forces Cipher.doFinal() in getMoreData()
while (cin.read() != -1)
{
}
// Close, blindly performs additional doFinal() and fails because no tag in buffered ciphertext
cin.close();
}
}
The following code will fail in Java SE 7 when run with the BouncyCastle 1.47 JCE provider.
public class CipherInputStreamAEAD
{
public static void main(String[] args) throws Exception
{
Security.addProvider(new BouncyCastleProvider());
Cipher c = Cipher.getInstance( " AES/EAX/NoPadding " , " BC " );
Key key = new SecretKeySpec(new byte[16], " AES " );
byte[] iv = new byte[16];
c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] pt = new byte[1000];
byte[] ct = c.doFinal(pt);
c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
CipherInputStream cin = new CipherInputStream(new ByteArrayInputStream(ct), c);
// Read until no more data, forces Cipher.doFinal() in getMoreData()
while (cin.read() != -1)
{
}
// Close, blindly performs additional doFinal() and fails because no tag in buffered ciphertext
cin.close();
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
One workaround is to avoid calling CipherInputStream.close(), resetting the cipher manually.
This does not match well with expected usage of the CipherInputStream, and prevents use of try(Closeable) {} blocks.
BouncyCastle implementations can be modified to throw BadPaddingException (and may have to for compatibility with previous Java SE releases).
OpenJDK 8 AES/GCM implementation could be modified to throw BadPaddingException.
The BadPaddingException approach would be in my opinion simply masking the error in CipherInputStream - in the case of AEAD ciphers it is not legal to call Cipher.doFinal() on a reset Cipher instance.