C#-使用params列表、json主体和secretkey计算HMAC



如何做到这一点?我迷路了。

表头参数:

new KeyValuePair<string, string>("checkout-account", MerchantId),
new KeyValuePair<string, string>("checkout-algorithm", "sha256"),
new KeyValuePair<string, string>("checkout-method", "POST"),
new KeyValuePair<string, string>("checkout-nonce", Guid.NewGuid().ToString()),
new KeyValuePair<string, string>("checkout-timestamp", DateTime.Now.ToString())

这些我只是添加到HttpClient中与它一起发送,但一个hmac我一辈子都无法想出如何使用C#进行计算。我一直在看的微软文档中的例子对我来说似乎毫无意义

正文:

{
"stamp": "d2568f2a-e4c6-40ba-a7cd-d573382ce548",
"reference": "3759170",
"amount": 1525,
"currency": "EUR",
"language": "FI",
"items": [
{
"unitPrice": 1525,
"units": 1,
"vatPercentage": 24,
"productCode": "#1234",
"deliveryDate": "2018-09-01"
}
],
"customer": {
"email": "test.customer@example.com"
},
"redirectUrls": {
"success": "https://ecom.example.com/cart/success",
"cancel": "https://ecom.example.com/cart/cancel"
}
}

以及密钥,例如"WHATEVER"。

文档中的php函数是:

function calculateHmac($secret, $params, $body = '')
{
// Keep only checkout- params, more relevant for response validation. Filter query
// string parameters the same way - the signature includes only checkout- values.
$includedKeys = array_filter(array_keys($params), function ($key) {
return preg_match('/^checkout-/', $key);
});
// Keys must be sorted alphabetically
sort($includedKeys, SORT_STRING);
$hmacPayload =
array_map(
function ($key) use ($params) {
return join(':', [ $key, $params[$key] ]);
},
$includedKeys
);
array_push($hmacPayload, $body);
return hash_hmac('sha256', join("n", $hmacPayload), $secret);
}

如果只是"下面是一些示例代码";因为这不仅仅是计算散列,而是在计算请求之前先对其进行规范化

  1. 获取参数列表(从显示的头中(,并使用PHP排序规则(似乎区分大小写(将它们排序为字符串
  2. 创建一个空字符串
  3. 对于排序列表中键以"0"开头的每个参数;结账-";将其附加到空字符串中,格式为key:value,然后附加换行符(\n(
  4. 获取请求正文并将其附加到字符串中。(它没有说明该怎么做——不过正文是空白的(这是您的规范化请求
  5. 使用您的机密在规范化请求上创建一个hmac
  6. 把它放在你的请求中?谁知道呢

如果这些猜测是正确的,那么规范化和哈希将是这样的(我基于.NET 6,有一些错误处理,但不多(

public static async Task<byte[]> HashRequest(HttpRequestMessage request, byte[] key) 
{
return CalculateHmac256(key, await CanonicalizeRequest(request));
}
private static async Task<string> CanonicalizeRequest(HttpRequestMessage request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (!request.Headers.Any())
{
throw new ArgumentException("Request has no headers");
}

var canonicalizedResourceBuilder = new StringBuilder();
SortedList<string, string> sortedCheckoutHeaderList = new();
foreach (var header in request.Headers)
{
if (header.Key.StartsWith("checkout-", StringComparison.Ordinal))
{
if (header.Value == null)
{
// Is this possible at all? Who knows, the spec doesn't say!
sortedCheckoutHeaderList.Add(header.Key, string.Empty);
}
else
{
var headerValue = header.Value.ToString();
if (headerValue == null)
{
sortedCheckoutHeaderList.Add(header.Key, string.Empty);
}
else
{
sortedCheckoutHeaderList.Add(header.Key, headerValue);
}
}                   
}
}
foreach (var keyValuePair in sortedCheckoutHeaderList)
{
canonicalizedResourceBuilder.Append(
keyValuePair.Key.ToLowerInvariant());
canonicalizedResourceBuilder.Append(':');
canonicalizedResourceBuilder.Append(keyValuePair.Value);
canonicalizedResourceBuilder.Append('n');
}
// Now get the body out.
if (request.Content != null)
{
using var bodyStream = new MemoryStream();
await request.Content.CopyToAsync(bodyStream).ConfigureAwait(false);
bodyStream.Seek(0, SeekOrigin.Begin);
using var streamReader = new StreamReader(bodyStream);
var requestContent = streamReader.ReadToEnd();
if (requestContent != null)
{
canonicalizedResourceBuilder.Append(requestContent);
}

canonicalizedResourceBuilder.Append('n');
}
return canonicalizedResourceBuilder.ToString();
}
private static byte[] CalculateHmac256(byte[] key, string plainText)
{
using HashAlgorithm hashAlgorithm = new HMACSHA256(key);
byte[] messageBuffer = new UTF8Encoding(false).GetBytes(plainText);
return hashAlgorithm.ComputeHash(messageBuffer);
}

如果这是有效的,因为规范化是正确的,那么就可以使用HttpMessageHandler自动完成,您可以在HTTP客户端发送请求之前将其放入HTTP管道中,并可以在请求退出时更改请求

public class AwfulSpecHttpMessageHandler : DelegatingHandler
{
public AwfulSpecHttpMessageHandler(byte[] key)
{
Key = key;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, 
CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
byte[] hash = await HashRequest(request, Key);
// Now put that hash somewhere in the message
// You'll probably have to base64 encode it first with Convert.ToBase64String(hash)
// Then send it on its way
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
private byte[] Key { get; set; }
}

然后你把它连接到

var authenticationHandler = new AwfulSpecHttpMessageHandler(key)
{
InnerHandler = new HttpClientHandler()
};
using (var httpClient = new HttpClient(authenticationHandler))
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://localhost");
{
httpRequestMessage.Content = new StringContent("myMessage");
await httpClient.SendAsync(httpRequestMessage);
};
}

或者使用ASP.NET中的HttpClientFactory,您可以在其中为命名客户端添加处理程序

我从一个共享的秘密实现中蚕食了这个,我写了

最新更新