9

I have the following code in Java:

byte[] secretKey = secretAccessKey.getBytes("UTF-8");
SecretKeySpec signingKey = new SecretKeySpec(secretKey, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] bytes = data.getBytes("UTF-8");
byte[] rawHmac = mac.doFinal(bytes);
String result = javax.xml.bind.DatatypeConverter.printBase64Binary(rawHmac);

and the following code in C#:

UTF8Encoding enc = new UTF8Encoding();
byte[] secretKey = enc.GetBytes(secretAccessKey);
HMACSHA256 hmac = new HMACSHA256(secretKey);
hmac.Initialize();
byte[] bytes = enc.GetBytes(data);
byte[] rawHmac = hmac.ComputeHash(bytes);
string result = Convert.ToBase64String(rawHmac);

The byte arrays "secretKey" and "bytes" are equivalent but the byte array "rawHmac" is different, and the string "result" is different. Can anyone see why?

Greg Tarr
  • 496
  • 3
  • 5
  • 19

2 Answers2

18

Don't do this:

byte[] bytes = data.getBytes();

That will use the platform default encoding to convert a string to a byte array. That can vary between platform, whereas you want something repeatable. I would suggest UTF-8:

byte[] bytes = data.getBytes("UTF-8");

(Do the same for the key, of course.)

You should then use the same encoding in your C# - not ASCII, unless you really want to not handle non-ASCII characters.

byte[] bytes = Encoding.UTF8.GetBytes(data);

It's also not clear how you're comparing the results afterwards - don't forget that byte is signed in Java, but unsigned in C#. It's probably simplest to convert the hash to hex or base64 for comparison purposes.

EDIT: I strongly suspect the last part was the problem - comparing the results.

Here are two short but complete programs (using the iharder.net base64 converter in Java) which produce the same base64 output:

Java:

import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class Test {
    public static void main (String[] args) throws Exception {
        String secretAccessKey = "mykey";
        String data = "my data";
        byte[] secretKey = secretAccessKey.getBytes();
        SecretKeySpec signingKey = new SecretKeySpec(secretKey, "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(signingKey);
        byte[] bytes = data.getBytes();
        byte[] rawHmac = mac.doFinal(bytes);
        System.out.println(Base64.encodeBytes(rawHmac));
    }
}

C#:

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

class Test
{
    static void Main()
    {
        String secretAccessKey = "mykey";
        String data = "my data";
        byte[] secretKey = Encoding.UTF8.GetBytes(secretAccessKey);
        HMACSHA256 hmac = new HMACSHA256(secretKey);
        hmac.Initialize();
        byte[] bytes = Encoding.UTF8.GetBytes(data);
        byte[] rawHmac = hmac.ComputeHash(bytes);
        Console.WriteLine(Convert.ToBase64String(rawHmac));
    }
}

Output from both:

ivEyFpkagEoghGnTw/LmfhDOsiNbcnEON50mFGzW9/w=
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks, but as I said the bytes[] contains the same numbers in both languages. So whilst your comments are helpful, they do not fix the issue – Greg Tarr Nov 05 '12 at 17:47
  • 1
    @GregT: Well they at least fix *an* issue. See my final paragraph too - you haven't given us any indication of how you're comparing these. If you could edit your question to provide two short but *complete* programs which demonstrate the problem, that would make life easier. – Jon Skeet Nov 05 '12 at 17:50
  • Thanks, done that now. I wasn't aware of the difference between bytes. – Greg Tarr Nov 05 '12 at 17:54
  • @GregT: I've edited my answer to show short but complete programs which *do* give the same answer. Can you edit your question in a way which shows *different* answers? (The code you've given still isn't complete.) – Jon Skeet Nov 05 '12 at 17:57
  • Can you get Base64 in java using the standard API? I am not able to edit the Java code that this example is from – Greg Tarr Nov 05 '12 at 17:58
  • @GregT: I get the same result using `javax.xml.bind.DatatypeConverter` as well. Have you tried my sample code? It really does sound like this is probably a comparison problem... – Jon Skeet Nov 05 '12 at 18:00
  • Yeah it is working with "my data" and "mykey". It must be something in the data/keys I am passing in – Greg Tarr Nov 05 '12 at 18:04
  • @GregT: Well if anything's non-ASCII, that would certainly do it as per my earlier points. Also check the length of each string - if you have some non-printable characters at the end of one but not the other, that will affect things. – Jon Skeet Nov 05 '12 at 18:05
  • It is all ASCII, and I have made the modifications you suggested. Got some new line \n characters in though – Greg Tarr Nov 05 '12 at 18:08
  • 1
    @GregT: Well, at this point you should be able to take the two standalone programs I've created, and provide a key/data pair which shows the difference. – Jon Skeet Nov 05 '12 at 18:40
1

This was a non-question, as demonstrated, the hashes are always the same.

The problem in my case was unrelated, the fact that Java uppercases percent encoding on UrlEncoder but .NET doesn't.

Goes to show how important it is to test in isolation!

Greg Tarr
  • 496
  • 3
  • 5
  • 19