我正在尝试使哈希比较器正常工作,以便可以验证传入的请求。
流量:
Sender creates json object
-> sender creates hash of json object with a key that they and I know
-> sender sends json object and header with hash in it
-> I recieve request
-> I hash the json object with the common key
-> I compare my hash to the one in the header to validate user sending it
我正在努力从json对象创建哈希。
这是Ruby中(来自发送者)的示例代码,其中request_payload
是JSON对象。
hmac=OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'),YOUR_COMPANY_SIGNING_KEY,request_payload)
signature = Base64.strict_encode64(hmac)
我想用C#进行此操作。
我正在使用the Call Rail API中的数据(请参见右侧),并尝试将其哈希为字符串然后进行编码。
[HttpPost]
public async Task<ActionResult> PostAsync(dynamic request)
{
string signature = GetHash(request.ToString(), "072e77e426f92738a72fe23c4d1953b4"); // this key is from the example in Call Rail
string encodedSignature = Base64Encode(signature);
return Ok();
}
public static String GetHash(dynamic text, String key)
{
ASCIIEncoding encoding = new ASCIIEncoding();
Byte[] textBytes = encoding.GetBytes(text);
Byte[] keyBytes = encoding.GetBytes(key);
Byte[] hashBytes;
using (HMACSHA1 hash = new HMACSHA1(keyBytes))
hashBytes = hash.ComputeHash(textBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
我认为我苦苦挣扎的是如何获取传入的JSON
{"answered":false,"business_phone_number":"","call_type":"voicemail","company_id":155920786,"company_name":"Boost Marketing","company_time_zone":"America/Los_Angeles","created_at":"2018-02-19T13:41:00.252-05:00","customer_city":"Rochester","customer_country":"US","customer_name":"Kaylah Mills","customer_phone_number":"+12148654559","customer_state":"PA","device_type":"","direction":"inbound","duration":"13","first_call":false,"formatted_call_type":"Voicemail","formatted_customer_location":"Rochester, PA","formatted_business_phone_number":"","formatted_customer_name":"Kaylah Mills","prior_calls":16,"formatted_customer_name_or_phone_number":"Kaylah Mills","formatted_customer_phone_number":"214-865-4559","formatted_duration":"13s","formatted_tracking_phone_number":"404-555-8514","formatted_tracking_source":"Google Paid","formatted_value":"--","good_lead_call_id":715587840,"good_lead_call_time":"2016-06-17T10:23:33.363-04:00","id":766970532,"lead_status":"previously_marked_good_lead","note":"","recording":"https://app.callrail.com/calls/766970532/recording/redirect?access_key=aaaaccccddddeeee","recording_duration":8,"source_name":"Google AdWords","start_time":"2018-02-19T13:41:00.236-05:00","tags":[],"total_calls":17,"tracking_phone_number":"+14045558514","transcription":"","value":"","voicemail":true,"tracker_id":354024023,"keywords":"","medium":"","referring_url":"","landing_page_url":"","last_requested_url":"","referrer_domain":"","conversational_transcript":"","utm_source":"google","utm_medium":"cpc","utm_term":"","utm_content":"","utm_campaign":"Google AdWords","utma":"","utmb":"","utmc":"","utmv":"","utmz":"","ga":"","gclid":"","integration_data":[{"integration":"Webhooks","data":null}],"keywords_spotted":"","recording_player":"https://app.callrail.com/calls/766970532/recording?access_key=aaaabbbbccccdddd","speaker_percent":"","call_highlights":[],"callercity":"Rochester","callercountry":"US","callername":"Kaylah Mills","callernum":"+12148654559","callerstate":"PA","callsource":"google_paid","campaign":"","custom":"","datetime":"2018-02-19 18:41:00","destinationnum":"","ip":"","kissmetrics_id":"","landingpage":"","referrer":"","referrermedium":"","score":1,"tag":"","trackingnum":"+14045558514","timestamp":"2018-02-19T13:41:00.236-05:00"}
然后可以将其哈希为有用的东西。
使用给出的测试签名密钥,我应该找回UZAHbUdfm3GqL7qzilGozGzWV64=
。我从APIDocs知道这一点。
我目前正在通过邮递员发送上面的JSON字符串,但我注意到当我将其视为数据类型{
或{{1}时,会添加额外的'}
dynamic
' }。
任何见识将不胜感激!
答案 0 :(得分:1)
我认为您面临的问题是.NET Core WebAPI正在帮助您将主体解析为JSON(转换为JObject
)。
@dbc确定,真正需要的是用于生成HMAC签名的原始字符串主体,您可以在将主体自行解析为JSON之前进行验证。
我测试了This answer,并能够以纯字符串的形式接收尸体:
using System;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using Microsoft.AspNetCore.Mvc.Formatters;
namespace netcoretest {
public class RawJsonBodyInputFormatter : InputFormatter
{
public RawJsonBodyInputFormatter()
{
this.SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
}
}
在 Startup.cs中:
// in ConfigureServices()
services.AddMvc(options => {
options.InputFormatters.Insert(0, new RawJsonBodyInputFormatter());
});
在您的控制器中:
[HttpPost]
public async Task<ActionResult> PostTest([FromBody]string request)
{
// here request is now the request body as a plain string;
// you can now compute the signature on it and then later parse it to JSON.
}
但是,测试您当前的代码以生成Base64编码的签名,我没有得到正确的签名:
HMAC.digest
中的十六进制字符串:[5] pry(main)> hmac = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), YOUR_COMPANY_SIGNING_KEY, s)
=> "Q\x90\amG_\x9Bq\xAA/\xBA\xB3\x8AQ\xA8\xCCl\xD6W\xAE"
因此,至少在实现过程中也需要纠正这一部分。
更新
我能够使用以下代码获得正确的签名:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using System.Security.Cryptography;
using System.Text;
namespace netcoretest.Controllers
{
[Route("test")]
[ApiController]
public class TestController : ControllerBase
{
public TestController()
{
}
// POST: /test
[HttpPost]
public async Task<ActionResult> PostTest([FromBody]string request)
{
ASCIIEncoding encoding = new ASCIIEncoding();
Byte[] key = encoding.GetBytes("072e77e426f92738a72fe23c4d1953b4");
HMACSHA1 hmac = new HMACSHA1(key);
Byte[] bytes = hmac.ComputeHash(encoding.GetBytes(request));
Console.WriteLine(ByteArrayToString(bytes));
String result = System.Convert.ToBase64String(bytes);
Console.WriteLine(result);
return Ok();
}
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
}
}
我使用以下方法测试了向该端点的发布:
url --request POST https://localhost:5001/test --insecure --header 'Content-Type: application/json' --data-binary @test.json
其中test.json
是API文档中的示例JSON blob。
如果使用相同的代码,则无法获得匹配的签名,请仔细检查test.json
上是否没有尾随换行符或空格。
希望这会有所帮助!
答案 1 :(得分:1)
尽管@john_Ledbetter的答案很有效,但我决定使用一个动作过滤器更适合我。因此,我以他的答案为基础并为我修改了答案。我不认为此解决方案需要InputFormatter
ValidateCallRailRequestFiler.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace My_API.ActionFilters
{
public class ValidateCallRailRequestFilter: ActionFilterAttribute
{
//private readonly ILogger<ValidateCallRailRequestFilter> _logger;
//public ValidateCallRailRequestFilter(ILogger<ValidateCallRailRequestFilter> logger)
//{
// _logger = logger;
//}
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
//executing before action is called
// this should only return one object since that is all an API allows. Also, it should send something else it will be a bad request
var param = actionContext.ActionArguments.SingleOrDefault();
if (param.Value == null)
{
//_logger.LogError("Object sent was null. Caught in ValidateCallRailRequestFilter class.");
actionContext.Result = new BadRequestObjectResult("Object sent is null");
return;
}
var context = actionContext.HttpContext;
if (!IsValidRequest(context.Request))
{
actionContext.Result = new ForbidResult();
return;
}
base.OnActionExecuting(actionContext);
}
private static bool IsValidRequest(HttpRequest request)
{
string json = GetRawBodyString(request.HttpContext);
string token = "072e77e426f92738a72fe23c4d1953b4"; // this is the token that the API (Call Rail) would provide
string signature = request.Headers["Signature"];
// validation for comparing encoding to bytes and hashing to be the same
//https://rextester.com/EBR67249
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] key = encoding.GetBytes(token);
HMACSHA1 hmac = new HMACSHA1(key);
byte[] bytes = hmac.ComputeHash(encoding.GetBytes(json));
string result = System.Convert.ToBase64String(bytes);
return signature.Equals(result, StringComparison.OrdinalIgnoreCase);
}
public static string GetRawBodyString(HttpContext httpContext)
{
var body = "";
if (httpContext.Request.ContentLength == null || !(httpContext.Request.ContentLength > 0) ||
!httpContext.Request.Body.CanSeek) return body;
httpContext.Request.EnableRewind();
httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(httpContext.Request.Body, System.Text.Encoding.UTF8, true, 1024, true))
{
body = reader.ReadToEnd();
}
httpContext.Request.Body.Position = 0;
return body;
}
}
}
这包括一个读取JSON的正文阅读器,以及一种拒绝与403签名不匹配的传入请求的方法。
然后在我的控制器内:
[ValidateCallRailRequestFilter]
[HttpPost]
public async Task<ActionResult> PostAsync(dynamic request)
{
...
return Ok();
}
带有using My_Api.ActionFilters;