Kjensen Kjensen - 24 days ago 4
C# Question

Calculating HMACSHA256 using c# to match payment provider example

For a payment provider, I need to calculate a hash-based message authentication code, using HMAC-SHA256. That is causing me quite a bit of trouble.

The payment provider gives two examples of orrectly calculated authentication code in pseudo-code. All keys are in hex.

Method 1



key = 57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66
message = "amount=100&currency=EUR"
MAC = HMAC-SHA256( hexDecode(key), message )
result = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905


Method 2



message = "amount=100&currency=EUR"
Ki = 61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c17616075060950
Ko = 0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b0a1f6c633a
MAC = SHA256( hexDecode(Ko) + SHA256( hexDecode(Ki) + message ) )
result = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905


I tried to write the code to do this, after doing some research, but I keep coming up with different results.

private static void Main(string[] args)
{
var key = "57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66";
var ki = "61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c17616075060950";
var ko = "0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b0a1f6c633a";
var mm = "amount=100&currency=EUR";

var result1 = CalcHMACSHA256Hash(HexDecode(key), mm);

var result2 = CalcSha256Hash(string.Format("{0}{1}", HexDecode(ko), CalcSha256Hash(HexDecode(ki) + mm)));

Console.WriteLine("Expected: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905");
Console.WriteLine("Actual 1: " + result1);
Console.WriteLine("Actual 2: " + result2);

Console.WriteLine("------------------------------");
Console.ReadKey();

}

private static string HexDecode(string hex)
{
var sb = new StringBuilder();
for (int i = 0; i <= hex.Length - 2; i += 2)
{
sb.Append(Convert.ToString(Convert.ToChar(Int32.Parse(hex.Substring(i, 2), System.Globalization.NumberStyles.HexNumber))));
}
return sb.ToString();
}

private static string CalcHMACSHA256Hash(string plaintext, string salt)
{
string result = "";
var enc = Encoding.Default;
byte[]
baText2BeHashed = enc.GetBytes(plaintext),
baSalt = enc.GetBytes(salt);
System.Security.Cryptography.HMACSHA256 hasher = new HMACSHA256(baSalt);
byte[] baHashedText = hasher.ComputeHash(baText2BeHashed);
result = string.Join("", baHashedText.ToList().Select(b => b.ToString("x2")).ToArray());
return result;
}


public static string CalcSha256Hash(string input)
{
SHA256 sha256 = new SHA256Managed();
byte[] sha256Bytes = Encoding.Default.GetBytes(input);
byte[] cryString = sha256.ComputeHash(sha256Bytes);
string sha256Str = string.Empty;
for (int i = 0; i < cryString.Length; i++)
{
sha256Str += cryString[i].ToString("x2");
}
return sha256Str;
}


And this is the result I get:

Expected: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Actual 1: 421ce16f2036bb9f2a3770c16f01e9220f0232d45580584ca41768fd16c15fe6
Actual 2: 290f14398bf8c0959dfc963e2fd9c377534c6fec1983025d2ab192382f132b92


So with none of the two methods, I can get the result the provider example wants.

What am I missing here? Is it encoding? Is my hexDecode screwed up?

Test tool from payment provider: http://tech.dibs.dk/dibs_api/other_features/hmac_tool/

PHP sample code: http://tech.dibspayment.com/dibs_api/other_features/mac_calculation/

Answer

I've made a complete solution to your issue (since that is probably what you were looking for). It calculates the correct hash using both your method 1 and 2.

Overview

