对于支付提供商,我需要使用 HMAC-SHA256 计算基于哈希的消息验证码.这给我带来了很大的麻烦.
支付提供商提供了两个伪代码中正确计算的验证码示例.所有的键都是十六进制的.
key = 57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f4646e633af6375消息=金额=100&货币=欧元"MAC = HMAC-SHA256(十六进制解码(密钥),消息)结果 = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
message = "amount=100¤cy=EUR"Ki = 61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c01761607506Ko = 0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f6367d0b0a1c6MAC = SHA256(十六进制解码(Ko)+ SHA256(十六进制解码(Ki)+消息))结果 = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
在进行了一些研究之后,我尝试编写代码来执行此操作,但我不断得出不同的结果.
private static void Main(string[] args){var key = "57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e6033a21575663";var ki = "61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258500c1761607";var ko = "0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b01a;"var mm = "金额=100&货币=欧元";var result1 = CalcHMACSHA256Hash(HexDecode(key), mm);var result2 = CalcSha256Hash(string.Format("{0}{1}", HexDecode(ko), CalcSha256Hash(HexDecode(ki) + mm)));Console.WriteLine("预期:b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905");Console.WriteLine("实际 1:" + result1);Console.WriteLine("实际 2:" + result2);Console.WriteLine("------------------");Console.ReadKey();}私有静态字符串HexDecode(字符串十六进制){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))));}返回 sb.ToString();}私有静态字符串 CalcHMACSHA256Hash(字符串明文,字符串盐){字符串结果 = "";var enc = 编码.默认值;字节[]baText2BeHashed = enc.GetBytes(明文),baSalt = enc.GetBytes(salt);System.Security.Cryptography.HMACSHA256 hasher = new HMACSHA256(baSalt);byte[] baHashedText = hasher.ComputeHash(baText2BeHashed);结果 = string.Join("", baHashedText.ToList().Select(b => b.ToString("x2")).ToArray());返回结果;}公共静态字符串 CalcSha256Hash(字符串输入){SHA256 sha256 = 新的 SHA256Managed();byte[] sha256Bytes = Encoding.Default.GetBytes(input);byte[] cryString = sha256.ComputeHash(sha256Bytes);字符串 sha256Str = string.Empty;for (int i = 0; i < cryString.Length; i++){sha256Str += cryString[i].ToString("x2");}返回 sha256Str;}
这是我得到的结果:
预期:b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905实际1:421ce16f2036bb9f2a3770c16f01e9220f0232d45580584ca41768fd16c15fe6实际2:290f14398bf8c0959dfc963e2fd9c377534c6fec1983025d2ab192382f132b92
所以没有这两种方法,我可以得到提供者示例想要的结果.
我在这里缺少什么?是编码吗?我的 hexDecode 搞砸了吗?
来自支付提供商的测试工具:http://tech.dibs.dk/dibs_api/other_features/hmac_tool/一个>
PHP 示例代码:http://tech.dibspayment.com/dibs_api/other_features/mac_calculation/
您可能正在寻找一种快速简单的方法来执行 HMAC-SHA256,而不是深入了解更详细的细节.最初的问题询问那些更详细的细节,这些细节将在下面进一步解释.
byte[]
消息输入执行 HMAC-SHA256使用 System.Security.Cryptography;...private static byte[] HashHMAC(byte[] key, byte[] message){var hash = new HMACSHA256(key);返回 hash.ComputeHash(message);}
在 .NET 5 及更高版本中,像这样使用 System.Convert.FromHexString
,(感谢@proximab).如果您使用的是 pre-.NET 5,请滚动到Helper functions";有替代解决方案.
使用系统;使用 System.Security.Cryptography;...私有静态字节[] HashHMACHex(字符串keyHex,字符串messageHex){var key = Convert.FromHexString(hexKey);var message = Convert.FromHexString(messageHex);var hash = new HMACSHA256(key);返回 hash.ComputeHash(message);}
继续阅读.您可能想使用方法 2";下面作为参考点并将其调整为您的服务希望您实施 HMAC 以进行消息防篡改.
在这里,我们将手动计算 HMAC-SHA256(这回答了原始问题中的方法 2").
假设outerKey
、innerKey
、message
已经是字节数组,我们执行如下:
表示法:假设 A + B
连接字节数组 A 和 B.您可以或者参见 A ||B
符号用于更多的学术环境.
HMAC = SHA256(outerKey + SHA256(innerKey + message))..`-----------------' .. `innerData`// `------------'/`innerHash`/`---------------------------------'`数据`
所以代码可以分解成这些步骤(以上述为指导):
byte[] innerData
innerKey.Length + message.Length
的长度(再次假设字节数组)innerKey
和message
复制到byte[] innerData
innerData
的SHA256并存入byte[] innerHash
byte[]数据
长度outerKey.Length + innerHash.Length
outerKey
和 innerHash
(从第 3 步开始)data
的最终hash并存入byte[] result
并返回.要进行字节复制,我使用的是 Buffer.BlockCopy()
函数,因为它显然比其他一些方法更快(source).
n.b.使用新的 ReadOnlySpan<T>
API 可能(阅读:当然)有更好的方法来做到这一点.
我们可以将这些步骤翻译成以下内容:
使用系统;使用 System.Security.Cryptography;...private static byte[] HashSHA(byte[] innerKey, byte[] outerKey, byte[] message){var hash = new SHA256Managed();//首先计算内部数据的哈希值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);//计算整个哈希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[] 结果 = hash.ComputeHash(data);返回结果;}
字符串
->字节[]
您有纯 ASCII 或 UTF8 文本,但需要它是 byte[]
.
使用 ASCIIEncoding
或 UTF8Encoding
或您使用的任何外来编码.
私有静态字节[] StringEncode(字符串文本){var encoding = new System.Text.ASCIIEncoding();返回编码.GetBytes(text);}
byte[]
->十六进制 字符串
你有一个 byte[]
,但你需要它是一个十六进制 string
.
私有静态字符串HashEncode(byte[] hash){return BitConverter.ToString(hash).Replace("-", "").ToLower();}
字符串
->字节[]
你有一个十六进制字符串,但你需要它是一个
byte[]`.
.NET 5 及更高版本
私有静态字节[] HexDecode(string hex) =>System.Convert.FromHexString(hex);
.NET 5 之前(感谢@bobince)
私有静态字节[] HexDecode(字符串十六进制){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);}返回字节;}
<块引用>
n.b.如果您需要 .NET Framework 4.x 上的性能调整版本,您也可以向后移植 .NET 5+ 版本(通过将 ReadOnlySpan
替换为 byte[]
).它使用正确的查找表并注意热代码路径.您可以参考 .NET 5 (MIT 许可) System.Convert
代码 在 Github 上.
为了完整起见,以下是使用方法 1"两种方法回答问题的最终方法.和方法2"
方法一"(使用 .NET 库)
私有静态字符串HashHMACHex(string keyHex, string message){byte[] hash = HashHMAC(HexDecode(keyHex), StringEncode(message));返回哈希编码(哈希);}
方法二"(手动计算)
私有静态字符串HashSHAHex(string innerKeyHex, string outerKeyHex, string message){byte[] hash = HashSHA(HexDecode(innerKeyHex), HexDecode(outerKeyHex), StringEncode(message));返回哈希编码(哈希);}
我们可以使用控制台应用程序执行快速健全性检查:
static void Main(string[] args){字符串消息=金额=100&货币=欧元";string expectedHex = "b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905";Console.WriteLine("预期:" + expectedHex);//测试 HMAC 哈希方法字符串键=57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e3033a2157566字符串 hashHMACHex = HashHMACHex(key, message);Console.WriteLine("方法一:" + hashHMACHex);//测试 SHA 哈希方法字符串内键=61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c1705";750650c1705";7字符串外键=0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b3a"字符串 hashSHAHex = HashSHAHex(innerKey, outerKey, message);Console.WriteLine("方法二:" + hashSHAHex);}
您应该正确排列所有哈希:
预期:b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905方法一:b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905方法二:b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
此答案的原始代码可在以下位置访问:http://pastebin.com/xAAuZrJX
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/
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.
byte[]
message inputusing System.Security.Cryptography;
...
private static byte[] HashHMAC(byte[] key, byte[] message)
{
var hash = new HMACSHA256(key);
return hash.ComputeHash(message);
}
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);
}
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.
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 seeA || 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):
byte[] innerData
the length of innerKey.Length + message.Length
(again assuming byte arrays)innerKey
and the message
into the byte[] innerData
innerData
and store it in byte[] innerHash
byte[] data
the length of outerKey.Length + innerHash.Length
outerKey
and innerHash
(from step #3)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;
}
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();
}
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>
withbyte[]
). 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
这篇关于使用 c# 计算 HMACSHA256 以匹配支付提供商示例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持html5模板网!