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:
- Hash functions - these are the actual functions that will calculate the hashes using
byte[]
for input
- 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!)
- 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:
- Create the buffer for the inner data and store it in
byte[] innerData
- Copy the
innerKey
and the message
to the byte[] innerData
- Now compute the SHA256 hash of
innerData
and store it in byte[] innerHash
- For the final and entire hash, create a buffer for it in
byte[] data
- Copy the
outerKey
and innerHash
, the previously computed hash (from #3), to the data
- 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¤cy=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