Python与C#的HMAC SHA1结果不匹配及JSON序列化等效性问询
移植Python HMAC SHA1到C# .NET 6时哈希不一致的问题解决
我是一名.NET开发者,目前正将一段Python的HMAC SHA1生成函数移植到C# .NET 6环境中,但无法得到与Python一致的输出。
Python代码
import hashlib import hmac import base64 import json def make_digest(payload, api_key,api_secret): api_key = api_key.encode("ascii") api_secret = api_secret.encode("ascii") payload = payload.encode("ascii") # print(payload) k=b'%s&%s' % (api_key, api_secret) signature = hmac.new( key= k, msg=payload, digestmod=hashlib.sha1) signature_digest = signature.digest() return base64.b64encode(signature_digest) api_key='somekey' api_secret='somesecret' payload1 = "{\"name\":\"somename\",\"filter_type\":\"somefilter\",\"filter_id\":\"1\",\"event_type\":\"some_event\",\"event_id\":\"1\",\"event_datetime\":\"2023-02-20T13:07:26.367367+00:00\",\"object_type\":\"sometype\",\"object_id\":\"2\",\"resources\":{\"respondent_id\":\"12\",\"recipient_id\":\"0\",\"survey_id\":\"12313\",\"user_id\":\"12312312313\",\"collector_id\":\"234234\"}}" payload=json.loads(payload1) payload=json.dumps(payload) result = make_digest(payload,api_key,api_secret) print(result) result1 = make_digest(payload1,api_key,api_secret) print(result1)
对应的C#代码
public static string GetHash(string apiKey,string apiSecret, string payLoad) { ASCIIEncoding encoding = new ASCIIEncoding(); byte[] apiSecretAscii = encoding.GetBytes(apiSecret); byte[] apiKeyAscii = encoding.GetBytes(apiKey); byte[] payLoadBytes = encoding.GetBytes(payLoad); byte[] hashBytes; var key = apiKeyAscii.Concat(encoding.GetBytes("&")).Concat(apiSecretAscii).ToArray(); using (HMACSHA1 hash = new (key)) { hashBytes = hash.ComputeHash(payLoadBytes); } return Convert.ToBase64String(hashBytes); }
C#调用方式
var str = GetHash("somekey", "somesecret", "{\"name\":\"somename\",\"filter_type\":\"somefilter\",\"filter_id\":\"1\",\"event_type\":\"some_event\",\"event_id\":\"1\",\"event_datetime\":\"2023-02-20T13:07:26.367367+00:00\",\"object_type\":\"sometype\",\"object_id\":\"2\",\"resources\":{\"respondent_id\":\"12\",\"recipient_id\":\"0\",\"survey_id\":\"12313\",\"user_id\":\"12312312313\",\"collector_id\":\"234234\"}}"); Console.WriteLine(str);
疑问
- 不清楚为何Python代码中两次打印的输出不一致,我需要在C#中匹配Python代码的第一个输出结果,该哈希值需要与同一请求的请求头中的哈希值一致。
- Python中的
json.dumps是否与C#中的NewtonSoft.Jsonconvert.SerializeObject()等效?
问题解答
1. Python两次输出不一致的原因及C#匹配方案
Python两次调用make_digest传入的payload字节内容存在本质差异:
- 第一次的
payload是先通过json.loads(payload1)解析为字典,再用json.dumps(payload)序列化的字符串。json.dumps会自动标准化JSON格式:按ASCII顺序排序字典的键、去掉所有多余空格、统一引号格式,最终生成紧凑且键有序的JSON字符串。 - 第二次的
payload1是手动编写的原始JSON字符串,可能存在键顺序不同、隐性空格等细节差异,导致字节流和第一次的payload完全不同,因此生成的HMAC哈希自然不一致。
要在C#中匹配Python第一个输出,必须让C#处理后的JSON字符串和Pythonjson.dumps的输出完全一致,核心是实现键排序+紧凑无缩进的序列化逻辑。
2. Python json.dumps与Newtonsoft.Json SerializeObject的等效性
默认情况下两者不等效,核心差异体现在两点:
- 键排序:
json.dumps默认按ASCII顺序排序字典的键;SerializeObject默认保留对象属性的定义顺序。 - 格式化:
json.dumps默认生成无缩进的紧凑JSON;SerializeObject默认生成带缩进的格式化JSON。
要实现等效,需要在C#中指定序列化选项,强制键排序并生成紧凑JSON:
var settings = new JsonSerializerSettings { Formatting = Formatting.None, // 紧凑输出,无缩进 ContractResolver = new OrderedContractResolver() // 按键名ASCII顺序排序 }; // 自定义ContractResolver实现键排序 public class OrderedContractResolver : DefaultContractResolver { protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { return base.CreateProperties(type, memberSerialization) .OrderBy(p => p.PropertyName) .ToList(); } }
修正后的C#代码
以下代码会先将原始JSON反序列化,再按Pythonjson.dumps的规则重新序列化,最后生成匹配的HMAC SHA1哈希:
using System; using System.Text; using System.Security.Cryptography; using Newtonsoft.Json; using System.Linq; using Newtonsoft.Json.Serialization; public class HmacHelper { public static string GetHash(string apiKey, string apiSecret, string rawPayload) { // 先反序列化再按标准规则序列化,匹配Python json.dumps的输出 var payloadObj = JsonConvert.DeserializeObject(rawPayload); var standardizedPayload = JsonConvert.SerializeObject(payloadObj, new JsonSerializerSettings { Formatting = Formatting.None, ContractResolver = new OrderedContractResolver() }); ASCIIEncoding encoding = new ASCIIEncoding(); // 直接拼接apiKey和apiSecret,避免Concat操作的潜在问题 byte[] keyBytes = encoding.GetBytes($"{apiKey}&{apiSecret}"); byte[] payloadBytes = encoding.GetBytes(standardizedPayload); using (HMACSHA1 hmac = new HMACSHA1(keyBytes)) { byte[] hashBytes = hmac.ComputeHash(payloadBytes); return Convert.ToBase64String(hashBytes); } } } public class OrderedContractResolver : DefaultContractResolver { protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { return base.CreateProperties(type, memberSerialization) .OrderBy(p => p.PropertyName) .ToList(); } }
调用示例
var rawPayload = "{\"name\":\"somename\",\"filter_type\":\"somefilter\",\"filter_id\":\"1\",\"event_type\":\"some_event\",\"event_id\":\"1\",\"event_datetime\":\"2023-02-20T13:07:26.367367+00:00\",\"object_type\":\"sometype\",\"object_id\":\"2\",\"resources\":{\"respondent_id\":\"12\",\"recipient_id\":\"0\",\"survey_id\":\"12313\",\"user_id\":\"12312312313\",\"collector_id\":\"234234\"}}"; var result = HmacHelper.GetHash("somekey", "somesecret", rawPayload); Console.WriteLine(result);
内容的提问来源于stack exchange,提问作者Debashish Saha
相关产品推荐
相关产品推荐

