Convert C# HMAC SHA256 to PHP - c#

I've a C# method which I have to convert to PHP. I tried several things, but I still get different results. Unfortunately I cannot change anything on the C# application. It has to be as it is. Maybe one of you could help?
C#:
static public void Main ()
{
string StringToSign = "test";
string Key = "123456";
//Calculate Signature
string Signature = CalculateSignature(StringToSign, Key);
Console.WriteLine ("StringToSign: " + StringToSign);
Console.WriteLine ("Key: " + Key);
Console.WriteLine ("Signature Caculated: " + Signature + "\r\n");
}
static private string CalculateSignature(String StringToSign, String Key)
{
Encoding enc = Encoding.GetEncoding(65001);
byte[] KeyHex = StringHexValuesToByteArray(Key);
byte[] StringToSign_byte = enc.GetBytes(StringToSign);
//Check Signature
HMACSHA256 hmac = new HMACSHA256(KeyHex);
byte[] hashValue = hmac.ComputeHash(StringToSign_byte);
return BitConverter.ToString(hashValue).Replace("-", "");
}
static public byte[] StringHexValuesToByteArray(string str)
{
if (str.Length % 2 != 0)
return null;
string s = string.Empty;
byte[] ret = new byte[str.Length / 2];
for (int run = 0; run < str.Length / 2; run++)
{
s = str.Substring(run * 2, 2);
ret[run] = Convert.ToByte(s, 16);
}
return ret;
}
PHP:
public function send() {
$stringToSign = 'test';
$key = '123456';
//Calculate Signature
$signature = $this->calculateSignature($stringToSign, $key);
print_r("StringToSign: " . $stringToSign . PHP_EOL);
print_r("Key: " . $key . PHP_EOL);
print_r("Signature Caculated: " . $signature . PHP_EOL);
}
private function calculateSignature($stringToSign, $key) {
// check signature
$hash = strtoupper(hash_hmac('sha256', $stringToSign, $key, false));
return $hash;
}
For better understanding, here is the output of the code blocks above:
C#
StringToSign: test
Key: 123456
Signature Caculated:
DA3617974490FB780F04F06287BF93B0F24A7F15970471146428B943FFDC7850
PHP
StringToSign: test
GroupKey: 123456
Signature Caculated: 9D2BB116E1DF997FFE8A5139FC1D187F976C19579A138414A112BC2E39020EBA

If you want to modify the PHP to make it equivalent to C#
Change
$hash = strtoupper(hash_hmac('sha256', $stringToSign, $key, false));
to
$hash = strtoupper(hash_hmac('sha256', $stringToSign, hex2bin($key), false));
Be sure to check the code will work for non-ASCII characters like àèéìòù.
ideone here.
If you want to modify the C# code to make it equivalent to PHP
You are complicating everything:
static private string CalculateSignature(String stringToSign, String key)
{
byte[] key_byte = Encoding.UTF8.GetBytes(key);
byte[] stringToSign_byte = Encoding.UTF8.GetBytes(stringToSign);
//Check Signature
HMACSHA256 hmac = new HMACSHA256(key_byte);
byte[] hashValue = hmac.ComputeHash(stringToSign_byte);
return BitConverter.ToString(hashValue).Replace("-", "");
}
There is nothing hex. And instead of using Encoding.GetEncoding(65001) it is normally better to use Encoding.UTF8, because it is clearer for everyone.
Note that there could be problems with the encoding. Try calculating the hmac of something like àèéìòù to see which encoding the PHP is using.

Related

Decrypt and verify hash for SHA256 - c#

