C# を使用して HMACSHA256 を計算し、支払いプロバイダーの例を一致させる 質問する

C# を使用して HMACSHA256 を計算し、支払いプロバイダーの例を一致させる 質問する

支払いプロバイダーの場合、HMAC-SHA256 を使用してハッシュベースのメッセージ認証コードを計算する必要があります。これはかなり面倒です。

支払いプロバイダーは、疑似コードで正しく計算された認証コードの例を 2 つ提供します。すべてのキーは 16 進数です。

方法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

したがって、どちらの方法も使用せずに、プロバイダーの例が求める結果を得ることができます。

ここで何が欠けているのでしょうか? エンコードですか? hexDecode が間違っているのでしょうか?

決済プロバイダーからのテストツール:http://tech.dibs.dk/dibs_api/other_features/hmac_tool/

PHP サンプルコード:http://tech.dibspayment.com/dibs_api/other_features/mac_calculation/

ベストアンサー1

編集:おそらく、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を実行したいのですが、16進文字列入力があります

.NET 5 以降では、System.Convert.FromHexString次のように使用します (@proximab に感謝)。.NET 5 より前のバージョンを使用している場合は、代替ソリューションが記載されている「ヘルパー関数」までスクロールしてください。

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」の回答です)。

outerKey、、innerKeyがすでにバイト配列であると仮定するとmessage、次の操作を実行します。

表記:A + Bバイト配列 A と B を連結すると仮定します。代わりに、A || Bより学術的な設定で使用される表記法が使用されることもあります。

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

したがって、コードは次の手順に分解できます (上記をガイドとして使用)。

  1. byte[] innerData長さの空のバッファを作成しますinnerKey.Length + message.Length(ここでもバイト配列を想定)
  2. innerKeyとをコピーmessageしてbyte[] innerData
  3. のSHA256を計算しinnerDataて保存するbyte[] innerHash
  4. byte[] data長さの空のバッファを作成しますouterKey.Length + innerHash.Length
  5. outerKeyおよびをコピーしますinnerHash(手順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;
}

ヘルパー関数

string->byte[]

プレーンASCIIまたはUTF8テキストがありますが、それを にする必要がありますbyte[]
ASCIIEncodingまたはUTF8Encodingまたは、使用している特殊なエンコード方式。

private static byte[] StringEncode(string text)
{
    var encoding = new System.Text.ASCIIEncoding();
    return encoding.GetBytes(text);
}
byte[]-> 16進数string

がありますbyte[]が、これは 16 進数 である必要がありますstring

private static string HashEncode(byte[] hash)
{
    return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
16進数string->byte[]

string, but you need it to be a 16進バイト[]`があります。

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

注: .NET Framework 4.x でパフォーマンスを調整したバージョンが必要な場合は、代わりに .NET 5+ バージョンをバックポートできます (ReadOnlySpan<byte>を に置き換えますbyte[])。適切なルックアップ テーブルを使用し、ホットコード パスを考慮します。.NET 5 (MITライセンス)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

おすすめ記事