The program can be organized in to three sections:

  1. Hash functions - these are the actual functions that will calculate the hashes using byte[] for input
  2. Encoding helpers - these are used with the the hash hex functions (#3) and help with converting the following:
    • string -> byte[]
    • byte[] -> hex string
    • hex string -> byte[] (thanks @bobince!)
  3. Hash hex functions - these are helper functions so that you can use the hash functions (#1) using hex string as input instead. These use the encoding helpers (#2) to do that.

Code

0. Using Statements

Before you get started, make sure to that you have the following using statements so that you don't get a ton of errors from not including them.

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

1. Hash functions

HMAC-SHA256 (Method 1)

This will calculate the HMAC-SHA256 (your method 1). As you can see, it is much simpler than method 2 but gives the same result.

private static byte[] HashHMAC(byte[] key, byte[] message)
{
    var hash = new HMACSHA256(key);
    return hash.ComputeHash(message);
}

SHA256 (Method 2)

Now to calculate the hash using a ton of SHA hashing (your method 2), it is a little bit more involved. This is basically the same as your pseudo-code without the hex decoding and uses byte[] for input instead. This would look like:

MAC = SHA256( outerKey + SHA256( innerKey + message ) )

Instead of your:

MAC = SHA256( hexDecode(outerKey) + SHA256( hexDecode(innerKey) + message ) )

Where outerKey, innerKey, and message are all byte[]s. Of course, in this case, all the keys have already been decoded from hexadecimal strings but it may as well been byte[]s too.

So the code can be broken down in to these steps:

  1. Create the buffer for the inner data and store it in byte[] innerData
  2. Copy the innerKey and the message to the byte[] innerData
  3. Now compute the SHA256 hash of innerData and store it in byte[] innerHash
  4. For the final and entire hash, create a buffer for it in byte[] data
  5. Copy the outerKey and innerHash, the previously computed hash (from #3), to the data
  6. Compute the final hash of data and store it in result and return it.

To do the byte copying I'm using the Buffer.BlockCopy() function since it apparently faster than some other ways (source). Those steps then can be written in code like this:

private static byte[] HashSHA(byte[] innerKey, byte[] outerKey, byte[] message)
{
    var hash = new SHA256Managed();

    // Compute the hash for the inner data first
    byte[] innerData = new byte[innerKey.Length + message.Length];
    Buffer.BlockCopy(innerKey, 0, innerData, 0, innerKey.Length);
    Buffer.BlockCopy(message, 0, innerData, innerKey.Length, message.Length);
    byte[] innerHash = hash.ComputeHash(innerData);

    // Compute the entire hash
    byte[] data = new byte[outerKey.Length + innerHash.Length];
    Buffer.BlockCopy(outerKey, 0, data, 0, outerKey.Length);
    Buffer.BlockCopy(innerHash, 0, data, outerKey.Length, innerHash.Length);
    byte[] result = hash.ComputeHash(data);

    return result;
}

2. Helper functions

Before we get to the hash hex function, you need a few functions to help with converting between things as said in the overview.

string -> byte[]

The string encoding assumes the text is plain ASCII and seems to work (for now). Though, if you need to encode with fancy symbols, you are probably going to need to use UTF8 instead. If that is the case, then switch out ASCIIEncoding with UTF8Encoding or what ever encoding your using.

private static byte[] StringEncode(string text)
{
    var encoding = new ASCIIEncoding();
    return encoding.GetBytes(text);
}

byte[] -> hex string

All this does is take an array of bytes and turn it to a lower-case hex string. Pretty simple.

private static string HashEncode(byte[] hash)
{
    return BitConverter.ToString(hash).Replace("-", "").ToLower();
}

hex string -> byte[]

Lastly is the conversion of a hex string to a byte array. This came from @bobince's answer so its not mine. Give credit where credit is due.

private static byte[] HexDecode(string hex)
{
    var bytes = new byte[hex.Length / 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        bytes[i] = byte.Parse(hex.Substring(i * 2, 2), NumberStyles.HexNumber);
    }
    return bytes;
}

3. Hash hex functions

As said before, these are the helper functions that work with the hash functions with hex data and strings instead. They are pretty self-explanatory:

Hex hashing for HMAC

private static string HashHMACHex(string keyHex, string message)
{
    byte[] hash = HashHMAC(HexDecode(keyHex), StringEncode(message));
    return HashEncode(hash);
}

Hex hashing for SHA

private static string HashSHAHex(string innerKeyHex, string outerKeyHex, string message)
{
    byte[] hash = HashSHA(HexDecode(innerKeyHex), HexDecode(outerKeyHex), StringEncode(message));
    return HashEncode(hash);
}

4. Console Test

Well to wrap all the functions together, here is a console program that will call the functions to show that they are actually working properly.

static void Main(string[] args)
{
    string message = "amount=100&currency=EUR";
    string expectedHex = "b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905";
    Console.WriteLine("Ref : " + expectedHex);

    // Test out the HMAC hash method
    string key = "57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66";
    string hashHMACHex = HashHMACHex(key, message);
    Console.WriteLine("HMAC: " + hashHMACHex);

    // Test out the SHA hash method
    string innerKey = "61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c17616075060950";
    string outerKey = "0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b0a1f6c633a";
    string hashSHAHex = HashSHAHex(innerKey, outerKey, message);
    Console.WriteLine("SHA : " + hashSHAHex);

    Console.ReadLine();
}

If everything went correctly and it ran without errors, you should get the following output showing that all the hashes are correct (ref is the expected hash):

Ref : b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
HMAC: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
SHA : b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905

Conclusion

Lastly, just to make sure everything worked, the code altogether can be found at:
http://pastebin.com/xAAuZrJX

Comments