使用 c# 计算 HMACSHA256 以匹配支付提供商示例 [英] Calculating HMACSHA256 using c# to match payment provider example

查看:29
本文介绍了使用 c# 计算 HMACSHA256 以匹配支付提供商示例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于支付提供商,我需要使用 HMAC-SHA256 计算基于哈希的消息验证码.这给我带来了很大的麻烦.

支付提供商提供了两个伪代码中正确计算的验证码示例.所有的键都是十六进制的.

方法一

key = 57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f4646e633af6375消息=金额=100&货币=欧元"MAC = HMAC-SHA256(十六进制解码(密钥),消息)结果 = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905

方法二

message = "amount=100&currency=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").

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

<块引用>

表示法:假设 A + B 连接字节数组 A 和 B.您可以或者参见 A ||B 符号用于更多的学术环境.

HMAC = SHA256(outerKey + SHA256(innerKey + message))..`-----------------' ..  `innerData`// `------------'/`innerHash`/`---------------------------------'`数据`

所以代码可以分解成这些步骤(以上述为指导):

  1. 创建一个空缓冲区byte[] innerData innerKey.Length + message.Length的长度(再次假设字节数组)
  2. innerKeymessage复制到byte[] innerData
  3. 计算innerData的SHA256并存入byte[] innerHash
  4. 创建一个空缓冲区byte[]数据长度outerKey.Length + innerHash.Length
  5. 复制 outerKeyinnerHash(从第 3 步开始)
  6. 计算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[].
使用 ASCIIEncodingUTF8Encoding 或您使用的任何外来编码.

私有静态字节[] 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&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):

  1. Create an empty buffer byte[] innerData the length of innerKey.Length + message.Length (again assuming byte arrays)
  2. Copy the innerKey and the message into the byte[] innerData
  3. Compute SHA256 of innerData and store it in byte[] innerHash
  4. Create an empty buffer byte[] data the length of outerKey.Length + innerHash.Length
  5. Copy the outerKey and innerHash (from step #3)
  6. 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

这篇关于使用 c# 计算 HMACSHA256 以匹配支付提供商示例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