.NET Framework - C# MD5 Hash Does Not Match Hash Generated From Java

Asked By Ratfish on 05-Jun-11 01:34 AM
I have got a problem where a MD5 Hash function in .NET generates a
different hash than a similar routine in Java/Android.  The Android
hash is calculated as "J0t=EF=BF=BD=EF=BF=BDj#=DF=B8=EF=BF=BDbq-=EF=BF=BD",=
while the .NET hash is
calculated as "SjB0j+xqI9+4hxticS0Cjw=3D=3D".  Is this due to a character
set issue?  Unicode versus non-unicode?

The code is below.  Any suggestions would be greatly appreciated.

Aaron

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
Here is the .NET code:

public static string EncodeText(byte[] key, string sText, Encoding
encoding)
{
HMACMD5 hmacMD5 =3D new HMACMD5(key);
byte[] textBytes =3D encoding.GetBytes(sText);
byte[] encodedTextBytes =3D hmacMD5.ComputeHash(textBytes);
string sEncodedText =3D Convert.ToBase64String(encodedTextBytes);
return sEncodedText;
}

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
Here is the Android code:

public class HMACMD5 {

private final String HMAC_MD5_NAME =3D "HmacMD5";

private SecretKeySpec sk;
private Mac mac;

public HMACMD5(byte[] key) throws GeneralSecurityException {
init(key);
}

public HMACMD5(String key) throws GeneralSecurityException {
init(EncodingUtils.getAsciiBytes(key));
}

private void init(byte[] key) throws GeneralSecurityException {
sk =3D new SecretKeySpec(key, HMAC_MD5_NAME);
mac =3D Mac.getInstance(HMAC_MD5_NAME);
mac.init(sk);
}

public byte[] ComputeHash(byte[] data) {
return mac.doFinal(data);
}

public byte[] ComputeHash(String data) {
return ComputeHash(EncodingUtils.getAsciiBytes(data));
}
}

public String encodeText(String sKey, String sSrc) throws Exception {
HMACMD5 hmacMD5 =3D new HMACMD5(sKey);
byte[] textBytes =3D EncodingUtils.getBytes(sSrc, "UTF-8");
byte[] encodedTextBytes =3D hmacMD5.ComputeHash(textBytes);
String sEncodedText =3D EncodingUtils.getString(encodedTextBytes,
return sEncodedText;
}




Peter Duniho replied to Ratfish on 05-Jun-11 02:12 AM
Without even looking at the code (which was not all that useful anyway,
since you completely omitted the .NET implementation of the HMACMD5
class), from the above I can tell you that you are not comparing apples
to apples.  The .NET hash is being represented as Base64.  But the
string you say is the output of your Java is definitely _not_ Base64.

Of course, neither are the actual output of the hash, which is simply
binary data.  The same binary data will encode in Base64 as the same
characters, so of course if you get the Java output as Base64 you can
compare strings.  But you may find it simpler to just compare the
original binary output from the hash algorithms.

If that is not enough to get you back on track, then you need to keep in
mind that without showing a concise-but-complete code example, including
the exact data that you are using for the input in the first place,
there is no practical way for anyone to help you with the problem.

Pete
rossum replied to Ratfish on 05-Jun-11 05:14 AM
You are not calculating a Hash, you are calculating an HMAC.  HMACs
have keys, which Hashes do not.

1 Check that the two keys are identical at the binary level.  Java can
do some strange things with its byte type due to it being signed so
check your Android code carefully for any possible cases of sign
extension.

2 Your Android code looks like it returns Base64, but "J0t??j#??bq-?"
is not Base64.  Are you comparing like with like?

rossum
Arne Vajhøj replied to Ratfish on 05-Jun-11 09:23 PM
Difficult to say based on just some of the code.

But I can see two potential sources for errors:
1) you use byte[] as key for .NET and String being converted to
byte[] as US-ASCII for Java
2) the .NET code gets encoding of data as input while the Java code
treat data as US-ASCII

The code below are:
- complete
- generates same result "QkY+XkbFsIqyaQp5jPuLjw==" on .NET 4.0 and Java
SE 1.6

I have modified the Java code slightly to:
- fix the above
- avoid using the StringUtil class [it should be easy to convert the
code back to use StringUtil]

Arne

====

using System;
using System.Text;
using System.Security.Cryptography;

