0

I have a website that connects to another external website and passes ID numbers, the page I am working on and that generates the links is written in c#, whereas the target website is written in Java and I am experiences some mismatches

I am trying to use the same approach in C# as the code in Java to encrypt the ID's, the code looks like this:

public static String shaHash(String input) 
throws NoSuchAlgorithmException {
  if (input == null) {
    return null;
  }
  MessageDigest sha = MessageDigest.getInstance("SHA");
  try {
    sha.update(input.getBytes("UTF8"));
    BigInteger hash = new BigInteger(1, sha.digest());
    return hash.toString(16);
  } catch (UnsupportedEncodingException e) {}
  return null;
}

And the C# code looks like

StringBuilder convertedId = new StringBuilder();
            var idToByte = Encoding.ASCII.GetBytes(id);
            using (SHA1 sha1 = SHA1.Create())
            {
                var bytes = sha1.ComputeHash(idToByte);
                for (int i = 0; i < bytes.Length; i++)
                {
                    convertedId.Append(bytes[i].ToString("x2"));
                }
            }
            return convertedId.ToString();

But the return values are different as the C# code seems like it is adding an extra 0 at the beginning:

Java: b83caf2f739209c42a8d02df0f6d4794f288703, and C#: 0b83caf2f739209c42a8d02df0f6d4794f288703

What would it be the problem? I have tried to change the encoding in the C# code for UTF8 but still doesn't work.

Any ideas guys?

Many thanks in advance

cacharry
  • 99
  • 2
  • 12
  • I'd argue that it is the Java code that has an issue, not the C# code. Hex encoding of bytes (which is exactly what you are doing) always has an even string length. That is, the leading 0 is expected. – Luke Joshua Park May 15 '19 at 20:51

3 Answers3

3

The question is, do you want the extra 0 or not?

Hex output is usually done as 2 hex digits per byte, so you get an even number of digits, i.e. the leading 0 is usually what you want.

Java's BigInteger doesn't do that, because to it, the output is a base-16 number, not a hex-encoding of bytes.

So, rather than removing the extra 0 in C#, you should add the leading 0 in Java.

You can keep using BigInteger but prepend a 0 if the resulting string has an odd length (e.g. s.length() % 2 != 0), or use some other means of hex-encoding the byte[].
See e.g. How to convert a byte array to a hex string in Java?

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thanks Andreas! it makes total sense however I can't change the Java code so I couldn't implement your suggestion, but you did help me to understand the problem :) – cacharry May 16 '19 at 09:09
1

Your conversion loop takes no account of the first byte being less than 16. Try something like:

StringBuilder convertedId = new StringBuilder();
            var idToByte = Encoding.ASCII.GetBytes(id);
            using (SHA1 sha1 = SHA1.Create())
            {
                var bytes = sha1.ComputeHash(idToByte);
                for (int i = 0; i < bytes.Length; i++)
                {
                    if (i == 0 && bytes[i] < 16)
                        convertedId.Append(bytes[i].ToString("x1"));
                    else
                        convertedId.Append(bytes[i].ToString("x2"));
                }
            }
            return convertedId.ToString();

Edit: Considering Peter O's answer below, the following is a better answer:

         var idToByte = Encoding.ASCII.GetBytes(id);
         using (var sha1 = SHA1.Create())
         {
            var bytes = sha1.ComputeHash(idToByte);
            return new BigInteger(bytes.Reverse().ToArray()).ToString("X");
         }

Not that:

1) You'll need to add a reference to System.Numerics

2) BigInteger's constructor expects data in Little Endian order, hence the Reverse statement.

Steve Todd
  • 1,250
  • 6
  • 13
  • Thank you so much Steve, your answer solved the problem, as @Andreas pointed out in his answer, the Java code is using BigInteger which output is a base-16 number not hex-encoding of bytes (I didn't know about it). As I am not able to change that code, I adjusted mine to avoid the extra 0, and it works very well, thanks a lot for your time! – cacharry May 16 '19 at 09:04
1

The answers given before I started this one are not ideal. Note that the Java code represents hashes as BigIntegers, which are essentially numbers, not necessarily byte arrays. BigInteger's toString(16) prints out that number using as few base-16 digits as necessary without leading zeros. As a result, the existing answers are problematic if that number happens to be less than 2152. A better solution to match the Java code would be to strip all leading zeros from the base-16 string in the C# code, not just the first (unless all the bytes in the hash code are 0).

Peter O.
  • 32,158
  • 14
  • 82
  • 96