I am trying to decrypt and validate the hash but at times of decrypting, it throws me the error 'Key does not exist' and at the time of validating the hash return False
https://payvyne.readme.io/docs/webhooks
Signature:
HEjoCsghC9X0slrE2DprptDLYdoA7jaw4Jl7vpJVxzx9GNJEiO3pYGLDPhLmVqk98QJJ/FuiS5J+fvp+msr3Y8aFzKqjRQXj5TBELT38N+A7I8y3Vc0mgeR0aDMx7I83yhfkcoyhdiGJibzqQ5SYFZ0nnEVHYXheLUlga45yg/McDICtMm6lhnrPWEuHzoZTQkhsrLN/1W1PtLjJ2DickWB78PmhpeflL2Cpe6qS3qCclqFGZ7HIl9OoxU4WXpTYgxw7eixAKB7apFdFqea4BnGravfENNl97pOBuU6fRof4KtMczVagQw3QnxFD3BBtpTepRaT+jHY8wStXUG1bxllH32WiA9CVcpY4mxKhpxzQ8YD0b+3OgkpzZYS+BVVAdVazMJeEAw7v/zaxpjbR+Zo5l9vOLdyatwM75qpwMoKnMeKJHeRytEOK54al49OHiaE+v1OkOhJA0zh5nLzEIZanIdf+hXHDz3Euecs/p0cABiFNmhzYY5fl8qEytK6j2CjXQOYgljG5dqPm7M9CW36ntZTDaIEVWql3jdi9frxc4/82w1jhROFL0pBG1zz8nimAEesB1AaxmNqW7BIxULweX7eaReeo/dIqDSbmFuT+TikPQo4XRtmpDqO37Y9P6q7ZXtHOFopSaykHUHs+NgrKlBJMM5ADg5bHWm2Qows=
Public key:
pA6ULfXWrIMq-qvxn_0CykoStq0ZMYm63lHsuXTsE4q4tgekLJDW2Lnf35ilbFU_vybBdyeJAphpsYc4P0eJBt_z2T62HAV3gnwp_GU6hWIo8faK31TSXIrLmGjZlAVynAxjFYZoNxMeZuwEXpxG4bRGs58P7XSx1fAzedX6oGIlcSLljKH4I1BHt6gJhPIHYNXQzq_a0hX54C1m1VDVP_kot8ui1YKZil_riROK_Xk4ktnOTAqXo9z4uNBqzzH2k0J2YNiCb8VOdbp7kjmH9sPLI-jb-ociy0wSkGZc1e8saGIkkSm4eUASvX_M_TTDD99OrgoIS2Vx07Tw4lK5yd28EMVBUzy2OypuPVf9PyoDGv_4241x5PpJsA9IKocD7AgwxJ3E7FBFhvuSP8c5wspkbQxBwv5nnk2zAxuZsiJeK0o3JSxjkZJEkeVY4mA3VV9SvSXEKAFg2h9J3CR9PTwrZoVBruycVtWJ4it5jroXff-aGlLoRAO0g3gtfjkJb3tw6SJTFOA49iJci76Mj8Adz3eeEEGxTxfDzh_lq0jXxTk7cQSaR2_ChYLHaoorrrFmAvWgDH_lSvlISIgey-SzUoJM9RAy4gVFdmg-XCQQlpMh_d1-IACO3EfBvYKWE-6uGIqx1nZhn9WIDdSqMp6940xRxl0vQy8vYCQ5q8U
Data for Sign in string:
{"type":"PAYMENT_STATUS_CHANGE","paymentId":"1c6e834f074ec941","status":"FAILED","timestamp":1652688286662,"amount":"164.69","currency":"GBP","description":"This is test payment","paymentType":"ONE_OFF","bankName":"Diamond bank","destinationAccount":"GBP2","createdAt":"2022-05-16T08:04:32.994","updatedAt":"2022-05-16T08:04:46.662","customerReference":"1199","refundedAmount":"0.00"}
Expo (exponent):
AQAB
Below is the code to Decrypt the signature using public key.
public static void DecryptUsingPublicKey(string publicKey, string expo, string signature)
{
var modulus = ConvertToBase64(publicKey);
var exponent = Convert.FromBase64String(expo);
RSACryptoServiceProvider csp = new RSACryptoServiceProvider(2048);
var _publicKey = csp.ExportParameters(false);
_publicKey.Modulus = modulus;
_publicKey.Exponent = exponent;
csp.ImportParameters(_publicKey);
var dataBytes = ConvertToBase64(signature);
var plainText = csp.Decrypt(dataBytes, false);
var returnData = Encoding.Unicode.GetString(plainText);
Console.WriteLine($"value: {returnData}");
}
Below is the code for Verify signature using public key
public static void VerifySignature(string signature, string pKey, string dataForSign)
{
string pKeyNew = pKey;
pKeyNew = pKeyNew.Replace("_", "/").Replace("-", "+");
string publicKey = $"<RSAKeyValue><Modulus>{pKeyNew}==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
var encoder = new UTF8Encoding();
byte[] dataForSignAsBytes = encoder.GetBytes(dataForSign);
byte[] signatureAsBytes = ConvertToBase64(signature);
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider();
rsaCryptoServiceProvider.FromXmlString(publicKey);
var hashData = SHA256.Create().ComputeHash(dataForSignAsBytes);
var result1 = rsaCryptoServiceProvider.VerifyData(dataForSignAsBytes, CryptoConfig.MapNameToOID("SHA256"), signatureAsBytes);
var result2 = rsaCryptoServiceProvider.VerifyHash(hashData, CryptoConfig.MapNameToOID("SHA256"), signatureAsBytes);
var result3 = rsaCryptoServiceProvider.VerifyHash(hashData, signatureAsBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var result4 = rsaCryptoServiceProvider.VerifyData(dataForSignAsBytes, signatureAsBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
Console.WriteLine(result1);
Console.WriteLine(result2);
Console.WriteLine(result3);
Console.WriteLine(result4);
}
ConvertToBase64 function
public static byte[] ConvertToBase64(string data)
{
byte[] cyperBuffer;
string dataNew = data;
dataNew = dataNew.Replace("_", "/").Replace("-", "+");
try
{
if (dataNew.Substring(dataNew.Length - 1) != "=")
{
dataNew += "=";
}
cyperBuffer = Convert.FromBase64String(dataNew);
}
catch
{
dataNew += "=";
try
{
cyperBuffer = Convert.FromBase64String(dataNew);
}
catch
{
//If any error occured while convert to base64 then append '=' at the end.
dataNew += "=";
cyperBuffer = Convert.FromBase64String(dataNew);
}
}
return cyperBuffer;
}
This is a conversion mistake; you need to decode the base 64 signature, not encode the signature, so the following line is wrong:
byte[] signatureAsBytes = ConvertToBase64(signature);
it should be something like:
byte[] signatureAsBytes = ConvertFromBase64(signature);
Decryption is modular exponentiation with a private key. Furthermore, encryption normally uses a different padding scheme than signature generation, so you'd expect that the unpadding would fail if you try and decrypt. Only verification is possible.

C# PHP SHA-256 hashing returns different value, but SHA-1 & base64 not

I am making backend system for mine game (server & client). I use following code to generate first part of signature (this one is corrupted one):
#example data
string Url = "https://example.com/api/test/example.php";
Dictionary<string, string> RequestBody = new Dictionary<string, string>() { { "example_value", "test" } };
string CreateFirstHash()
{
string Combined = "";
foreach(KeyValuePair<string, string> BodyPart in RequestBody)
{
Combined += BodyPart.Key + "-" + BodyPart.Value + ".";
}
string HashedCombined = Encryption.SHA1(Combined);
string EncodedUrl = Encryption.Base64Encode(this.Url);
string PlainText = HashedCombined + ":" + EncodedUrl + ":" + 'ACBANE8AX98FT7JY6YVWKAMTMJHMYH3E2C582FCYJBTQLU4UZVSJ2E67CPB7BG75NDASGS3BAMR34UVUZN2SSPCV35A8VJPKPPCGGVEH5U9JM47GLUKRZSH3T65MBVZ2RY78C69ZGMC7JG998HRBY6U9TLQH6JDCVRE5YAR8D3TUJ3H2LBE2C598M7VNDSME5WM2YX2449Q8Z923QWGPFLCXXXCC4CETTKUJ28RYSHN372WP2KCXH6V7ZNZNJRAE';
return Encryption.SHA256(PlainText);
}
Here is Encryption class:
using System;
using System.Security.Cryptography;
using System.Text;
public class Encryption
{
private static readonly Encoding enc = Encoding.UTF8;
public static string MD5(string input)
{
byte[] inputBytes = enc.GetBytes(input);
using(System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
byte[] hashBytes = md5.ComputeHash(inputBytes);
StringBuilder sb = new StringBuilder();
foreach(byte hashByte in hashBytes)
{
sb.Append(hashByte.ToString("x2"));
}
return sb.ToString();
}
}
public static string SHA1(string input)
{
byte[] inputBytes = enc.GetBytes(input);
using(SHA1Managed sha = new SHA1Managed())
{
byte[] hashBytes = sha.ComputeHash(inputBytes);
StringBuilder sb = new StringBuilder();
foreach(byte hashByte in hashBytes)
{
sb.Append(hashByte.ToString("x2"));
}
return sb.ToString();
}
}
public static string SHA256(string input)
{
byte[] inputBytes = enc.GetBytes(input);
using (SHA256Managed sha = new SHA256Managed())
{
byte[] hashBytes = sha.ComputeHash(inputBytes);
StringBuilder sb = new StringBuilder();
foreach (byte hashByte in hashBytes)
{
sb.Append(hashByte.ToString("x2"));
}
return sb.ToString();
}
}
public static string SHA512(string input)
{
byte[] inputBytes = enc.GetBytes(input);
using (SHA512Managed sha = new SHA512Managed())
{
byte[] hashBytes = sha.ComputeHash(inputBytes);
StringBuilder sb = new StringBuilder();
foreach (byte hashByte in hashBytes)
{
sb.Append(hashByte.ToString("x2"));
}
return sb.ToString();
}
}
public static string HMAC512(string input, string secret)
{
byte[] inputBytes = enc.GetBytes(input);
byte[] secretBytes = enc.GetBytes(secret);
using(HMACSHA512 hmac = new HMACSHA512(secretBytes))
{
byte[] hashBytes = hmac.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
public static string Base64Encode(string input)
{
byte[] inputBytes = Encoding.ASCII.GetBytes(input);
return Convert.ToBase64String(inputBytes);
}
}
Server validates data by making same hash, with the same data and finally checks if generated signature is equal to input one. This is server implementation for CreateFirstHash() function:
#example data
public $requestBody = array('example_value' => 'test');
public $url = 'https://example.com/api/test/example.php';
public $scope = 'game'; #this is not important, you can disregard it
private static function generateFirstHash($requestBody, $url, $scope)
{
$combined = "";
foreach ($requestBody as $key => $value)
{
$combined .= $key . '-' . $value . ".";
}
$combined = sha1($combined);
$encodedUrl = base64_encode($url);
$plainString = $combined . ':' . $encodedUrl . ':' . 'ACBANE8AX98FT7JY6YVWKAMTMJHMYH3E2C582FCYJBTQLU4UZVSJ2E67CPB7BG75NDASGS3BAMR34UVUZN2SSPCV35A8VJPKPPCGGVEH5U9JM47GLUKRZSH3T65MBVZ2RY78C69ZGMC7JG998HRBY6U9TLQH6JDCVRE5YAR8D3TUJ3H2LBE2C598M7VNDSME5WM2YX2449Q8Z923QWGPFLCXXXCC4CETTKUJ28RYSHN372WP2KCXH6V7ZNZNJRAE';
return hash('sha256', $plainString);
}
All data from input were the same (checked manually). This is list what was the same in debug (step by step):
Combined string: same
SHA-1 hash of combined string: same
Encoded URL: same
Plain text: same
Final SHA-256 hash: invalid
Can anyone knows what is wrong and how can I make this valid?
Edit 1
Added example input data.
Thanks for adding some sample data but your C#-code is not running directly as some functions are missing.
I run your PHP-code and could extract the input to the SHA256-function:
plainString: d4a1466c15dc46dd6f7533b172313660eab1aba5:aHR0cHM6Ly9leGFtcGxlLmNvbS9hcGkvdGVzdC9leGFtcGxlLnBocA==:ACBANE8AX98FT7JY6YVWKAMTMJHMYH3E2C582FCYJBTQLU4UZVSJ2E67CPB7BG75NDASGS3BAMR34UVUZN2SSPCV35A8VJPKPPCGGVEH5U9JM47GLUKRZSH3T65MBVZ2RY78C69ZGMC7JG998HRBY6U9TLQH6JDCVRE5YAR8D3TUJ3H2LBE2C598M7VNDSME5WM2YX2449Q8Z923QWGPFLCXXXCC4CETTKUJ28RYSHN372WP2KCXH6V7ZNZNJRAE
With this input the PHP-SHA256 is:
hash: dced08719b7da56f69f70204122a498f5eda5090ad6b5a90691eb73731cc4c15
Test the plainString-value with an online-tool (https://emn178.github.io/online-tools/sha256.html) gives the same result:
dced08719b7da56f69f70204122a498f5eda5090ad6b5a90691eb73731cc4c15
Last but not least I tested your C#-implementation of SHA256 after fixing the missing
byte[] inputBytes = **enc.GetBytes**(input);
and got the result:
dced08719b7da56f69f70204122a498f5eda5090ad6b5a90691eb73731cc4c15
So in the end - there is no difference in SHA256-results between C# and PHP.

PHP SHA-256 not the same as this c# code?

Got a problem - maybe... It seems that this piece of PHP:
$mydigestb = hash("sha256",$digeststring);
does not generate the same result as this C# code:
private string GenerateDigest(long currentTime)
{
SHA256Managed hashString = new SHA256Managed();
StringBuilder hex = new StringBuilder();
byte[] hashValue = hashString.ComputeHash(Encoding.UTF8.GetBytes(String.Format("{0}{1}", currentTime, txtApiKey.Text)));
foreach (byte x in hashValue)
{
hex.AppendFormat("{0:x2}", x);
}
return hex.ToString();
}
The input values are the same, but it appears that what comes out is different.
It is the same for me:
PHP code: http://codepad.org/gcGC5Omp
<?php
$mydigestb = hash("sha256" , "abhinav" );
echo $mydigestb;
?>
C# code: https://ideone.com/jrz05O
using System;
public class Test
{
public static void Main()
{
System.Security.Cryptography.SHA256Managed hm = new System.Security.Cryptography.SHA256Managed();
byte[] hashValue = hm.ComputeHash(System.Text.Encoding.ASCII.GetBytes("abhinav"));
Console.WriteLine(System.BitConverter.ToString(hashValue).Replace("-", "").ToLower());
}
}
Are you using the same sample data?

hashing "SHA256" with two parameters

I must convert a JAVA function that Hashing a string.
this is a function:
private static String hmacSha256(String value, String key) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] keyBytes = key.getBytes();
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(value.getBytes());
return String.format("%0" + (rawHmac.length << 1) + "x", new BigInteger(1, rawHmac));
}
My doubt is: this function take 2 parameters:
String value: It is the string to crypt
String Key: It is another key
I already used the Sha256, but I always use it with only one parameter (one string to encrypt)
please, how can I wrote this function in c# or is there anyone who can explain to me the logical?
thank you
You can use HMACSHA256 class to make it work:
private static string ComputeHash(string key, string value)
{
var byteKey = Encoding.UTF8.GetBytes(key);
string hashString;
using (var hmac = new HMACSHA256(byteKey))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(value));
hashString = Convert.ToBase64String(hash);
}
return hashString;
}
This is not plain SHA256, this is HMACSHA256 and there is allready a class in .Net.
HMACSHA256

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/
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):
Create an empty buffer byte[] innerData the length of innerKey.Length + message.Length (again assuming byte arrays)
Copy the innerKey and the message into the byte[] innerData
Compute SHA256 of innerData and store it in byte[] innerHash
Create an empty buffer byte[] data the length of outerKey.Length + innerHash.Length
Copy the outerKey and innerHash (from step #3)
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&currency=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
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);
}
A SHA hash is calculated on a sequence of bytes. Bytes are a profoundly different datatype to characters. You should not use character Strings to store binary data such as hashes.
sb.Append(Convert.ToString(Convert.ToChar(Int32.Parse(hex.Substring(i, 2)...
This creates a character string by reading each encoded byte and turning into a character of the same Unicode code point number. This is equivalent to decoding the bytes 0-255 using the ISO-8859-1 (Latin1) encoding, due to that encoding's property of matching the first 256 code points in Unicode.
var enc = Encoding.Default; [...]
baSalt = enc.GetBytes(salt);
byte[] sha256Bytes = Encoding.Default.GetBytes(input);
These both convert the characters back to bytes using the system default encoding. This encoding varies between installs, but it will never be ISO-8859-1 - even the similar Western European code page 1252 has different characters in the range 0x80-0x9F.
Consequently the byte array you are using doesn't contain the bytes implied by the example hex sequences. A cheap fix would be to use Encoding.GetEncoding("ISO-8859-1") instead of the default encoding, but really you should be using a bytes array to store data in the first place instead of a String, eg:
byte[] key= new byte[] { 0x57, 0x61, 0x7b, 0x5d, 0x23, 0x49, ... };
and pass that directly into ComputeHash.
If you must initialise data from a hex string, parse it directly into a byte array, eg:
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;
}
I realize the question is answered, but I am posting this in case others need it. Here is a snippet of code created by the payment provider (DIBS):
/**
* calculateMac
* Calculates the MAC key from a Dictionary<string, string> and a secret key
* #param params_dict The Dictionary<string, string> object containing all keys and their values for MAC calculation
* #param K_hexEnc String containing the hex encoded secret key from DIBS Admin
* #return String containig the hex encoded MAC key calculated
**/
public static string calculateMac(Dictionary<string, string> paramsDict, string kHexEnc)
{
//Create the message for MAC calculation sorted by the key
var keys = paramsDict.Keys.ToList();
keys.Sort();
var msg = "";
foreach (var key in keys)
{
if (key != keys[0]) msg += "&";
msg += key + "=" + paramsDict[key];
}
//Decoding the secret Hex encoded key and getting the bytes for MAC calculation
var kBytes = new byte[kHexEnc.Length / 2];
for (var i = 0; i < kBytes.Length; i++)
{
kBytes[i] = byte.Parse(kHexEnc.Substring(i * 2, 2), NumberStyles.HexNumber);
}
//Getting bytes from message
var msgBytes = Encoding.Default.GetBytes(msg);
//Calculate MAC key
var hash = new HMACSHA256(kBytes);
var macBytes = hash.ComputeHash(msgBytes);
var mac = BitConverter.ToString(macBytes).Replace("-", "").ToLower();
return mac;
}
http://tech.dibspayment.com/DX/Hosted/HMAC
Thanks you saved my time.
request.Method = "GET";
string signature = "";
string strtime = DateTime.UtcNow.ToString("yyyy-MM-ddTHH\\:mm\\:ssZ");
string secret = "xxxx";
string message = "sellerid:email:" + strtime;
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(secret);
byte[] messageBytes = encoding.GetBytes(message);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
var hash = new HMACSHA256(keyByte);
byte[] signature1 = hash.ComputeHash(messageBytes);
signature = BitConverter.ToString(signature1).Replace("-", "").ToLower();
}
request.Headers.Add("authorization", "HMAC-SHA256" + " " +
"emailaddress=xxx#xx.com,timestamp=" + strtime + ",signature=" + signature);
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
private static string GenerateSignature(string data, string signatureKey)
{
var keyByte = Encoding.UTF8.GetBytes(signatureKey);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(data));
return hmacsha256.Hash.Aggregate("", (current, t) => current + t.ToString("X2")).ToLower();
}
}

Categories

Resources