Подтвердить что ты не робот

Вычисление HMACSHA256 с использованием С# для соответствия примеру поставщика платежей

Для поставщика платежей мне нужно вычислить код проверки подлинности на основе хэша, используя HMAC-SHA256. Это вызывает у меня немало проблем.

Поставщик платежа дает два примера правильно рассчитанного кода аутентификации в псевдокоде. Все ключи в шестнадцатеричном формате.

Способ 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/

4b9b3361

Ответ 1

Я сделал полное решение вашей проблемы (так как это, вероятно, то, что вы искали). Он вычисляет правильный хеш, используя оба метода 1 и 2.

Обзор

Программа может быть организована в три раздела:

  • Хэш-функции - это фактические функции, которые будут вычислять хэши с помощью byte[] для ввода
  • Помощники кодирования - они используются с шестнадцатеричными функциями хеширования (# 3) и помогают с преобразованием следующего:
    • stringbyte[]
    • byte[] → hex string
    • hex stringbyte[] (спасибо @bobince!)
  • Хэш-функции - это вспомогательные функции, поэтому вы можете использовать хеш-функции (# 1), используя вместо этого шестнадцатеричную строку. Для этого используются помощники кодирования (# 2).

Код

0. Использование выражений

Прежде чем приступить к работе, убедитесь, что у вас есть следующие инструкции, чтобы вы не получали тонну ошибок, не считая их.

using System;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;

1. Хэш-функции

HMAC-SHA256 (метод 1)

Это вычислит HMAC-SHA256 (ваш метод 1). Как вы можете видеть, это намного проще, чем метод 2, но дает тот же результат.

private static byte[] HashHMAC(byte[] key, byte[] message)
{
    var hash = new HMACSHA256(key);
    return hash.ComputeHash(message);
}

SHA256 (метод 2)

Теперь, чтобы рассчитать хеш с использованием тонны хэширования SHA (ваш метод 2), это немного больше задействовано. Это в основном то же, что и ваш псевдокод без шестнадцатеричного декодирования, и вместо этого использует byte[] для ввода. Это будет выглядеть так:

MAC = SHA256( outerKey + SHA256( innerKey + message ) )

Вместо вашего:

MAC = SHA256( hexDecode(outerKey) + SHA256( hexDecode(innerKey) + message ) )

Где outerKey, innerKey и message все byte[] s. Конечно, в этом случае все ключи уже были декодированы из шестнадцатеричных строк, но также может быть byte[].

Таким образом, код можно разбить на следующие этапы:

  • Создайте буфер для внутренних данных и сохраните его в byte[] innerData
  • Скопируйте innerKey и message в byte[] innerData
  • Теперь вычислим хэш SHA256 innerData и сохраним его в byte[] innerHash
  • Для окончательного и полного хэша создайте для него буфер в byte[] data
  • Скопируйте outerKey и innerHash, ранее вычисленный хэш (из # 3), в data
  • Вычислить окончательный хэш data и сохранить его в result и вернуть его.

Чтобы выполнить байтовое копирование, я использую функцию Buffer.BlockCopy(), поскольку она, по-видимому, быстрее, чем некоторые другие способы (источник). Затем эти шаги можно записать в код следующим образом:

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

2. Вспомогательные функции

Прежде чем перейти к функции хеш-шестерки, вам нужно несколько функций, которые помогут преобразовать вещи, как указано в обзоре.

stringbyte[]

Строковая кодировка предполагает, что текст является простым ASCII и, похоже, работает (на данный момент). Хотя, если вам нужно кодировать с причудливыми символами, вам, вероятно, придется использовать UTF8 вместо этого. Если это так, то отключите ASCIIEncoding с UTF8Encoding или любую кодировку, которую вы используете.

private static byte[] StringEncode(string text)
{
    var encoding = new ASCIIEncoding();
    return encoding.GetBytes(text);
}

byte[] → hex string

Все это делает массив байтов и превращает его в строчную шестую строку в нижнем регистре. Довольно просто.

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

hex stringbyte[]

Наконец, это преобразование шестнадцатеричной строки в массив байтов. Это пришло от @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;
}

3. Хэш-функции хеша

Как уже говорилось, это вспомогательные функции, которые работают с хэш-функциями с шестнадцатеричными данными и строками. Они довольно понятны:

Хеширование хешей для HMAC

private static string HashHMACHex(string keyHex, string message)
{
    byte[] hash = HashHMAC(HexDecode(keyHex), StringEncode(message));
    return HashEncode(hash);
}

Хеширование хешей для SHA

private static string HashSHAHex(string innerKeyHex, string outerKeyHex, string message)
{
    byte[] hash = HashSHA(HexDecode(innerKeyHex), HexDecode(outerKeyHex), StringEncode(message));
    return HashEncode(hash);
}

4. Консольный тест

Хорошо объединить все функции вместе, вот консольная программа, которая вызовет функции, чтобы показать, что они действительно работают правильно.

static void Main(string[] args)
{
    string message = "amount=100&currency=EUR";
    string expectedHex = "b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905";
    Console.WriteLine("Ref : " + expectedHex);

    // Test out the HMAC hash method
    string key = "57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66";
    string hashHMACHex = HashHMACHex(key, message);
    Console.WriteLine("HMAC: " + hashHMACHex);

    // Test out the SHA hash method
    string innerKey = "61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c17616075060950";
    string outerKey = "0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b0a1f6c633a";
    string hashSHAHex = HashSHAHex(innerKey, outerKey, message);
    Console.WriteLine("SHA : " + hashSHAHex);

    Console.ReadLine();
}

Если все прошло правильно и оно бежало без ошибок, вы должны получить следующий результат, показывающий, что все хеши корректны (ref - ожидаемый хеш):

Ref : b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
HMAC: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
SHA : b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905

Заключение

Наконец, чтобы убедиться, что все работает, код вообще можно найти по адресу:
http://pastebin.com/xAAuZrJX

Ответ 2

Здесь используется метод расширения строки для получения достаточно стандартного токена 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();
}

Ответ 3

Вы можете использовать этот метод для 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);
    }

Ответ 4

Хэш SHA вычисляется по последовательности байтов. Байты представляют собой глубоко различный тип данных для персонажей. Вы не должны использовать символьные строки для хранения двоичных данных, таких как хэши.

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

Это создает строку символов, считывая каждый закодированный байт и превращаясь в символ того же номера кодовой точки Юникода. Это эквивалентно декодированию байтов 0-255 с использованием кодировки ISO-8859-1 (Latin1) из-за этого свойства кодирования для сопоставления первых 256 кодовых точек в Юникоде.

var enc = Encoding.Default; [...] baSalt= enc.GetBytes(соль);

byte [] sha256Bytes = Encoding.Default.GetBytes(ввод);

Оба они преобразуют символы обратно в байты, используя системную кодировку по умолчанию. Эта кодировка варьируется между установками, но никогда не будет 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;
}

Ответ 5

Я понимаю, что на вопрос ответили, но я отправляю это, если это кому-то понадобится. Вот фрагмент кода, созданного поставщиком платежей (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

Ответ 6

Спасибо, что вы спасли мое время.

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" + " " + 
"[email protected],timestamp=" + strtime + ",signature=" + signature);
HttpWebResponse response = request.GetResponse() as HttpWebResponse;