Calculating Hmacsha256 Using C# to Match Payment Provider Example

Calculating HMACSHA256 using c# to match payment provider example

Edit: You likely are looking for a quick and simple way to do HMAC-SHA256 and not get into the finer details. The original question asks of those finer details which are explained further below.

I want to perform a HMAC-SHA256 on a byte[] message input

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

I want to perform HMAC-SHA256 but I have a hex string input

In .NET 5 and above, use System.Convert.FromHexString like so, (thanks @proximab). If you're on pre-.NET 5, scroll to "Helper functions" which has alternative solutions.

using System;
using System.Security.Cryptography;
...
private static byte[] HashHMACHex(string keyHex, string messageHex)
{
var key = Convert.FromHexString(hexKey);
var message = Convert.FromHexString(messageHex);
var hash = new HMACSHA256(key);
return hash.ComputeHash(message);
}

I'm using a strange API service that sort of does HMAC, but it's something custom

Continue reading. You likely want to use "Method 2" below as a reference point and adjust it to however your service wants you to implement HMAC for message anti-tampering.



How HMAC-SHA256 Works (should you need to know how...)

Here we will compute an HMAC-SHA256 manually (this answers "Method 2" from the original question).

Assume outerKey, innerKey, and message are already byte arrays, we perform the following:

Notation: Assume A + B concatenates byte array A and B. You may
alternatively see A || B notation used in more academic settings.

HMAC = SHA256( outerKey + SHA256( innerKey + message  )   )
. . `------------------´ . .
\ \ `innerData` / /
\ `------------------------´ /
\ `innerHash` /
`----------------------------------´
`data`

So the code can be broken down into these steps (using the above as a guide):

  1. Create an empty buffer byte[] innerData the length of innerKey.Length + message.Length (again assuming byte arrays)
  2. Copy the innerKey and the message into the byte[] innerData
  3. Compute SHA256 of innerData and store it in byte[] innerHash
  4. Create an empty buffer byte[] data the length of outerKey.Length + innerHash.Length
  5. Copy the outerKey and innerHash (from step #3)
  6. Compute the final hash of data and store it in byte[] 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).

n.b. There is likely (read: most certainly) a better way to do this using the the new ReadOnlySpan<T> API.

We can translate those steps into the following:

using System;
using System.Security.Cryptography;
...
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;
}

Helper functions

string -> byte[]

You have plain ASCII or UTF8 text, but need it to be a byte[].

Use ASCIIEncoding or UTF8Encoding or whichever exotic encoding you're using.

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

You have a byte[], but you need it to be a hex string.

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

You have a hex string, but you need it to be a byte[]`.

.NET 5 and above

private static byte[] HexDecode(string hex) =>
System.Convert.FromHexString(hex);

Before .NET 5 (thanks @bobince)

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;
}

n.b. If you need a performance tuned version on .NET Framework 4.x, you can alternatively backport the .NET 5+ version (by replacing ReadOnlySpan<byte> with byte[]). It uses proper lookup tables and conscious about hot-code paths. You can reference the .NET 5 (MIT licensed) System.Convert code on Github.


For completeness, here are the final methods answering the question using both "Method 1" and "Method 2"

"Method 1" (using .NET libraries)

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

"Method 2" (manually computed)

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

We can perform a quick sanity check with a console app:

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

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

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

You should have all the hashes line up correctly:

Expected: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Method 1: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Method 2: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905

The original code for this answer can be accessed at:
http://pastebin.com/xAAuZrJX

How to compute a HMAC SHA256 in c#

You are converting your key directly to bytes rather than interpreting it as a hex string. Using a string-to-bytes function from here:

public static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}

Change your code to this:

Byte[] key_bytes = StringToByteArray(key);

How to implement hmacSHA256 with Javascipt using CryptoJS

The reference implementation CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(mess), key)) generates an HMAC using the SHA256 digest. Thereby the message is hex decoded and the key UTF8 encoded. The UTF8 encoding results in a key of 64 bytes, which is exactly the block size of SHA256. Therefore neither padding with 0x00 values to 64 bytes nor hashing with SHA256 is necessary.

In your code I mean to see the following problems: Nowhere are the different encodings taken into account, which are crucial for the result. Also, it seems to me that the block size of SHA256 has not been considered properly or at all. And as for the XOR operation, it can be easily done with CryptoJS, no other tool is needed.

