Java5 signature fails verify with Java6

Hi!

I have an application that create signatures with versions 1.4.2 and 1.5.0. But when I use Java 6 to verify those signatures, the verification always fails. A signature generated with Java 6 will verify with Java 6, but on 1.4.2 and 1.5.0 verify fails.

I run the exact same code to create signatures and verify them on every JVM version.

I use hard-coded keys, they are the same for all platforms.

Signature algorithm is SHA1withDSA, provider is SUN.

What's wrong with my code or what has changed between 1.5.0 and Java 6?

[565 byte] By [arivea] at [2007-11-26 21:39:06]
# 1
> What's wrong with my code or what has changed between 1.5.0 and Java 6?It's obvious! The problem is right there on line 37. Here is the correction: Is that clearer?
ghstarka at 2007-7-10 3:22:38 > top of Java-index,Security,Cryptography...
# 2

Point taken ;)

Key initialization:

private PrivateKey kPrivate = null;

private byte[] privateEncKey = ....;

private PublicKey kPublic = null;

private byte[] publicEncKey = ....;

public void initKeys() {

X509EncodedKeySpec xeksPublic = new X509EncodedKeySpec( publicEncKey );

KeyFactory kfPublic = KeyFactory.getInstance("DSA","SUN");

kPublic = kfPublic.generatePublic( xeksPublic );

PKCS8EncodedKeySpec xeksPrivate = new PKCS8EncodedKeySpec( privateEncKey );

KeyFactory kfPrivate = KeyFactory.getInstance("DSA","SUN");

kPrivate = kfPrivate.generatePrivate( xeksPrivate );

}

Signature creation:

private byte[] sign(byte[] signInput) {

try {

Signature signCre = Signature.getInstance("SHA1withDSA","SUN");

signCre.initSign( kPrivate );

signCre.update( signInput );

return signCre.sign();

}

catch (Exception e) {

e.printStackTrace();

return null;

}

}

Verification

private final boolean verify(byte[] input, byte[] signature) throws Exception

{

Signature signVer = Signature.getInstance("SHA1withDSA","SUN");

signVer.initVerify(kPublic);

signVer.update(input);

return signVer.verify(signature);

}

arivea at 2007-7-10 3:22:38 > top of Java-index,Security,Cryptography...
# 3
Building a simple test harness with your code, I can sign using any Sun jre in 1.4.1, 1.4.2, 1.5.10 and 1.6 and I can verify the signature with any other jre in the same range or the same jre as was used to sign!
sabre150a at 2007-7-10 3:22:38 > top of Java-index,Security,Cryptography...
# 4

Yes... that is true. My results are as follows

A signature created by 1.4.2

- verified ok with 1.4.2

- verified ok with 1.5

- verification fails with 1.6

A signature created by 1.5

- verified ok with 1.4.2

- verified ok with 1.5

- verification fails with 1.6

A signature created by 1.6

- verification fails with 1.4.2

- verification fails with 1.5

- verification ok with 1.6

The problem is that I need a signature that can be verified successfully on 1.4.2, 1.5 and 1.6.

The background is that the signature is used in a product license and the product should be usable on 1.4.2, 1.5 and 1.6, most of the time in mixed enviroment of all those VMs.

arivea at 2007-7-10 3:22:38 > top of Java-index,Security,Cryptography...
# 5
I think you are missing the point! I don't get your failures to verify.It does not matter which jvm I sign with, I can verify with any jvm. I never ever ever get any failures to verify regardless of which jvm I use to sign and which jvm I use to verify.
sabre150a at 2007-7-10 3:22:38 > top of Java-index,Security,Cryptography...
# 6
OK. That's good news and bad news... The good news is that I'll get my licenses working but the bad news is that I have no clue where things go wrong. Oh well, back to the drawing board... eh... debugger.
arivea at 2007-7-10 3:22:38 > top of Java-index,Security,Cryptography...
# 7
How are you storing the signature?
sabre150a at 2007-7-10 3:22:38 > top of Java-index,Security,Cryptography...
# 8

