Details
-
Bug
-
Resolution: Fixed
-
P2
-
7
-
b27
-
x86
-
windows_xp
-
Verified
Backports
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-2221012 | 7u4 | Bradford Wetmore | P2 | Closed | Fixed | b13 |
JDK-2221014 | 6u32 | Mala Bankal | P2 | Closed | Fixed | b03 |
JDK-2221015 | OpenJDK6 | Mala Bankal | P2 | Resolved | Fixed | b26 |
JDK-2221016 | 5.0u35 | Mala Bankal | P2 | Closed | Fixed | b02 |
Description
FULL PRODUCT VERSION :
java version "1.7.0_02"
Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
Java HotSpot(TM) Client VM (build 22.0-b10, mixed mode, sharing)
- OR -
Java HotSpot(TM) Server VM (build 22.0-b10, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
When invoking Cipher.doFinal(ByteBuffer, ByteBuffer) with input.remaining() == 0 and at least one of the buffers is a direct buffer, the method fails to process any data internally buffered in the cipher and store it in the output buffer.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create and initialize a cipher, e.g. for the DES/CBC/PKCS5Padding transformation.
2. Allocate two separate byte buffers inBuffer and outBuffer, at least one of which is a direct buffer, of sufficient size, and put some random data into inBuffer.
3. Invoke cipher.update(inBuffer, outBuffer), which processes all data except for the last (partial or complete) block. inBuffer.hasRemaining() should now return false.
4. Invoke cipher.doFinal(inBuffer, outBuffer) to process the last block which is buffered in the cipher, applying padding if necessary.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
outBuffer should now contain all encrypted blocks, including the last one that was processed by doFinal(ByteBuffer, ByteBuffer).
Subsequently calling doFinal() [no arguments] twice will yield byte arrays with equal contents, the ciphertext for the empty message.
ACTUAL -
outBuffer contains all blocks EXCEPT the last one that should have been processed by doFinal(ByteBuffer, ByteBuffer).
Subsequently calling doFinal() [no arguments] twice will yield byte arrays with DIFFERENT contents, as the first invocation finally processes the data internally buffered in the cipher.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package javax.crypto.test;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Simple test case to show that {@link javax.crypto.Cipher#doFinal(ByteBuffer, ByteBuffer)} fails to process the data
* internally buffered in the cipher when <tt>input.remaining() == 0<tt> and at least one buffer is a direct buffer.
*/
public class BrokenDoFinal {
public static void main(String args[]) throws Exception {
Random random = new SecureRandom();
byte[] keyBytes = new byte[8];
random.nextBytes(keyBytes);
SecretKey key = new SecretKeySpec(keyBytes, "DES");
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
int size = 12;
byte[] testdata = new byte[size];
random.nextBytes(testdata);
byte[] expected = cipher.doFinal(testdata);
// first case: both buffers are heap buffers
System.out.print("Test case 1 (only heap buffers) : ");
ByteBuffer in1 = ByteBuffer.allocate(size);
ByteBuffer out1 = ByteBuffer.allocate(cipher.getOutputSize(size));
// this succeeds
try {
encrypt(in1, out1, cipher, testdata, expected);
System.out.println("ok");
} catch (AssertionError e) {
System.out.println("FAILED -- " + e.getMessage());
}
if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
System.out.println(" Internal buffers still held data!");
}
// second case: input buffer is direct buffer, output buffer is heap buffer
System.out.print("Test case 2 (input buffer is direct) : ");
ByteBuffer in2 = ByteBuffer.allocateDirect(size);
ByteBuffer out2 = ByteBuffer.allocate(cipher.getOutputSize(size));
// this fails
try {
encrypt(in2, out2, cipher, testdata, expected);
System.out.println("ok");
} catch (AssertionError e) {
System.out.println("FAILED -- " + e.getMessage());
}
if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
System.out.println(" Internal buffers still held data!");
}
// third case: input buffer is heap buffer, output buffer is direct buffer
System.out.print("Test case 3 (output buffer is direct) : ");
ByteBuffer in3 = ByteBuffer.allocate(size);
ByteBuffer out3 = ByteBuffer.allocateDirect(cipher.getOutputSize(size));
// this fails, too
try {
encrypt(in3, out3, cipher, testdata, expected);
System.out.println("ok");
} catch (AssertionError e) {
System.out.println("FAILED -- " + e.getMessage());
}
if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
System.out.println(" Internal buffers still held data!");
}
// fourth case: both buffers are direct buffers
System.out.print("Test case 4 (both buffers are direct) : ");
ByteBuffer in4 = ByteBuffer.allocateDirect(size);
ByteBuffer out4 = ByteBuffer.allocateDirect(cipher.getOutputSize(size));
// and this of course also fails
try {
encrypt(in4, out4, cipher, testdata, expected);
System.out.println("ok");
} catch (AssertionError e) {
System.out.println("FAILED -- " + e.getMessage());
}
if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
System.out.println(" Internal buffers still held data!");
}
}
private static void encrypt(ByteBuffer in, ByteBuffer out, Cipher cipher, byte[] testdata, byte[] expected)
throws Exception {
in.put(testdata);
in.flip();
// process all data
cipher.update(in, out);
if (in.hasRemaining()) {
throw new RuntimeException("buffer not empty");
}
// finish encryption and process all data buffered in the cipher
cipher.doFinal(in, out);
out.flip();
// validate output size
if (out.remaining() != expected.length) {
throw new AssertionError("incomplete encryption output, expected " + expected.length
+ " bytes but was only " + out.remaining() + " bytes");
}
// validate output data
byte[] encrypted = new byte[out.remaining()];
out.get(encrypted);
if (Arrays.equals(expected, encrypted) == false) {
throw new AssertionError("bad encryption output");
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Use Cipher.doFinal() [no arguments] to retrieve a byte array with the last block instead. If necessary for channel I/O, put the contents of the byte array into the output buffer.
java version "1.7.0_02"
Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
Java HotSpot(TM) Client VM (build 22.0-b10, mixed mode, sharing)
- OR -
Java HotSpot(TM) Server VM (build 22.0-b10, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
When invoking Cipher.doFinal(ByteBuffer, ByteBuffer) with input.remaining() == 0 and at least one of the buffers is a direct buffer, the method fails to process any data internally buffered in the cipher and store it in the output buffer.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create and initialize a cipher, e.g. for the DES/CBC/PKCS5Padding transformation.
2. Allocate two separate byte buffers inBuffer and outBuffer, at least one of which is a direct buffer, of sufficient size, and put some random data into inBuffer.
3. Invoke cipher.update(inBuffer, outBuffer), which processes all data except for the last (partial or complete) block. inBuffer.hasRemaining() should now return false.
4. Invoke cipher.doFinal(inBuffer, outBuffer) to process the last block which is buffered in the cipher, applying padding if necessary.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
outBuffer should now contain all encrypted blocks, including the last one that was processed by doFinal(ByteBuffer, ByteBuffer).
Subsequently calling doFinal() [no arguments] twice will yield byte arrays with equal contents, the ciphertext for the empty message.
ACTUAL -
outBuffer contains all blocks EXCEPT the last one that should have been processed by doFinal(ByteBuffer, ByteBuffer).
Subsequently calling doFinal() [no arguments] twice will yield byte arrays with DIFFERENT contents, as the first invocation finally processes the data internally buffered in the cipher.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package javax.crypto.test;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Simple test case to show that {@link javax.crypto.Cipher#doFinal(ByteBuffer, ByteBuffer)} fails to process the data
* internally buffered in the cipher when <tt>input.remaining() == 0<tt> and at least one buffer is a direct buffer.
*/
public class BrokenDoFinal {
public static void main(String args[]) throws Exception {
Random random = new SecureRandom();
byte[] keyBytes = new byte[8];
random.nextBytes(keyBytes);
SecretKey key = new SecretKeySpec(keyBytes, "DES");
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
int size = 12;
byte[] testdata = new byte[size];
random.nextBytes(testdata);
byte[] expected = cipher.doFinal(testdata);
// first case: both buffers are heap buffers
System.out.print("Test case 1 (only heap buffers) : ");
ByteBuffer in1 = ByteBuffer.allocate(size);
ByteBuffer out1 = ByteBuffer.allocate(cipher.getOutputSize(size));
// this succeeds
try {
encrypt(in1, out1, cipher, testdata, expected);
System.out.println("ok");
} catch (AssertionError e) {
System.out.println("FAILED -- " + e.getMessage());
}
if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
System.out.println(" Internal buffers still held data!");
}
// second case: input buffer is direct buffer, output buffer is heap buffer
System.out.print("Test case 2 (input buffer is direct) : ");
ByteBuffer in2 = ByteBuffer.allocateDirect(size);
ByteBuffer out2 = ByteBuffer.allocate(cipher.getOutputSize(size));
// this fails
try {
encrypt(in2, out2, cipher, testdata, expected);
System.out.println("ok");
} catch (AssertionError e) {
System.out.println("FAILED -- " + e.getMessage());
}
if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
System.out.println(" Internal buffers still held data!");
}
// third case: input buffer is heap buffer, output buffer is direct buffer
System.out.print("Test case 3 (output buffer is direct) : ");
ByteBuffer in3 = ByteBuffer.allocate(size);
ByteBuffer out3 = ByteBuffer.allocateDirect(cipher.getOutputSize(size));
// this fails, too
try {
encrypt(in3, out3, cipher, testdata, expected);
System.out.println("ok");
} catch (AssertionError e) {
System.out.println("FAILED -- " + e.getMessage());
}
if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
System.out.println(" Internal buffers still held data!");
}
// fourth case: both buffers are direct buffers
System.out.print("Test case 4 (both buffers are direct) : ");
ByteBuffer in4 = ByteBuffer.allocateDirect(size);
ByteBuffer out4 = ByteBuffer.allocateDirect(cipher.getOutputSize(size));
// and this of course also fails
try {
encrypt(in4, out4, cipher, testdata, expected);
System.out.println("ok");
} catch (AssertionError e) {
System.out.println("FAILED -- " + e.getMessage());
}
if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
System.out.println(" Internal buffers still held data!");
}
}
private static void encrypt(ByteBuffer in, ByteBuffer out, Cipher cipher, byte[] testdata, byte[] expected)
throws Exception {
in.put(testdata);
in.flip();
// process all data
cipher.update(in, out);
if (in.hasRemaining()) {
throw new RuntimeException("buffer not empty");
}
// finish encryption and process all data buffered in the cipher
cipher.doFinal(in, out);
out.flip();
// validate output size
if (out.remaining() != expected.length) {
throw new AssertionError("incomplete encryption output, expected " + expected.length
+ " bytes but was only " + out.remaining() + " bytes");
}
// validate output data
byte[] encrypted = new byte[out.remaining()];
out.get(encrypted);
if (Arrays.equals(expected, encrypted) == false) {
throw new AssertionError("bad encryption output");
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Use Cipher.doFinal() [no arguments] to retrieve a byte array with the last block instead. If necessary for channel I/O, put the contents of the byte array into the output buffer.
Attachments
Issue Links
- backported by
-
JDK-2221015 Cipher.doFinal(ByteBuffer,ByteBuffer) fails to process when in.remaining() == 0
-
- Resolved
-
-
JDK-2221012 Cipher.doFinal(ByteBuffer,ByteBuffer) fails to process when in.remaining() == 0
-
- Closed
-
-
JDK-2221014 Cipher.doFinal(ByteBuffer,ByteBuffer) fails to process when in.remaining() == 0
-
- Closed
-
-
JDK-2221016 Cipher.doFinal(ByteBuffer,ByteBuffer) fails to process when in.remaining() == 0
-
- Closed
-