63

支払いプロバイダーの場合、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

したがって、2 つの方法のいずれも使用せずに、プロバイダーの例が望む結果を得ることができます。

ここで何が欠けていますか?エンコードですか?私の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を実行したいのですが、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>nb新しい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[]。または、または
使用しているエキゾチックなエンコーディングを使用します。ASCIIEncodingUTF8Encoding

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

あなたはを持ってbyte[]いますが、それはヘックスである必要がありますstring

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

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

.NET5以降

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で.NET5MITライセンス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 に答える
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 = Encoding.Default; [...] baSalt = enc.GetBytes(salt);

byte[] sha256Bytes = Encoding.Default.GetBytes(入力);

これらは両方とも、システムのデフォルトのエンコーディングを使用して、文字をバイトに戻します。このエンコーディングはインストールごとに異なりますが、ISO-8859-1 になることはありません。同様の西ヨーロッパ コード ページ 1252 でも、0x80 ~ 0x9F の範囲に異なる文字が含まれています。

したがって、使用しているバイト配列には、例の 16 進シーケンスによって暗示されるバイトが含まれていません。安価な修正はEncoding.GetEncoding("ISO-8859-1")、デフォルトのエンコーディングの代わりに使用することですが、実際には、最初に文字列の代わりにバイト配列を使用してデータを格納する必要があります。次に例を示します。

byte[] key= new byte[] { 0x57, 0x61, 0x7b, 0x5d, 0x23, 0x49, ... };

それを に直接渡しComputeHashます。

16 進文字列からデータを初期化する必要がある場合は、バイト配列に直接解析します。たとえば、次のようになります。

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

おかげで時間を節約できました。

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 に答える