hashing "SHA256" with two parameters - c#

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

Related

C#: Error while decoding OAEP padding weird issue

I'm currently working on a class that encrypts large amounts of text with a randomly generated encryption key encrypted by a X509 certificate from a smart card, using a RSACryptoServiceProvider to perform the master key encryption and decryption operations. However, when I have the fOEAP padding option set to true, I have the "Error while decoding OAEP padding" error on decryption every time. I've checked the key size and it is within acceptable limits. And I've gone through breakpoints to make sure that the Base64 string that gets returned from the encryption function is the exact same as the encrypted Base64 string that gets passed back to the decryption function when the file gets loaded again.
The key pair is definitely correct, since it works fine without OAEP. And I've checked the text encoding too.
EDIT: It turns out this could be a smart card specific issue, when I tried decryption with a local X509 certificate the decryption succeeded.
EDIT: This is the decryption code that fails:
string TestString = "Hello World!";
X509Certificate2 cert = DRXEncrypter.GetCertificate("Select a test certificate", "Select a certificate to use for this test from the local store.");
string key = DRXEncrypter.GenerateEncryptionKey(214);
Console.WriteLine("Encryption Key: " + key);
string encrypted = DRXEncrypter.EncryptBody(TestString, key);
Console.WriteLine("Encrypted Body: " + encrypted);
string cryptokey = DRXEncrypter.EncryptWithCert(cert, key);
Console.WriteLine("Encrypted Decryption Key: " + cryptokey);
string decrypted = DRXEncrypter.DecryptBody(encrypted, cryptokey, cert);
Console.WriteLine("Decrypted Body: " + decrypted);
Console.WriteLine("Output String: " + decrypted + ".");
Here is the code from the crypto provider class I've written. I've been stuck on this issue for hours, so it would be great if someone could help me out.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.IO;
namespace CoreDRXEditor
{
public class DRXEncrypter
{
private byte[] Salt = Encoding.ASCII.GetBytes("81PO9j8I1a94j");
private string EncryptionKey;
private const bool UseOAEP = true;
public DRXEncrypter(string EncryptionKey)
{
this.EncryptionKey = EncryptionKey;
}
public static string EncryptBody(string body, string encryptionkey)
{
// Use the plaintext master key to encrypt the body.
DRXEncrypter enc = new DRXEncrypter(encryptionkey);
// Encrypt the body.
return enc.Encrypt(body);
}
public static int GetMaxKeySize(X509Certificate2 cert)
{
RSACryptoServiceProvider csp = cert.PublicKey.Key as RSACryptoServiceProvider;
return csp.KeySize;
}
public static string DecryptBody(string body, string encryptionkey, X509Certificate2 cert)
{
// Decrypt the encrypted encryption key with the certificate.
string DecryptedKey = Convert.ToBase64String(DecryptWithCert(cert, encryptionkey));
// Create a new DRXEncrypter using the decrypted encryption key to decrypt the body.
DRXEncrypter enc = new DRXEncrypter(DecryptedKey);
// Return the decrypted body.
return enc.Decrypt(body);
}
public static string GenerateEncryptionKey(int KeyLength)
{
using (RandomNumberGenerator rng = new RNGCryptoServiceProvider())
{
byte[] CryptoBytes = new byte[KeyLength];
rng.GetBytes(CryptoBytes);
return Convert.ToBase64String(CryptoBytes);
}
}
public static X509Certificate2 GetCertificate(string title, string message)
{
X509Store cstore = new X509Store(StoreLocation.CurrentUser);
cstore.Open(OpenFlags.ReadOnly);
X509CertificateCollection certs = X509Certificate2UI.SelectFromCollection(cstore.Certificates, title, message, X509SelectionFlag.SingleSelection);
if (certs.Count == 1)
{
X509Certificate2 mcert = certs[0] as X509Certificate2;
return mcert;
}
else
{
return null;
}
}
public static string EncryptWithCert(X509Certificate2 cert, string PlainText)
{
RSACryptoServiceProvider csp = cert.PublicKey.Key as RSACryptoServiceProvider;
byte[] PlainBytes = Convert.FromBase64String(PlainText);
// This converts the plain text into a byte array and then encrypts the raw bytes.
byte[] CryptoBytes = csp.Encrypt(PlainBytes, UseOAEP);
// This converts the encrypted bytes into a Base64 string.
string ReturnString = Convert.ToBase64String(CryptoBytes);
return ReturnString;
}
public static byte[] DecryptWithCert(X509Certificate2 cert, string EncryptedText)
{
RSACryptoServiceProvider csp = cert.PrivateKey as RSACryptoServiceProvider;
//CspParameters csps = new CspParameters();
byte[] EncryptedBytes = Convert.FromBase64String(EncryptedText);
// This converts the encrypted, Base64 encoded byte array from EncryptWithCert() to a byte[] and decrypts it.
byte[] CryptoBytes = csp.Decrypt(EncryptedBytes, UseOAEP);
return CryptoBytes;
}
public string Encrypt(string PlainText)
{
RijndaelManaged Algorithm = null;
string Output = null;
try
{
Rfc2898DeriveBytes PrivateKey = new Rfc2898DeriveBytes(this.EncryptionKey, this.Salt);
Algorithm = new RijndaelManaged();
Algorithm.Key = PrivateKey.GetBytes(Algorithm.KeySize / 8);
Algorithm.Padding = PaddingMode.PKCS7;
ICryptoTransform Encryption = Algorithm.CreateEncryptor(Algorithm.Key, Algorithm.IV);
using (MemoryStream msa = new MemoryStream())
{
msa.Write(BitConverter.GetBytes(Algorithm.IV.Length), 0, sizeof(int));
msa.Write(Algorithm.IV, 0, Algorithm.IV.Length);
using (CryptoStream csa = new CryptoStream(msa, Encryption, CryptoStreamMode.Write))
{
using (StreamWriter swa = new StreamWriter(csa))
{
swa.Write(PlainText);
}
}
Output = Convert.ToBase64String(msa.ToArray());
}
}
finally
{
if (Algorithm != null)
{
Algorithm.Clear();
}
}
return Output;
}
public string Decrypt(string EncryptedText)
{
RijndaelManaged Algorithm = null;
string Output = null;
try
{
Rfc2898DeriveBytes PrivateKey = new Rfc2898DeriveBytes(this.EncryptionKey, this.Salt);
byte[] KeyBytes = Convert.FromBase64String(EncryptedText);
using (MemoryStream msb = new MemoryStream(KeyBytes))
{
Algorithm = new RijndaelManaged();
Algorithm.Key = PrivateKey.GetBytes(Algorithm.KeySize / 8);
Algorithm.IV = ReadByteArray(msb);
Algorithm.Padding = PaddingMode.PKCS7;
ICryptoTransform Decryption = Algorithm.CreateDecryptor(Algorithm.Key, Algorithm.IV);
using (CryptoStream csb = new CryptoStream(msb, Decryption, CryptoStreamMode.Read))
{
using (StreamReader srb = new StreamReader(csb))
{
Output = srb.ReadToEnd();
}
}
}
}
finally
{
if (Algorithm != null)
{
Algorithm.Clear();
}
}
return Output;
}
public static string Sha512(string ToHash)
{
using (SHA512 SHA = new SHA512Managed())
{
byte[] HashByte = Encoding.UTF8.GetBytes(ToHash);
byte[] HashBytes = SHA.ComputeHash(HashByte);
string Hash = System.Text.Encoding.UTF8.GetString(HashBytes, 0, HashBytes.Length);
return Hash;
}
}
public static string Base64Encode(string data)
{
byte[] str = Encoding.UTF8.GetBytes(data);
return Convert.ToBase64String(str);
}
public static string Base64Decode(string data)
{
byte[] str = Convert.FromBase64String(data);
return Encoding.UTF8.GetString(str);
}
private byte[] ReadByteArray(Stream st)
{
byte[] Length = new byte[sizeof(int)];
st.Read(Length, 0, Length.Length);
byte[] Buffer = new byte[BitConverter.ToInt32(Length, 0)];
st.Read(Buffer, 0, Buffer.Length);
return Buffer;
}
}
}
I've been arguing with this today with smartcards (or more accurately, a Yubikey Neo with the smartcard PIV applet enabled); using this code:
var encryptor = (RSACryptoServiceProvider)c.PublicKey.Key;
var decryptor = (RSACryptoServiceProvider)c.PrivateKey;
var encrypt = encryptor.Encrypt(bytes, RSAEncryptionPadding.Pkcs1);
var decrypt = decryptor.Decrypt(encrypt, RSAEncryptionPadding.Pkcs1);
I've found that it matters what padding algo I use. If I use PKCS1 padding, everything works. If I use OaepSHA1, I get the Error while decoding [...] error. If I use anything else (e.g., OaepSHA256) I get a Not supported error.
I can only conclude that my smartcard doesn't properly support OAEP SHA1, but padding with PKCS#1 everything is good.
Even if this answers what you already know, it may be useful as another datapoint for anyone else coming along using smartcards.
Make sure your key size is not too small or too large.
See comments from MSDN
The RSACryptoServiceProvider supports key sizes from 384 bits to 16384
bits in increments of 8 bits if you have the Microsoft Enhanced
Cryptographic Provider installed. It supports key sizes from 384 bits
to 512 bits in increments of 8 bits if you have the Microsoft Base
Cryptographic Provider installed.
So you might need to pad short key strings with some bytes to get the minimum key length
Ok, I managed to check this and from what I can see, I have problems with some certificates. I am not sure why some certificates work while others don't. It would be good to know why some certificates fail in this case?
Anyway, I created a new self signed certificate using windows "Manage File Encryption Certificates" and used this certificate, and all seems to work.
The out put from your code.
Encryption Key: aUc/GXWDoh2LktaEGeCJfju1dHP118yD/fzfT0iJLuhOq2QeyGpG6m3aBHaxvdH0ufeXRHbMjmlmPgIL/bhABzkT2C5Oa6ZhY3IFXb5t7JXZ3AtUunvtNAnRyFJ7MzklrSZGgQ
vF67DSNfIVE17doKt6j6mkCpSco56ooZCrOs2Mp3vSXqNjvjiwMEfQbk41aYUNVNVNlBGhdNQCIZIAKezQCUpWqzn2II27FIDfqDIEW4ieyzpXC05GzUlGXDxFOiFUPk3n0Y94vgeF8AlCD74eyZtz
WQ==
Encrypted Body: EAAAANS/W7+GGRbT1q5NCYvZlDZYtxaA8g55HzUqP5qxhenn
Encrypted Decryption Key: vc/tcsApmY1503BFi7oSu/RDvZivA1Ed58KJuLoEC6eE8q0BIa6ye2JvtXyxkVbzzL0MA51pZ2ZhMIsfCnBsEDjCgy+JLTZTGM1Mv+em9frFUKb0zHbICnPUa/3H
yd1yOWsdn5ws19QN2dzC6eau+ExhT2T/vyZO4Nf9NdHKnB8n2yB1rrQ/T+N2EYCNH/AVPDAsme6JG7k9Od2XIipBXMyCgXgWYZmQusq+JQjA9d3c4CrQYcg/ERF+K3oZv/gPicBkAR5taxwSxAajGg
bpkJNsbhTMHTN9bOn333qZ6ojlo5e882baZXuZWPr9qtj1b7ONoOyuSx/OvGKjt93BQg==
Decrypted Body: Hello World!
Output String: Hello World!.
Hope that helps

