問題描述
對于支付提供商,我需要使用 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.T??oString("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);}
我想執行 HMAC-SHA256 但我有一個十六進制字符串輸入
在 .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);}
我正在使用一種類似于 HMAC 的奇怪 API 服務,但它是自定義的
繼續閱讀.您可能想使用方法 2";下面作為參考點并將其調整為您的服務希望您實施 HMAC 以進行消息防篡改.
HMAC-SHA256 的工作原理(您是否需要知道如何...)
在這里,我們將手動計算 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.
Method 1
key = 57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66
message = "amount=100¤cy=EUR"
MAC = HMAC-SHA256( hexDecode(key), message )
result = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Method 2
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.
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 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):
- Create an empty buffer
byte[] innerData
the length ofinnerKey.Length + message.Length
(again assuming byte arrays) - Copy the
innerKey
and themessage
into thebyte[] innerData
- Compute SHA256 of
innerData
and store it inbyte[] innerHash
- Create an empty buffer
byte[] data
the length ofouterKey.Length + innerHash.Length
- Copy the
outerKey
andinnerHash
(from step #3) - Compute the final hash of
data
and store it inbyte[] 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>
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模板網!