Calculating HMACSHA256 using c# to match payment provider example

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 into 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 whatever encoding you're 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 it's not mine. Giving 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


Here's a string extension method for getting a fairly standard HMAC SHA 256 token for a given string:

usage:

myMessageString.HmacSha256Digest(mySecret)

string extension method:

public static string HmacSha256Digest(this string message, string secret)
{
    ASCIIEncoding encoding = new ASCIIEncoding();
    byte[] keyBytes = encoding.GetBytes(secret);
    byte[] messageBytes = encoding.GetBytes(message);
    System.Security.Cryptography.HMACSHA256 cryptographer = new System.Security.Cryptography.HMACSHA256(keyBytes);

    byte[] bytes = cryptographer.ComputeHash(messageBytes);

    return BitConverter.ToString(bytes).Replace("-", "").ToLower();
}

You can use this method for HMACSHA256.

string key = "your key";
string message = "your message";
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(key);

HMACSHA256 hmacsha256 = new HMACSHA256(keyByte);

byte[] messageBytes = encoding.GetBytes(message);
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
return ByteToString(hashmessage);

Here is the ByteToString method:

public static string ByteToString(byte[] buff)
    {
        string sbinary = "";

        for (int i = 0; i < buff.Length; i++)
        {
            sbinary += buff[i].ToString("X2"); // hex format
        }
        return (sbinary);
    }