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.
key = 57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66
message = "amount=100¤cy=EUR"
MAC = HMAC-SHA256( hexDecode(key), message )
result = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
message = "amount=100¤cy=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¤cy=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/
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.
The program can be organized in to three sections:
byte[]
for input string
-> byte[]
byte[]
-> hex string
string
-> byte[]
(thanks @bobince!)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;
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);
}
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:
byte[] innerData
innerKey
and the message
to the byte[] innerData
innerData
and store it in byte[] innerHash
byte[] data
outerKey
and innerHash
, the previously computed hash (from #3), to the data
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;
}
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();
}
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;
}
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:
private static string HashHMACHex(string keyHex, string message)
{
byte[] hash = HashHMAC(HexDecode(keyHex), StringEncode(message));
return HashEncode(hash);
}
private static string HashSHAHex(string innerKeyHex, string outerKeyHex, string message)
{
byte[] hash = HashSHA(HexDecode(innerKeyHex), HexDecode(outerKeyHex), StringEncode(message));
return HashEncode(hash);
}
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
Lastly, just to make sure everything worked, the code altogether can be found at:
http://pastebin.com/xAAuZrJX