namespace E
{
public class Program
{
public static string EncodeText(byte[] key, string sText, Encoding
encoding)
{
HMACMD5 hmacMD5 = new HMACMD5(key);
byte[] textBytes = encoding.GetBytes(sText);
byte[] encodedTextBytes = hmacMD5.ComputeHash(textBytes);
string sEncodedText = Convert.ToBase64String(encodedTextBytes);
return sEncodedText;
}
public static void Main(string[] args)
{
Console.WriteLine(EncodeText(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 },
Console.ReadKey();
}
}
}

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.mail.MessagingException;
import javax.mail.internet.MimeUtility;

public class HMACFun {
public static String encodeText(byte[] key, String sSrc, String
encoding) throws Exception {
HMACMD5 hmacMD5 = new HMACMD5(key);
byte[] textBytes = sSrc.getBytes(encoding);
byte[] encodedTextBytes = hmacMD5.ComputeHash(textBytes);
String sEncodedText = B64.b64encode(encodedTextBytes);
return sEncodedText;
}
public static void main(String[] args) throws Exception {
System.out.println(encodeText(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 },
}
}

class HMACMD5 {
private final String HMAC_MD5_NAME = "HmacMD5";
private SecretKeySpec sk;
private Mac mac;
public HMACMD5(byte[] key) throws GeneralSecurityException {
init(key);
}
public HMACMD5(String key, String encoding) throws
GeneralSecurityException, UnsupportedEncodingException {
init(key.getBytes(encoding));
}
private void init(byte[] key) throws GeneralSecurityException {
sk = new SecretKeySpec(key, HMAC_MD5_NAME);
mac = Mac.getInstance(HMAC_MD5_NAME);
mac.init(sk);
}
public byte[] ComputeHash(byte[] data) {
return mac.doFinal(data);
}
public byte[] ComputeHash(String data, String encoding) throws
UnsupportedEncodingException {
return ComputeHash(data.getBytes(encoding));
}
}

class B64 {
public static String b64encode(byte[] b) throws MessagingException,
IOException  {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream b64os = MimeUtility.encode(baos, "base64");
b64os.write(b);
b64os.close();
return new String(baos.toByteArray());
}
public static byte[] b64decode(String s) throws
MessagingException, IOException  {
ByteArrayInputStream bais = new ByteArrayInputStream(s.getBytes());
InputStream b64is = MimeUtility.decode(bais, "Base64");
byte[] tmp = new byte[s.length()];
int n = b64is.read(tmp);
byte[] res = new byte[n];
System.arraycopy(tmp, 0, res, 0, n);
return res;
}
}
Ratfish replied to Arne Vajhøj on 06-Jun-11 01:31 AM
d
aracter
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
);
tBytes);
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
n {
n {
tion {
;
es(data));
);
);
xtBytes,
va
ing EncodeText(byte[] key, string sText, Encoding
MD5 hmacMD5 =3D new HMACMD5(key);
[] textBytes =3D encoding.GetBytes(sText);
[] encodedTextBytes =3D hmacMD5.ComputeHash(textBytes);
ng sEncodedText =3D Convert.ToBase64String(encodedTextBytes);
rn sEncodedText;
d Main(string[] args)
=A0 =C2=A0 Console.WriteLine(EncodeText(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8=
},
=A0 =C2=A0 Console.ReadKey();
tring sSrc, String
key);
es(encoding);
MD5.ComputeHash(textBytes);
ncode(encodedTextBytes);
Exception {
n(encodeText(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 },
D5";
rityException {
hrows
(encoding));
ecurityException {
KeySpec(key, HMAC_MD5_NAME);
stance(HMAC_MD5_NAME);
l(data);
encoding) throws
h(data.getBytes(encoding));
gingException,
ArrayOutputStream();
de(baos, "base64");
rrayInputStream(s.getBytes());
e(bais, "Base64");


Thanks Arne for the detailed response.  And the others who set me
straight.  I understand the suggested changes, but am still getting
strange results on the Android side:  I have included the full example
projects I am using for .NET and Android, using Arne's test case, which
should produce "QkY+XkbFsIqyaQp5jPuLjw=3D=3D" on the Java side, but
does not.  I did not implement Arne's B64 class yet, but that does not
appear to be the problem.  The bytes returned from the computeHash
call do not match the bytes returned from the equivalent .NET call.
Anyone know what is going on with the Android project.  Why does not it
yield the correct result?

Aaron

Android Project
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
package org.example.hmacmd5;

import org.apache.http.util.EncodingUtils;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class Test extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
TextView tv =3D new TextView(this);
try {
tv.setText(encodeText(new byte[] { 1, 2, 3, 4, 5, 6, 7,
8 }, "This is a test!", "UTF-8"));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
setContentView(tv);
}

public String encodeText(byte[] key, String sSrc, String sEncoding)
throws Exception {
HMACMD5 hmacMD5 =3D new HMACMD5(key);
byte[] textBytes =3D EncodingUtils.getBytes(sSrc, sEncoding);
byte[] encodedTextBytes =3D hmacMD5.ComputeHash(textBytes);
String sEncodedText =3D EncodingUtils.getString(encodedTextBytes,
return sEncodedText;
}
}

package org.example.hmacmd5;

import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class HMACMD5 {
private final String HMAC_MD5_NAME =3D "HmacMD5";
private SecretKeySpec sk;
private Mac mac;

public HMACMD5(byte[] key) throws GeneralSecurityException {
init(key);
}

public HMACMD5(String key, String encoding) throws
GeneralSecurityException, UnsupportedEncodingException {
init(key.getBytes(encoding));
}

private void init(byte[] key) throws GeneralSecurityException {
sk =3D new SecretKeySpec(key, HMAC_MD5_NAME);
mac =3D Mac.getInstance(HMAC_MD5_NAME);
mac.init(sk);
}

public byte[] ComputeHash(byte[] data) {
return mac.doFinal(data);
}

public byte[] ComputeHash(String data, String encoding) throws
UnsupportedEncodingException {
return ComputeHash(data.getBytes(encoding));
}
}

.NET code
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
using System;
using System.Text;
using System.Security.Cryptography;

namespace HmacMD5Test
{
class Program
{
public static string EncodeText(byte[] key, string sText,
Encoding encoding)
{
HMACMD5 hmacMD5 =3D new HMACMD5(key);
byte[] textBytes =3D encoding.GetBytes(sText);
byte[] encodedTextBytes =3D hmacMD5.ComputeHash(textBytes);
string sEncodedText =3D
Convert.ToBase64String(encodedTextBytes);
return sEncodedText;
}

static void Main(string[] args)
{
Console.WriteLine(EncodeText(new byte[] { 1, 2, 3, 4, 5, 6,
7, 8 }, "This is a test!", Encoding.UTF8));
Console.ReadKey();
}
}
}
Ratfish replied to Ratfish on 06-Jun-11 02:03 AM
oid
character
.
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
es);
extBytes);
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
ion {
ion {
eption {
E);
ytes(data));
8");
es);
TextBytes,
Java
tring EncodeText(byte[] key, string sText, Encoding
ACMD5 hmacMD5 =3D new HMACMD5(key);
te[] textBytes =3D encoding.GetBytes(sText);
te[] encodedTextBytes =3D hmacMD5.ComputeHash(textBytes);
ring sEncodedText =3D Convert.ToBase64String(encodedTextBytes);
turn sEncodedText;
oid Main(string[] args)
=C2=A0 =C2=A0 Console.WriteLine(EncodeText(new byte[] { 1, 2, 3, 4, 5, 6, 7=
, 8 },
=C2=A0 =C2=A0 Console.ReadKey();
String sSrc, String
5(key);
ytes(encoding);
acMD5.ComputeHash(textBytes);
4encode(encodedTextBytes);
ws Exception {
tln(encodeText(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 },
cMD5";
curityException {
throws
es(encoding));
lSecurityException {
etKeySpec(key, HMAC_MD5_NAME);
Instance(HMAC_MD5_NAME);
nal(data);
ng encoding) throws
ash(data.getBytes(encoding));
sagingException,
teArrayOutputStream();
code(baos, "base64");
;
eArrayInputStream(s.getBytes());
ode(bais, "Base64");
t
t
{ 1, 2, 3, 4, 5, 6, 7,
ck
coding)
ncoding);
Bytes);
dedTextBytes,
n {
ws
Text,

Realized that Java has signed and unsigned bytes.  This got me looking
at the Base64 encoding, which was not working.  Found a working Android
Base64 class that fixed the problem.  Thanks for all the help.

Aaron