12

This unit test is failing:

    public void testDigest() throws NoSuchAlgorithmException {
    String hashExpected = "150a14ed5bea6cc731cf86c41566ac427a8db48ef1b9fd626664b3bfbb99071fa4c922f33dde38719b8c8354e2b7ab9d77e0e67fc12843920a712e73d558e197";
    MessageDigest md = new MessageDigest();
    String hashActual = new String(md.digest("hi"));
    Assert.assertEquals(hashExpected, hashActual);
}

Below is my implementation of my MessageDigest class:


import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.digests.SHA512Digest; import org.bouncycastle.crypto.io.DigestInputStream; import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class MessageDigest { private Digest messageDigest;

public MessageDigest() throws NoSuchAlgorithmException {
    Security.addProvider(new BouncyCastleProvider());
    messageDigest = new SHA512Digest();
}

public byte[] digest(String message) {
    byte[] retValue = new byte[messageDigest.getDigestSize()];
    messageDigest.update(message.getBytes(), 0, message.length());
    messageDigest.doFinal(retValue, 0);
    return retValue;
}

}

The test fails with the following reason:


junit.framework.ComparisonFailure: expected:<150a14ed5bea6cc731cf86c41566ac427a8db48ef1b9fd626664b3bfbb99071fa4c922f33dde38719b8c8354e2b7ab9d77e0e67fc12843920a712e73d558e197> but was:<
í[êlÇ1φÄf¬Bz�´Žñ¹ýbfd³¿»™¤É"ó=Þ8q›ŒƒTâ·«�wàæÁ(C’
q.sÕXá

I have a feeling I'm not using the right encoding scheme when I convert my byte[] digest into a string. Any help would be appreciated.

Lee Warner
  • 2,543
  • 4
  • 30
  • 45

4 Answers4

34

The value you're expecting is a Hex-encoded value. You're creating a String based on the raw bytes, which won't work.

You should use the standard Java Crypto API whenever possible instead of BouncyCastle specific APIs.

Try the following (the Hex library comes from commons-codec):

Security.addProvider(new BouncyCastleProvider());

String data = "hello world";

MessageDigest mda = MessageDigest.getInstance("SHA-512", "BC");
byte [] digesta = mda.digest(data.getBytes());

MessageDigest mdb = MessageDigest.getInstance("SHA-512", "BC");
byte [] digestb = mdb.digest(data.getBytes());

System.out.println(MessageDigest.isEqual(digesta, digestb));

System.out.println(Hex.encodeHex(digesta));
Kevin
  • 30,111
  • 9
  • 76
  • 83
  • 1
    +1 I like your more-comprehensive answer. Question: when/why would one use MessageDigest.isEqual over Arrays.equals? – C. K. Young Feb 05 '10 at 16:14
  • 1
    They're functionally equivalent. MessageDigest#isEqual() provides a little more semantic meaning but that is debatable. – Kevin Feb 05 '10 at 16:20
  • 2
    They are not entirely equivalent. The Arrays.equals methods consider two null references to be equal, while the MessageDigest.isEqual method would throw a NullPointerException. – jarnbjo Feb 05 '10 at 16:31
  • 1
    Commons Codec is not necessary for the hex conversion. Try `System.out.println(new BigInteger(1,m.digest(new byte[]{0x00})).toString(16));` – Janus Troelsen Nov 14 '12 at 13:04
  • 1
    Regarding MessageDigest#isEqual(): It is time-constant, i.e. it takes the same time to calculate, and does not return false as soon as it sees a difference. That way, you cannot time the function call to check if you have a hash which has some common prefix with the required hash. – JesperSM Nov 27 '13 at 12:37
  • Thank you. That is the best explanation of "time-constant" I have seen. I didn't quite get it the first time I read it (albeit I read it really briefly). – Kevin M Jan 31 '17 at 16:36
22

Just an addition to Kevin's answer: Since Java 5, you can use String.format("%0128x", new BigInteger(1, digesta)) instead of commons-codec to format the byte array as a 128 digit hex encoded number with leading zeros.

jarnbjo
  • 33,923
  • 7
  • 70
  • 94
  • Thanks for this, but one question. Other than not needing to pull in another library, is there any benefit to using this method over the one specified in the commons-codec answer? – jtbradle Jun 26 '14 at 21:11
  • @jtbradle: Probably not. – jarnbjo Oct 06 '14 at 16:45
3

Yes, you need to turn your byte array into a hex string. :-) Look into Apache Commons Codec, especially the Hex class.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
2

Since BouncyCastle 1.49 there is a handful toHexString method in the Hex class. For example:

Hex.toHexString(digest);

will return you the hash digest as a Java String in a hexadecimal format.

For reference see BouncyCastle javadoc or grepcode.

Diego
  • 5,326
  • 1
  • 35
  • 32