The signature is Base64 encoded using Robert Harder's Base64 class (version 1.4) and stored as a string.

The verification uses the same class to decode the signature string

byte[] signature = Base64.decode( signatureString );

Another point of possible failure is the input which is a String converted to bytes using the following code:

public static byte[] convertToBytes(String input) {

Charset cset = Charset.forName("UTF-16");

ByteBuffer bbuf = cset.encode(input);

return bbuf.array();

}

arivea at 2007-7-10 3:22:38 > top of Java-index,Security,Cryptography...
# 9

Is there some reason you don't just do

public static byte[] convertToBytes(String input)

{

return input.getBytes("utf-16");

}

Also, is there some reason why you use utf-16 rather than utf-8 ?

sabre150a at 2007-7-10 3:22:39 > top of Java-index,Security,Cryptography...
# 10
Thank you! convertToBytes is the cause of my problems. With String's getBytes everything works just fine.There might have been some reason I used ByteBuffer and UTF-16 etc. but I do not remember what it was.
arivea at 2007-7-10 3:22:39 > top of Java-index,Security,Cryptography...
# 11

> Thank you! convertToBytes is the cause of my

> problems. With String's getBytes everything works

> just fine.

>

> There might have been some reason I used ByteBuffer

> and UTF-16 etc. but I do not remember what it was.

Make sure you specify the encoding! Don't rely on the default.

P.S. I used utf-8 encoding.

Message was edited by:

sabre150

sabre150a at 2007-7-10 3:22:39 > top of Java-index,Security,Cryptography...
# 12

OK, I found the problem.

First of all, the UTF-16 encoding method I used before behaves differently on 1.6 than on 1.4.2 and 1.5.

Compared to String's getBytes() -method Charset+ByteBuffer code creates approximately twice as long byte array, but on 1.6, it creates an array that is one byte longer than on 1.4.2 and 1.5. The longer array is padded with 0-bytes on the end.

String input = "abc";

getBytes: len:8 [-2 -1 0 97 0 98 0 99 ]

CharSet+ByteBuffer: len:12 [-2 -1 0 97 0 98 0 99 0 0 0 0 ]

CharSet+ByteBuffer_on_1.6: len:13 [-2 -1 0 97 0 98 0 99 0 0 0 0 0 ]

So on 1.6 I cut a byte from the byte array and now everything works just fine.

public static byte[] legacyConvertToBytes(String input) {

Charset cset = Charset.forName("UTF-16");

ByteBuffer bbuf = cset.encode(input);

byte[] output = bbuf.array();

if ( JavaVersionCheck.isVersionAtLeast(1,6,0)) {

byte[] newOutput = new byte[output.length-1];

System.arraycopy(output,0,newOutput,0,output.length-1);

return newOutput;

}

return output;

}

JavaVersionCheck is my own utility class.

arivea at 2007-7-10 3:22:39 > top of Java-index,Security,Cryptography...
# 13

> > String input = "abc";

> getBytes: len:8 [-2 -1 0 97 0 98 0 99 ]

> CharSet+ByteBuffer: len:12 [-2 -1 0 97 0 98 0 99 0 0

> 0 0 ]

> CharSet+ByteBuffer_on_1.6: len:13 [-2 -1 0 97 0 98 0

> 99 0 0 0 0 0 ]

>

>

The getBytes() output looks like the correct UTF-16 encoding. The last two look incorrect. I can't say I understand the CharsetEncoder class well enough to call it a bug though.

ghstarka at 2007-7-10 3:22:39 > top of Java-index,Security,Cryptography...
# 14

> OK, I found the problem.

Sorry but you haven't found the problem. Your solution will not work in general and I don't think there is a problem outside of your code. You are misusing the ByteBuffer. Your code

byte[] output = bbuf.array();

assumes that the array() method returns just the used bytes in the ByteBuffer. It doesn't. It return the backing array used by the byte buffer which in general will be longer than the active length.