Password hashing in UWP

I have the following code in .net framework.
public string GetHashedPassword(string password, string salt)
{
byte[] saltArray = Convert.FromBase64String(salt);
byte[] passArray = Convert.FromBase64String(password);
byte[] salted = new byte[saltArray.Length + passArray.Length];
byte[] hashed = null;
saltArray.CopyTo(salted, 0);
passArray.CopyTo(salted, saltArray.Length);
using (var hash = new SHA256Managed())
{
hashed = hash.ComputeHash(salted);
}
return Convert.ToBase64String(hashed);
}
I'm trying to create an equivalent in .net core for a UWP application. Here's what I have so far.
public string GetHashedPassword(string password, string salt)
{
IBuffer input = CryptographicBuffer.ConvertStringToBinary(password + salt, BinaryStringEncoding.Utf8);
var hashAlgorithm = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
var hash = hashAlgorithm.HashData(input);
//return CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, hash);
}
The last line, converting the buffer back to a string doesn't work. I get this exception:
No mapping for the Unicode character exists in the target multi-byte code page.
How can I convert the buffer back into a string?
I am assuming, that you want to get the hashed password in a base64-format, because you did that in your .net example.
To get this, change:
CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, hash);
to:
CryptographicBuffer.EncodeToBase64String(hash);
So the complete method looks like this:
public string GetHashedPassword(string password, string salt)
{
IBuffer input = CryptographicBuffer.ConvertStringToBinary(password + salt, BinaryStringEncoding.Utf8);
var hashAlgorithm = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
var hash = hashAlgorithm.HashData(input);
return CryptographicBuffer.EncodeToBase64String(hash);
}