The calculation of HMAC can be performed in three steps:

  1. determination of (K xor opad) and (K xor ipad).
  2. determination of P = H( (K xor ipad) || M )
  3. determination of HMAC = H( (K xor opad) || P ), which corresponds to the final result.

All steps can be done with CryptoJS. Thereby crypto-js/src/hmac.js is a helpful blueprint. Note that CryptoJS works internally with WordArrays. Regarding the XOR operation, this means that words are processed, i.e. iterated over 64/4 = 16 words.

A possible implementation is:

// Key is UTF8 encoded 64 bytes -> no padding / no hashing required
const key = "e9058ab198f6908f702111b0c0fb5b36f99d00554521886c40e2891b349dc7a1"
const mess = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";

// Step 1: determine K xor opad (oKeyWA), K xor ipad (iKeyWA)
//
var hasherBlockSizeBytes = 64; // in bytes
var hasherBlockSize = hasherBlockSizeBytes/4; // in words
var keyWA = CryptoJS.enc.Utf8.parse(key);
var oKeyWA = keyWA.clone();
var iKeyWA = keyWA.clone();
var oKeyWords = oKeyWA.words;
var iKeyWords = iKeyWA.words;
for (var i = 0; i < hasherBlockSize; i++) {
oKeyWords[i] ^= 0x5c5c5c5c;
iKeyWords[i] ^= 0x36363636;
}

// Step 2: determine P = H( (K xor ipad) || M )
//
var messWA = CryptoJS.enc.Hex.parse(mess);
var iKeyMessWA = iKeyWA.concat(messWA);
var iKeyMessHashWA = CryptoJS.SHA256(iKeyMessWA);

// Step 3: determine HMAC = H ( (K xor opad) || P)
//
var oKeyiKeyMessHashWA = oKeyWA.concat(iKeyMessHashWA);
var hmacWA = CryptoJS.SHA256(oKeyiKeyMessHashWA);
document.getElementById("hmac").innerHTML = hmacWA.toString(CryptoJS.enc.Hex);

// Comparison with built-in function
var hmacDirectWA = CryptoJS.HmacSHA256(messWA, keyWA);
document.getElementById("hmacDir").innerHTML = hmacDirectWA.toString(CryptoJS.enc.Hex);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js">
</script>
<p style="font-family:'Courier New', monospace;" id="hmac"></p>
<p style="font-family:'Courier New', monospace;" id="hmacDir"></p>

Trying to replicate a Hash example in C#

For some reason you are converting hash to base-64 string, then you convert each character of that string to int and that you convert to hex. All that is not needed and not described in "documentation". Instead, do like this:

var hashBin = hash.ComputeHash(Encoding.UTF8.GetBytes(data64));
var hashHex = BitConverter.ToString(hashBin).Replace("-", "").ToLowerInvariant();
Console.WriteLine(hashHex);

Adyen, Unable to get correct SHA-256 encryption

There is a problem with your signing string. You need to replace "\" with "\" and ":" with ":" in all your vallues.

I also suggest to use the code from this sample on GIT to generate create your encrypted signature.

Using the code below I get the same signature as on the test page provided by Adyen.

 // Computes the Base64 encoded signature using the HMAC algorithm with the HMACSHA256 hashing function.
string CalculateHMAC(string hmacKey, string signingstring)
{
byte[] key = PackH(hmacKey);
byte[] data = Encoding.UTF8.GetBytes(signingstring);

try
{
using (HMACSHA256 hmac = new HMACSHA256(key))
{
// Compute the hmac on input data bytes
byte[] rawHmac = hmac.ComputeHash(data);

// Base64-encode the hmac
return Convert.ToBase64String(rawHmac);
}
}
catch (Exception e)
{
throw new Exception("Failed to generate HMAC : " + e.Message);
}
}

byte[] PackH(string hex)
{
if ((hex.Length % 2) == 1)
{
hex += '0';
}

byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < hex.Length; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}

return bytes;
}

Try to use only a limited amound of fields and see if you get any results.
I used the fields below (also take their order into account!)

currencyCode:merchantAccount:merchantReference:paymentAmount:sessionValidity:shipBeforeDate:shopperLocale:skinCode


Related Topics



Leave a reply



Submit