使用 c # 计算 HMACSHA256以匹配支付提供商示例

对于支付提供商,我需要计算一个金钥杂凑讯息鑑别码,使用 HMAC-SHA256。这给我惹了不少麻烦。

支付提供者给出了两个正确计算伪代码认证代码的例子。

方法1

key = 57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66
message = "amount=100&currency=EUR"
MAC = HMAC-SHA256( hexDecode(key), message )
result = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905

方法2

message = "amount=100&currency=EUR"
Ki = 61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c17616075060950
Ko = 0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b0a1f6c633a
MAC = SHA256( hexDecode(Ko) + SHA256( hexDecode(Ki) + message ) )
result = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905

在做了一些研究之后,我尝试编写这样做的代码,但是我总是得到不同的结果。

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

这就是我得到的结果:

Expected: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Actual 1: 421ce16f2036bb9f2a3770c16f01e9220f0232d45580584ca41768fd16c15fe6
Actual 2: 290f14398bf8c0959dfc963e2fd9c377534c6fec1983025d2ab192382f132b92

因此,如果不使用这两种方法中的任何一种,我就可以得到提供程序示例想要的结果。

我错过了什么? 是编码吗? 是我的己形解码搞砸了吗?

来自付款提供商的测试工具: http://tech.dibs.dk/dibs_api/other_features/hmac_tool/

PHP 示例代码: http://tech.dibspayment.com/dibs_api/other_features/mac_calculation/

103004 次浏览

SHA 哈希是按字节序列计算的。字节是与字符完全不同的数据类型。不应使用字符串来存储二进制数据,如散列。

Append (Convert.ToString (Convert.ToChar (Int32.Parse (hex. Substring (i,2) ...

这通过读取每个编码字节并转换为具有相同 Unicode字符的字符来创建一个字符串。这相当于使用 ISO-8859-1(Latin1)编码对字节0-255进行解码,因为该编码的属性与 Unicode 中的前256个编码点匹配。

Var enc = Encoding. Default; [ ... ] BaSalt = enc. GetBytes (salt) ;

Byte [] sha256Bytes = Encoding. Default. GetBytes (input) ;

它们都使用系统默认编码将字符转换回字节。这种编码在不同的安装之间有所不同,但它永远不会是 ISO-8859-1-即使类似的西欧代码页1252在0x80-0x9F 范围内有不同的字符。

因此,您正在使用的字节数组不包含示例十六进制序列所隐含的字节。一个便宜的解决方案是使用 Encoding.GetEncoding("ISO-8859-1")而不是默认的编码,但实际上你首先应该使用字节数组而不是字符串来存储数据,例如:

byte[] key= new byte[] { 0x57, 0x61, 0x7b, 0x5d, 0x23, 0x49, ... };

然后直接传给 ComputeHash

如果必须初始化来自十六进制字符串的数据,则将其直接解析为字节数组,例如:

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

编辑: 您可能正在寻找一种快速而简单的方法来执行 HMAC-SHA256,而不是进入更精细的细节。最初的问题要求那些更细微的细节,下面将进一步解释。

我想对 byte[]消息输入执行 HMAC-SHA256

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

我想执行 HMAC-SHA256,但我有一个十六进制字符串输入

进去。NET 5及以上版本,可以这样使用 System.Convert.FromHexString,(谢谢@ximab)。如果你在 pre-.NET 5,滚动到“ Helper 函数”,它有其他的解决方案。

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

我正在使用一个奇怪的 API 服务,它有点像 HMAC,但它是定制的

继续读。您可能希望使用下面的“方法2”作为参考点,并将其调整为您的服务希望您实现 HMAC 以进行消息防篡改的方式。


HMAC-SHA256如何工作(您是否需要知道如何... ...)

这里我们将手动计算 HMAC-SHA256(这回答了原始问题中的“方法2”)。

假设 outerKeyinnerKeymessage已经是字节数组,我们执行以下操作:

表示法: 假设 A + B连接字节数组 A 和 B 或者参见 A || B符号在更多的学术设置中使用。

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

因此,代码可以分解为以下几个步骤(使用上面的指南) :

  1. 创建一个长度为 innerKey.Length + message.Length的空缓冲区 byte[] innerData(同样假设字节数组)
  2. innerKeymessage复制到 byte[] innerData
  3. 计算 innerData的 SHA256并将其存储在 byte[] innerHash
  4. 创建一个长度为 outerKey.Length + innerHash.Length的空缓冲区 byte[] data
  5. 复制 outerKeyinnerHash(从步骤 # 3开始)
  6. 计算 data的最终散列并将其存储在 byte[] result中并返回它。

为了进行字节复制,我使用了 Buffer.BlockCopy()函数,因为它显然比其他方法(来源)更快。

使用新的 ReadOnlySpan<T> API 可能有更好的方法来实现这一点。

我们可以将这些步骤转化为以下内容:

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

辅助功能

- > byte[]

您有纯 ASCII 或 UTF8文本,但需要它是 byte[]
使用 ASCIIEncoding或者 UTF8Encoding或者任何你正在使用的特殊编码。

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

你有一个 byte[],但你需要它是一个十六进制 string

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

你有一个十六进制字节[]。

.NET 5及以上版本

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

在.NET 5 之前(谢谢@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.如果需要在。NET Framework 4.x,也可以将。NET 5 + 版本(将 ReadOnlySpan<byte>替换为 byte[])。它使用适当的查找表并注意热代码路径。您可以引用。NET 5(麻省理工的执照) System.Convert代码 在 Github 上


为了完整起见,下面是使用“方法1”和“方法2”回答问题的最终方法

“方法1”(使用.NET 库)

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

“方法2”(手动计算)

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

我们可以通过一个控制台应用程序执行一个快速的健康检查:

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

您应该让所有散列正确排列:

Expected: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Method 1: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Method 2: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905

本答案的原始代码可在以下网址查阅: Http://pastebin.com/xaauzrjx

可以对 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);

下面是 ByteToString 方法:

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

我知道这个问题已经得到了回答,但我还是要发布这个,以防其他人需要它。下面是支付提供商(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

谢谢你节省了我的时间。

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;

下面是一个字符串扩展方法,用于为给定的字符串获取相当标准的 HMAC SHA 256令牌:

用途:

myMessageString.HmacSha256Digest(mySecret)

字符串扩展方法:

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