63

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

支付提供商以伪代码形式给出了两个正确计算的验证码示例。所有的键都是十六进制的。

方法一

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

方法二

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

因此,如果没有这两种方法,我可以获得提供者示例想要的结果。

我在这里想念什么?是编码吗?我的 hexDecode 搞砸了吗?

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

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

4

7 回答 7

167

编辑:您可能正在寻找一种快速简单的方法来执行 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像这样使用(感谢@proximab)。如果您使用的是 pre-.NET 5,请滚动到具有替代解决方案的“Helper functions”。

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

我正在使用一种类似于 HMAC 的奇怪 API 服务,但它是自定义的

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


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

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

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

表示法:假设A + B连接字节数组 A 和 B。您也可以看到A || B在更多学术环境中使用的表示法。

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

所以代码可以分解成这些步骤(使用上面的作为指导):

  1. byte[] innerData创建一个长度为的空缓冲区innerKey.Length + message.Length(再次假设字节数组)
  2. innerKey和复制messagebyte[] innerData
  3. 计算 SHA256innerData并将其存储在byte[] innerHash
  4. 创建一个空缓冲区byte[] data,长度为outerKey.Length + innerHash.Length
  5. 复制outerKeyand innerHash(从第 3 步开始)
  6. 计算最终的哈希值data并将其存储byte[] result并返回。

要进行字节复制,我正在使用该Buffer.BlockCopy()函数,因为它显然比其他一些方式(源代码)更快。

nb 使用新的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;
}

辅助函数

string->byte[]

您有纯 ASCII 或 UTF8 文本,但需要它是byte[].
使用ASCIIEncodingorUTF8Encoding或您正在使用的任何外来编码。

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

你有一个byte[],但你需要它是一个 hex string

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

你有一个十六进制string, but you need it to be a 字节[]`。

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

nb 如果您需要在 .NET Framework 4.x 上调整性能的版本,您也可以向后移植 .NET 5+ 版本(通过替换ReadOnlySpan<byte>byte[])。它使用正确的查找表并注意热代码路径。您可以参考Github 上的 .NET 5( MIT 许可System.Convert代码。


为了完整起见,以下是使用“方法 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

于 2012-09-03T20:33:21.167 回答
54

这是一个字符串扩展方法,用于为给定字符串获取相当标准的 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();
}
于 2016-11-11T01:43:16.847 回答
7

您可以将此方法用于 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);
    }
于 2015-05-28T11:04:16.973 回答
0

SHA 哈希是根据字节序列计算的。字节是与字符截然不同的数据类型。您不应该使用字符串来存储二进制数据,例如哈希。

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

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

var enc = 编码。默认值;[...] 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;
}
于 2012-08-29T23:01:04.480 回答
0

我意识到这个问题已经得到解答,但我会发布这个以防其他人需要它。以下是支付提供商 (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

于 2015-07-15T07:39:41.687 回答
0

谢谢你节省了我的时间。

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;
于 2016-10-06T14:09:19.307 回答
-1
    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();
        }
    }
于 2021-02-23T18:51:19.397 回答