Why not just simply usebyte[] output = "the string".getBytes("utf-16");

Adendum : I have just done some tests and on both 1.5 and 1.6 the array() method does not return just the encoded bytes. As expected, it returns at least as many bytes as the encoded string should have but the excess depends on the JRE version.If this ever worked then it is because the buffer space allocation was the same for all the earlier versions of ByteBuffer.

If you have existing values that must match under 1.6 then without extensive frigging then you look to be stuffed. You will have to try to mimic the ByteBuffer space allocation of the earlier version. Best of luck!

Message was edited by:

sabre150

sabre150a at 2007-7-10 3:22:39 > top of Java-index,Security,Cryptography...
# 15

Ah, yes, thanks Sabre150. Although using getBytes("UTF-16") would be the easiest, the OPs code could be corrected by the followingpublic static byte[] convertToBytes(String input)

{

Charset cset = Charset.forName("UTF-16");

ByteBuffer bbuf = cset.encode(input);

byte [] result = new byte[bbuf.remaining()];

bbuf.get(result);

return result;

}

ghstarka at 2007-7-21 18:23:16 > top of Java-index,Security,Cryptography...
# 16

I'm not convinced that byte [] result = new byte[bbuf.remaining()];

is correct. I think one needs

int offset = bbuf.arrayOffset();

int limit = bbuf.limit();

byte[] result = new byte[limit-offset];

because then one gets the same result as byte[] result = getBytes("UTF-16")

I'm not a NIO expert and what I have done with NIO is at a higher level than worrying about the backing array. I would be interested in the views of anyone with more experience of NIO than I have.

sabre150a at 2007-7-21 18:23:16 > top of Java-index,Security,Cryptography...
# 17
I agree. ByteBuffer.remaining() is the amount of unused room in the buffer, not the amount of data in it.
ejpa at 2007-7-21 18:23:16 > top of Java-index,Security,Cryptography...
# 18

I respectfully reassert that I did it correctly. The javadocs for CharsetEncoder.encode() clearly state that the method returns: "A newly-allocated byte buffer containing the result of the encoding operation. The buffer's position will be zero and its limit will follow the last byte written."

There's nothing in the API to guarantee that the returned ByteBuffer will have a backing array.

ghstarka at 2007-7-21 18:23:17 > top of Java-index,Security,Cryptography...
# 19
> I respectfully reassert that I did it correctly. I obviously do not understand NIO well enough because your code does seem to work!
sabre150a at 2007-7-21 18:23:17 > top of Java-index,Security,Cryptography...
# 20
I get it, the ByteBuffer returned by encode() is already flipped.
ejpa at 2007-7-21 18:23:17 > top of Java-index,Security,Cryptography...
# 21
Yep. It takes hubris to disagree with the author of the book I use as a reference, but I got away with it this time :)
ghstarka at 2007-7-21 18:23:17 > top of Java-index,Security,Cryptography...
# 22
You got the hubris and I got the nemesis. Watch out next time ... I've been tripped up by that decoder behaviour before. I always keep my byte buffers ready for *reading*, so that I know where the flips should be, and so does the JDK with AFAIK this thing being about the only exception.
ejpa at 2007-7-21 18:23:17 > top of Java-index,Security,Cryptography...
# 23

In case it's helpful to anyone else I have found that you can simulate the JDK 1.5 behaviour of:

public static byte[] convertToBytes(String input) {

Charset cset = Charset.forName("UTF-16");

ByteBuffer bbuf = cset.encode(input);

return bbuf.array();

}

in JDK 1.6 (and above) using:

public static byte[] convertToBytes(String input) {

Charset cset = Charset.forName("UTF-16");

byte[] data = input.getBytes(cset);

return Arrays.copyOf(data, input.length()-1)*2);

}

I can't guarantee it works for all inputs but I have tested it to some extent.

Thomas_rynnea at 2007-7-21 18:23:17 > top of Java-index,Security,Cryptography...