bug in JCEea2 with DES/CBC/NoPadding decryption in place

George Chung (gchung@openhorizon.com)
Fri, 27 Mar 1998 20:15:24 -0800

From: "George Chung" <gchung@openhorizon.com>
To: <java-security@javasoft.com>
Subject: bug in JCEea2 with DES/CBC/NoPadding decryption in place
Date: Fri, 27 Mar 1998 20:15:24 -0800

This is a multi-part message in MIME format.

------=_NextPart_000_0008_01BD59BD.13FF4770
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

I found this bug by plugging your crypto in an SSL package that allows 3rd
party crypto to be plugged in, specifically IAIK SSL. Hey, I love your
stuff, but I think it would be worth your while to examine the possibility
of using their SSL framework to plug your crypto in, cycle through the
various SSL ciphersuites and try to talk to commercial SSL web servers using
their SSLClient sample. Hey, who can argue against too much QA! :-)

Anyway, I've attached the source code that reproduces the bug to this email.
Bug reproduces in ea in addition to ea2. Should be able to compile and run
against ea2 and JDK 1.2beta3.

Regards,
George Chung
Open Horizon, Inc.

------=_NextPart_000_0008_01BD59BD.13FF4770
Content-Type: application/octet-stream;
name="TestDESBugSun.java"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="TestDESBugSun.java"

import java.io.*;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import com.sun.crypto.provider.SunJCE;

public class TestDESBugSun
{
public static void main(String argv[]) throws Exception
{
String provider =3D "SunJCE";

Security.addProvider(new SunJCE());

Cipher desDecrypt =3D Cipher.getInstance("DES/CBC/NoPadding", =
provider);
String ivDecrypt =3D "C26B94DD3B5FCEF5";
String keyDecrypt =3D "838BDCD479844683";
desDecrypt.init(Cipher.DECRYPT_MODE,
new DESSecretKey(Hex.fromString(keyDecrypt)),
new IvParameterSpec(Hex.fromString(ivDecrypt))
);

=09
Cipher desEncrypt =3D Cipher.getInstance("DES/CBC/NoPadding", =
provider);
String ivEncrypt =3D "F27B52B042439CDE";
String keyEncrypt =3D "DEFB96E59809A08D";
desEncrypt.init(Cipher.ENCRYPT_MODE,
new DESSecretKey(Hex.fromString(keyEncrypt)),
new IvParameterSpec(Hex.fromString(ivEncrypt))
);

String plain =3D =
"0000000000140000241848C22A7A950A2133236E88F1F6087D7C0BF5904914A4E9F6668A=
369EB560708269A8F26915181555C1BD55148E412F9EFBFD975808BC5100000003FFFFFFF=
FFF";
String expectedCipher =3D =
"2FB593E1787FE132E1C94EEDAB69DCD7CFCFA762C64A9D0B4B76C274561E80D1637F5CFF=
DCF1E8A7346A78CE18CDCD0C37A125E175CC009CE316C1EDCF351840";
byte buffer[] =3D Hex.fromString(plain);
desEncrypt.doFinal(buffer, 5, 64, buffer, 5);
if (expectedCipher.equals(Hex.toString(buffer, 5, 64)))
System.out.println("DES/CBC encryption in place OK...");
else
System.out.println("DES/CBC encryption in place _not_ OK...");

String cipher =3D =
"000000000090C5BC54F6E97DF248401927463A1219C8300FD2F4E5AD6DD75A45EA19DF59=
27DD6329AC24AE46A3AB28FC7292A6C94E1BABFEF85A0E13A6F2B6214521DAA873FFFFFFF=
FFF";
String expectedPlain =3D =
"1400002455C380EE1909A475CAA9B27A9E7059DF9BB44C6302342D3787116DA9B2956367=
E91ACE4C7AA7AD34DA1C2A7F6E1B95709AE623078D15409E00000003";
buffer =3D Hex.fromString(cipher);
desDecrypt.doFinal(buffer, 5, 64, buffer, 5);
if (expectedPlain.equals(Hex.toString(buffer, 5, 64)))
System.out.println("DES/CBC decryption in place OK...");
else
System.out.println("DES/CBC decryption in place _not_ OK...");
}
}