Bitstamp - new authentication in C# - signature

The new authentication of bitstamp says the following:
Signature is a HMAC-SHA256 encoded message containing: nonce, client ID and API key. The HMAC-SHA256 code must be generated using a secret key that was generated with your API key. This code must be converted to it's hexadecimal representation (64 uppercase characters).Example (Python):
message = nonce + client_id + api_key
signature = hmac.new(API_SECRET, msg=message, digestmod=hashlib.sha256).hexdigest().upper()
Source: link
I've got the following code to add the new signature (and other parameters):
public void AddApiAuthentication(RestRequest restRequest)
{
var nonce = DateTime.Now.Ticks;
var signature = GetSignature(nonce, apiKey, apiSecret, clientId);
restRequest.AddParameter("key", apiKey);
restRequest.AddParameter("signature", signature);
restRequest.AddParameter("nonce", nonce);
}
private string GetSignature(long nonce, string key, string secret, string clientId)
{
string msg = string.Format("{0}{1}{2}", nonce,
clientId,
key);
return ByteArrayToString(SignHMACSHA256(secret, StrinToByteArray(msg))).ToUpper();
}
public static byte[] SignHMACSHA256(String key, byte[] data)
{
HMACSHA256 hashMaker = new HMACSHA256(Encoding.ASCII.GetBytes(key));
return hashMaker.ComputeHash(data);
}
public static byte[] StrinToByteArray(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
public static string ByteArrayToString(byte[] hash)
{
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
And then I get this error:
{"error": "Invalid signature"}
Anyone got an idea what the problem could be? I checked my parameters a 100 times and those aren't wrong. Maybe somebody got a working piece of code (in C#) for the new authentication?
UPDATE
Abhinav was right, the StringToByteArray method was wrong (not only the typo :P) the working code is:
public static byte[] StrinToByteArray(string str)
{
return System.Text.Encoding.ASCII.GetBytes(str);
}
You are using str.ToCharArray() in StrinToByteArray which is incorrect (correct ONLY when used on the same system). You need to use ASCII encoding or something.

Match C# MD5 hashing algorithm with node.js

I am having trouble matching a C# hashing algorithm with node, the problem seems to be the Unicode encoding. Is there a way to convert a string to Unicode then hash it and output it as hexadecimal? Unfortunately I cannot change the c# code, it is out of my control.
node algorithm
function createMd5(message) {
var crypto = require("crypto");
var md5 = crypto.createHash("md5");
return md5.update(message).digest('hex');
}
c# hashing algorithm
private static string GetMD5(string text)
{
UnicodeEncoding UE = new UnicodeEncoding();
byte[] hashValue;
byte[] message = UE.GetBytes(text);
using (MD5 hasher = new MD5CryptoServiceProvider())
{
string hex = "";
hashValue = hasher.ComputeHash(message);
foreach (byte x in hashValue)
{
hex += String.Format("{0:x2}", x);
}
return hex.ToLower();
}
}
Your suspicion of this being an encoding problem is correct. You can fix your node code with the following alteration, which will convert your message string into utf-16 (which is what .NET's default encoding is):
function createMd5(message) {
var crypto = require("crypto");
var md5 = crypto.createHash("md5");
return md5.update(new Buffer(message, 'ucs-2')).digest('hex');
}

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