You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 06:47:03