final class DESSecretKey implements SecretKey
{
private byte m_key[];

public DESSecretKey(byte rawkey[])
throws InvalidKeyException
{
this(rawkey, 0);
}

public DESSecretKey(byte rawkey[], int offset)
throws InvalidKeyException
{
if (rawkey =3D=3D null || rawkey.length - offset < 8)
throw new InvalidKeyException("Wrong key size");
m_key =3D new byte[8];
System.arraycopy(rawkey, offset, m_key, 0, 8);
}

public String getAlgorithm()
{
return "DES";
}

public byte[] getEncoded()
{
byte rawkey[] =3D new byte[8];
System.arraycopy(m_key, 0, rawkey, 0, 8);
return rawkey;
}

public int getEncodedLength() { return 8; }

public int getEncoded(byte buffer[], int offset, int length)
{
System.arraycopy(m_key, 0, buffer, offset, 8);
return 8;
}

public String getFormat()
{
return "RAW";
}
}

class Hex
{
private Hex() {} // static methods only

private static final char[] hexDigits =3D {
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
};

/**
* Returns a string of hexadecimal digits from a byte array. Each
* byte is converted to 2 hex symbols.
* <p>
* If offset and length are omitted, the whole array is used.
*/
public static String toString(byte[] ba, int offset, int length) {
char[] buf =3D new char[length * 2];
int j =3D 0;
int k;

for (int i =3D offset; i < offset + length; i++) {
k =3D ba[i];
buf[j++] =3D hexDigits[(k >>> 4) & 0x0F];
buf[j++] =3D hexDigits[ k & 0x0F];
}
return new String(buf);
}

public static String toString(byte[] ba) {
return toString(ba, 0, ba.length);
}

/**
* Returns a string of hexadecimal digits from an integer array. =
Each
* int is converted to 4 hex symbols.
* <p>
* If offset and length are omitted, the whole array is used.
*/
public static String toString(int[] ia, int offset, int length) {
char[] buf =3D new char[length * 8];
int j =3D 0;
int k;

for (int i =3D offset; i < offset + length; i++) {
k =3D ia[i];
buf[j++] =3D hexDigits[(k >>> 28) & 0x0F];
buf[j++] =3D hexDigits[(k >>> 24) & 0x0F];
buf[j++] =3D hexDigits[(k >>> 20) & 0x0F];
buf[j++] =3D hexDigits[(k >>> 16) & 0x0F];
buf[j++] =3D hexDigits[(k >>> 12) & 0x0F];
buf[j++] =3D hexDigits[(k >>> 8) & 0x0F];
buf[j++] =3D hexDigits[(k >>> 4) & 0x0F];
buf[j++] =3D hexDigits[ k & 0x0F];
}
return new String(buf);
}

public static String toString(int[] ia) {
return toString(ia, 0, ia.length);
}

/**
* Returns a string of hexadecimal digits in reverse order from a =
byte array
* (i.e. the least significant byte is first, but within each byte =
the
* most significant hex digit is before the least significant hex =
digit).
* <p>
* If offset and length are omitted, the whole array is used.
*/
public static String toReversedString(byte[] b, int offset, int =
length) {
char[] buf =3D new char[length * 2];
int j =3D 0;

for (int i =3D offset + length - 1; i >=3D offset; i--) {
buf[j++] =3D hexDigits[(b[i] >>> 4) & 0x0F];
buf[j++] =3D hexDigits[ b[i] & 0x0F];
}
return new String(buf);
}

public static String toReversedString(byte[] b) {
return toReversedString(b, 0, b.length);
}

/**
* Returns a byte array from a string of hexadecimal digits.
*/
public static byte[] fromString(String hex) {
int len =3D hex.length();
byte[] buf =3D new byte[((len + 1) / 2)];

int i =3D 0, j =3D 0;
if ((len % 2) =3D=3D 1)
buf[j++] =3D (byte) fromDigit(hex.charAt(i++));

while (i < len) {
buf[j++] =3D (byte) ((fromDigit(hex.charAt(i++)) << 4) |
fromDigit(hex.charAt(i++)));
}
return buf;
}

/**
* Returns a byte array from a string of hexadecimal digits in =
reverse
* order (i.e. the least significant byte is first, but within each =
byte the
* most significant hex digit is before the least significant hex =
digit).
* The string must have an even number of digits.
* <p>
* This is not really either little nor big-endian; it's just =
obscure. It is
* here because it is the format used for the SPEED certification =
data.
*/
public static byte[] fromReversedString(String hex) {
int len =3D hex.length();
byte[] buf =3D new byte[((len + 1) / 2)];

int j =3D 0;
if ((len % 2) =3D=3D 1) throw new IllegalArgumentException(
"string must have an even number of digits");

while (len > 0) {
buf[j++] =3D (byte) (fromDigit(hex.charAt(--len)) |
(fromDigit(hex.charAt(--len)) << 4));
}
return buf;
}

/**
* Returns the hex digit corresponding to a number <i>n</i>, from 0 =
to 15.
*/
public static char toDigit(int n) {
try {
return hexDigits[n];
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException(n +
" is out of range for a hex digit");
}
}

/**
* Returns the number from 0 to 15 corresponding to the hex digit =
<i>ch</i>.
*/
public static int fromDigit(char ch) {
if (ch >=3D '0' && ch <=3D '9')
return ch - '0';
if (ch >=3D 'A' && ch <=3D 'F')
return ch - 'A' + 10;
if (ch >=3D 'a' && ch <=3D 'f')
return ch - 'a' + 10;

throw new IllegalArgumentException("invalid hex digit '" + ch + =
"'");
}

/**
* Returns a string of 2 hexadecimal digits (most significant digit =
first)
* corresponding to the lowest 8 bits of <i>n</i>.
*/
public static String byteToString(int n) {
char[] buf =3D { hexDigits[(n >>> 4) & 0x0F], hexDigits[n & =
0x0F] };
return new String(buf);
}

/**
* Returns a string of 4 hexadecimal digits (most significant digit =
first)
* corresponding to the lowest 16 bits of <i>n</i>.
*/
public static String shortToString(int n) {
char[] buf =3D { hexDigits[(n >>> 12) & 0x0F], hexDigits[(n >>> =
8) & 0x0F],
hexDigits[(n >>> 4) & 0x0F], hexDigits[ n =
& 0x0F] };
return new String(buf);
}

/**
* Returns a string of 8 hexadecimal digits (most significant digit =
first)
* corresponding to the integer <i>n</i>, which is treated as =
unsigned.
*/
public static String intToString(int n) {
char[] buf =3D new char[8];
=20
for (int i =3D 7; i >=3D 0; i--) {
buf[i] =3D hexDigits[n & 0x0F];
n >>>=3D 4;
}
return new String(buf);
}

/**
* Returns a string of 16 hexadecimal digits (most significant digit =
first)
* corresponding to the long <i>n</i>, which is treated as unsigned.
*/
public static String longToString(long n) {
char[] buf =3D new char[16];
=20
for (int i =3D 15; i >=3D 0; i--) {
buf[i] =3D hexDigits[(int)n & 0x0F];
n >>>=3D 4;
}
return new String(buf);
}

/**
* Dump a byte array as a string, in a format that is easy to read =
for
* debugging. The string <i>m</i> is prepended to the start of each =
line.
* <p>
* If offset and length are omitted, the whole array is used. If m =
is
* omitted, nothing is prepended to each line.
*
* @param data the byte array to be dumped
* @param offset the offset within <i>data</i> to start from
* @param length the number of bytes to dump
* @param m a string to be prepended to each line
* @return a String containing the dump.
*/
public static String dumpString(byte[] data, int offset, int length, =
String m) {
if (data =3D=3D null) return m + "null\n";

StringBuffer sb =3D new StringBuffer(length * 3);
if (length > 32)
sb.append(m).append("Hexadecimal dump of ").append(length)
.append(" bytes...\n");

// each line will list 32 bytes in 4 groups of 8 each
int end =3D offset + length;
String s;
int l =3D Integer.toString(length).length();
if (l < 4) l =3D 4;
for (; offset < end; offset +=3D 32) {
if (length > 32) {
s =3D " " + offset;
sb.append(m).append(s.substring(s.length()-l)).append(": =
");
}
int i =3D 0;
for (; i < 32 && offset + i + 7 < end; i +=3D 8)
sb.append(toString(data, offset + i, 8)).append(' ');

if (i < 32) {
for (; i < 32 && offset + i < end; i++)
sb.append(byteToString(data[offset + i]));
}
sb.append('\n');
}
return sb.toString();
}

public static String dumpString(byte[] data) {
return (data =3D=3D null) ? "null\n"
: dumpString(data, 0, data.length, "");
}

public static String dumpString(byte[] data, String m) {
return (data =3D=3D null) ? "null\n"
: dumpString(data, 0, data.length, m);
}

public static String dumpString(byte[] data, int offset, int length) =
{
return dumpString(data, offset, length, "");
}

/**
* Dump an int array as a string, in a format that is easy to read =
for
* debugging. The string <i>m</i> is prepended to the start of each =
line.
* <p>
* If offset and length are omitted, the whole array is used. If m =
is
* omitted, nothing is prepended to each line.
*
* @param data The int[] to dump
* @param offset The offset within <i>data</i> to start from
* @param length The number of ints to dump
* @param m A string to prepend to each line
* @return A String containing the dump.
*/
public static String dumpString(int[] data, int offset, int length, =
String m)
{
if (data =3D=3D null) return m + "null\n";

StringBuffer sb =3D new StringBuffer(length * 3);
if (length > 8)
sb.append(m).append("Hexadecimal dump of ").append(length)
.append(" integers...\n");

// each line will list 32 bytes in 8 groups of 4 each (1 int)
int end =3D offset + length;
String s;
int x =3D Integer.toString(length).length();
if (x < 8) x =3D 8;
for ( ; offset < end; ) {
if (length > 8) {
s =3D " " + offset;
sb.append(m).append(s.substring(s.length()-x)).append(": =
");
}
for (int i =3D 0; i < 8 && offset < end; i++)
sb.append(intToString(data[offset++])).append(' ');
sb.append('\n');
}
return sb.toString();
}

public static String dumpString(int[] data) {
return dumpString(data, 0, data.length, "");
}

public static String dumpString(int[] data, String m) {
return dumpString(data, 0, data.length, m);
}

public static String dumpString(int[] data, int offset, int length) =
{
return dumpString(data, offset, length, "");
}

// Test methods
//.......................................................................=
....

public static void main(String[] args) {
self_test(new PrintWriter(System.out, true));
}

public static void self_test(PrintWriter out) {
String test =3D "Hello. This is a test string with more than 32 =
characters.";
byte[] buf =3D new byte[test.length()];
for (int i =3D 0; i < test.length(); i++) {
buf[i] =3D (byte) test.charAt(i);
}
String s;
byte[] buf2;

s =3D toString(buf);
out.println("Hex.toString(buf) =3D " + s);
buf2 =3D fromString(s);
if (!ArrayUtil.areEqual(buf, buf2))
System.out.println("buf !=3D buf2");

s =3D toReversedString(buf);
out.println("Hex.toReversedString(buf) =3D " + s);
buf2 =3D fromReversedString(s);
if (!ArrayUtil.areEqual(buf, buf2))
out.println("buf !=3D buf2");

out.print("Hex.dumpString(buf, 0, 28) =3D\n" + dumpString(buf, =
0, 28));
out.print("Hex.dumpString(null) =3D\n" + dumpString((byte[]) =
null));
out.print(dumpString(buf, "+++"));

out.flush();
}
}

class ArrayUtil
{
private ArrayUtil() {} // static methods only

private static final int ZEROES_LEN =3D 500; // adjust for =
memory/speed tradeoff
private static byte[] zeroes =3D new byte[ZEROES_LEN];

/**
* Clears a byte array to all-zeroes.
*/
public static void clear (byte[] buf) { clear(buf, 0, buf.length); }

/**
* Clears <i>length</i> bytes of a byte array to zeroes, starting at
* <i>offset</i>.
*/
public static void clear (byte[] buf, int offset, int length) {
if (length <=3D ZEROES_LEN)
System.arraycopy(zeroes, 0, buf, offset, length);
else {
System.arraycopy(zeroes, 0, buf, offset, ZEROES_LEN);
int halflength =3D length / 2;
for (int i =3D ZEROES_LEN; i < length; i +=3D i) {
System.arraycopy(buf, offset, buf, offset+i,
(i <=3D halflength) ? i : length-i);
}
}
}

/**
* Unserializes a long from a byte array at a specific offset in =
big-endian order
*
* @param b byte array to read a short value from.
* @param offset offset within byte array to start reading short.
* @return short read from byte array.
* @author Tom Orsi
*/
public static long readLong(byte b[], int offset)
{
long retValue;

retValue =3D ((long)b[offset++] & 0xff) << 56;
retValue |=3D ((long)b[offset++] & 0xff) << 48;
retValue |=3D ((long)b[offset++] & 0xff) << 40;
retValue |=3D ((long)b[offset++] & 0xff) << 32;
retValue |=3D ((long)b[offset++] & 0xff) << 24;
retValue |=3D ((long)b[offset++] & 0xff) << 16;
retValue |=3D ((long)b[offset++] & 0xff) << 8;
retValue |=3D (long)b[offset] & 0xff;

return retValue;
}

/**
* Serializes long into the byte array at offset in big-endian order
*/
public static void writeLong(byte b[], int offset, long value)
{
b[offset++] =3D (byte)(value >>> 56);
b[offset++] =3D (byte)(value >>> 48);
b[offset++] =3D (byte)(value >>> 40);
b[offset++] =3D (byte)(value >>> 32);
b[offset++] =3D (byte)(value >>> 24);
b[offset++] =3D (byte)(value >>> 16);
b[offset++] =3D (byte)(value >>> 8);
b[offset] =3D (byte)value;
}

/**
* Unserializes an integer from a byte array in big-endian order
*
* @param b byte array to read integer from.
* @param offset location within the byte array to read integer from.
* @author Tom Orsi
*/
public static int readInt(byte b[], int offset)
{
int retValue;

retValue =3D ((int)b[offset++] & 0xff) << 24;
retValue |=3D ((int)b[offset++] & 0xff) << 16;
retValue |=3D ((int)b[offset++] & 0xff) << 8;
retValue |=3D (int)b[offset] & 0xff;

return retValue;
}

/**
* Serializes an int into the byte array at offset. The int's MSB is =
first
* (big-endian order).
*/
public static void writeInt(byte[] b, int offset, int value)
{
b[offset++] =3D (byte)(value >>> 24);
b[offset++] =3D (byte)(value >>> 16);
b[offset++] =3D (byte)(value >>> 8);
b[offset] =3D (byte)value;
}

/**
* Unserializes a short from a byte array at a specific offset in =
big-endian order
*
* @param b byte array to read a short value from.
* @param offset offset within byte array to start reading short.
* @return short read from byte array.
* @author Tom Orsi
*/
public static short readShort(byte b[], int offset)
{
int retValue;

retValue =3D b[offset++] << 8;
retValue |=3D b[offset] & 0xff;

return (short)retValue;
}

/**
* Serializes a short into the byte array at offset. The short's MSB is =
first
* (big-endian order).
*/
public static void writeShort(byte b[], int offset, short value)
{
b[offset++] =3D (byte)(value >>> 8);
b[offset] =3D (byte)value;
}

/**
* Unserializes a String from the byte array at offset
*/
public static String getString(byte b[], int offset)
{
int len =3D ((int)b[offset++] & 0xff) << 8 | ((int)b[offset] & 0xff);
return new String(b, offset+1, len);
}

/**
* Returns an int built from two shorts.
*
* @param s0 the least significant short
* @param s1 the most significant short
*/
public static int toInt(short s0, short s1) { return (s0 & 0xFFFF) | =
(s1 << 16); }

/**
* Returns a short built from two bytes.
*
* @param b0 the least significant byte
* @param b1 the most significant byte
*/
public static short toShort(byte b0, byte b1) { return (short) (b0 & =
0xFF | b1 << 8); }

/**
* Returns a 4-byte array built from an int. The int's MSB is first
* (big-endian order).
*/
public static byte[] toBytes(int n)
{
byte[] buf =3D new byte[4];
writeInt(buf, 0, n);
return buf;
}

/**
* Returns a byte array built from a short array. Each short is =
broken
* into 2 bytes with the short's MSB first (big-endian order).
* <p>
* If offset and length are omitted, the whole array is used.
*/
public static byte[] toBytes(short[] array, int offset, int length) =
{
byte[] buf =3D new byte[2 * length];
int j =3D 0;

for (int i =3D offset; i < offset + length; i++) {
buf[j++] =3D (byte) ((array[i] >>> 8) & 0xFF);
buf[j++] =3D (byte) (array[i] & 0xFF);
}
return buf;
}
=20
public static byte[] toBytes(short[] array) { return toBytes(array, =
0, array.length); }

/**
* Returns a short array built from a byte array. Each 2 bytes form
* a short with the first byte as the short's MSB (big-endian =
order).
* <p>
* If offset and length are omitted, the whole array is used.
*/
public static short[] toShorts(byte[] array, int offset, int length) =
{
short[] buf =3D new short[length / 2];
int j =3D 0;

for (int i =3D offset; i < offset + length - 1; i +=3D 2)
buf[j++] =3D (short) (((array[i] & 0xFF) << 8) | (array[i + =
1] & 0xFF));

return buf;
}

public static short[] toShorts(byte[] array) { return =
toShorts(array, 0, array.length); }

/**
* Compares two byte arrays for equality.
*
* @return true if the arrays have identical contents
*/
public static boolean areEqual(byte[] a, byte[] b) {
int aLength =3D a.length;
if (aLength !=3D b.length) return false;

for (int i =3D 0; i < aLength; i++)
if (a[i] !=3D b[i]) return false;

return true;
}

/**
* Compares two int arrays for equality.
*
* @return true if the arrays have identical contents
*/
public static boolean areEqual(int[] a, int[] b) {
int aLength =3D a.length;
if (aLength !=3D b.length) return false;

for (int i =3D 0; i < aLength; i++)
if (a[i] !=3D b[i]) return false;

return true;
}

/*
* Compare two byte arrays returning -1, 0 or +1 if the first =
argument
* is less than, equal to, or greater than the second one. Both =
arguments
* are assumed to have the same order of byte significance.
* <p>
* When last argument is true, the comparison assumes the MSB (Most
* Significant Byte) is at the highest index position, and when it's
* false, the contrary; i.e. MSB at [0].
*
* @since Cryptix 2.2.2
* @return -1, 0 or 1 if a < b, a =3D=3D b and a > b respectively.
*/
public static int compared (byte[] a, byte[] b, boolean msbFirst) {
int aLength =3D a.length;
if (aLength < b.length) return -1;
if (aLength > b.length) return 1;
int b1, b2;
if (msbFirst)
for (int i =3D aLength - 1; i >=3D 0; i--) {
b1 =3D a[i] & 0xFF;
b2 =3D b[i] & 0xFF;
if (b1 < b2) return -1;
if (b1 > b2) return 1;
}
else
for (int i =3D 0; i < aLength; i++) {
b1 =3D a[i] & 0xFF;
b2 =3D b[i] & 0xFF;
if (b1 < b2) return -1;
if (b1 > b2) return 1;
}
return 0;
}

/** @return true If the data in the byte array consists of just =
text. */
public static boolean isText (byte[] buffer) {
int len =3D buffer.length;
if (len =3D=3D 0) return false;
for (int i =3D 0; i < len; i++) {
int c =3D buffer[i] & 0xFF;
if (c < '\u0020' || c > '\u007F')
switch (c) { // control chars that are allowed in text =
files
case '\u0007': // BEL
case '\u0008': // BS
case '\t': // HT
case '\n': // LF
case '\u000B': // VT
case '\u000C': // FF
case '\r': // CR
case '\u001A': // EOF
case '\u001B': // ESC
case '\u009B': // CSI
continue;
default: // anything else, isn't.
return false;
}
}
return true;
}
}

------=_NextPart_000_0008_01BD59BD.13FF4770--