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

Graph Event订阅Webhook负载解密失败:签名不匹配求助

问题描述

创建Graph Event订阅时设置IncludeResourceData=true,提供Base64编码的证书公钥后,订阅创建成功且能正常接收Webhook回调,但使用微软官方示例解密代码时,actualSignature与expectedSignature始终不匹配,二者长度分别为32字节和44字节。

环境与代码信息

  • 订阅创建与Webhook接收使用同一份PFX证书,运行在同一设备:
    • 订阅创建时获取公钥代码:
      X509Certificate2 certif = new X509Certificate2(@"C:\test\keys\GraphEncryption-20230221.pfx", "", X509KeyStorageFlags.PersistKeySet);
      byte[] exp = certif.Export(X509ContentType.Cert);
      string expString = Convert.ToBase64String(exp);
      
      expString作为订阅的EncryptionCertificate属性值。
    • Webhook接收端加载证书代码:
      X509Certificate2 certif = new X509Certificate2(@"C:\test\keys\GraphEncryption-20230221.pfx", "", X509KeyStorageFlags.PersistKeySet);
      RSACryptoServiceProvider rsaProvider = (RSACryptoServiceProvider)certif.PrivateKey;
      

签名差异

接收Webhook后得到的十六进制签名:

expectedSignature 53-55-52-79-62-50-59-51-4A-4A-39-62-57-34-69-69-66-34-31-30-30-62-47-4D-4B-4A-4F-73-52-47-33-69-48-6E-46-4C-33-7A-4F-4D-63-64-4D-3D
actualSignature 8A-EE-D9-FE-47-C9-F8-83-2E-27-3C-43-6E-F9-95-E7-92-9C-85-ED-E0-70-17-39-64-54-8B-65-B8-A9-EB-E4

已尝试通过OpenSSL和Azure Key Vault生成不同证书,问题依旧。

Webhook接收端完整代码

X509Certificate2 certif = new X509Certificate2(@"C:\test\keys\GraphEncryption-20230221.pfx", "", X509KeyStorageFlags.PersistKeySet);
RSACryptoServiceProvider rsaProvider = (RSACryptoServiceProvider)certif.PrivateKey;

if (Request.RequestType != "POST") return;

string postdata;
using (StreamReader stream = new StreamReader(Request.InputStream))
   postdata = stream.ReadToEnd();

if (string.IsNullOrEmpty(postdata)) return;

System.Diagnostics.Debug.WriteLine(postdata);

GraphEvent ev = JsonConvert.DeserializeObject<GraphEvent>(postdata);

foreach (GraphSubs val in ev.value)
{
                byte[] encryptedSymmetricKey = Convert.FromBase64String(val.encryptedContent.dataKey);

                // Decrypt using OAEP padding.
                byte[] decryptedSymmetricKey = rsaProvider.Decrypt(encryptedSymmetricKey, fOAEP: true);

                // Can now use decryptedSymmetricKey with the AES algorithm.
                byte[] encryptedPayload = Encoding.ASCII.GetBytes(val.encryptedContent.data);
                byte[] expectedSignature = Encoding.ASCII.GetBytes(val.encryptedContent.dataSignature);
                byte[] actualSignature;

                using (HMACSHA256 hmac = new HMACSHA256(decryptedSymmetricKey))
                {
                    actualSignature = hmac.ComputeHash(encryptedPayload);
                }

                Debug.WriteLine("expectedSignature " + BitConverter.ToString(expectedSignature));
                Debug.WriteLine("actualSignature " + BitConverter.ToString(actualSignature));

                if (actualSignature.SequenceEqual(expectedSignature))
                {
                    AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider();
                    aesProvider.Key = decryptedSymmetricKey;
                    aesProvider.Padding = PaddingMode.PKCS7;
                    aesProvider.Mode = CipherMode.CBC;

                    // Obtain the intialization vector from the symmetric key itself.
                    int vectorSize = 16;
                    byte[] iv = new byte[vectorSize];
                    Array.Copy(decryptedSymmetricKey, iv, vectorSize);
                    aesProvider.IV = iv;

                    string decryptedResourceData;
                    // Decrypt the resource data content.
                    using (var decryptor = aesProvider.CreateDecryptor())
                    {
                        using (MemoryStream msDecrypt = new MemoryStream(encryptedPayload))
                        {
                            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                            {
                                using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                                {
                                    decryptedResourceData = srDecrypt.ReadToEnd();
                                }
                            }
                        }
                    }

                    // decryptedResourceData now contains a JSON string that represents the resource.
                    System.Diagnostics.Debug.Write("decrypted data: " + decryptedResourceData);
                }

                else Debug.WriteLine("! no decrypt performed: actualSignature not equal expectedSignature");
}

反序列化类定义

class GraphSubs
    {
        public GraphSubs() { }

        public string subscriptionId;
        public DateTimeOffset subscriptionExpirationDateTime;
        public string changeType;
        public string resource;
        public EncryptedContent encryptedContent;
        public ResourceData resourceData;
        public string clientState;
        public string tenantId;
        public string lifecycleEvent;
    }

    class ResourceData
    {
        [JsonProperty("@odata.type")]
        public string dataType;
        [JsonProperty("@odata.id")]
        public string dataId;
        [JsonProperty("@odata.etag")]
        public string dataEtag;
        public string id;
    }

    class EncryptedContent
    {
        public string data;
        public string dataKey;
        public string dataSignature;
        public string encryptionCertificateId;
        public string encryptionCertificateThumbprint;
    }

    class GraphEvent
    {
        public GraphSubs[] value;
    }
问题排查与解决

核心原因

代码中对encryptedContent.data和encryptedContent.dataSignature的处理逻辑错误:

  1. encryptedContent.data是Base64编码的加密负载,不能直接用Encoding.ASCII.GetBytes()转换,需先解码为原始字节数组。
  2. encryptedContent.dataSignature是Base64编码的HMAC签名,同样需先解码为字节数组,而非直接转换为ASCII字节。

这两个错误导致计算出的actualSignature与expectedSignature完全不匹配,且出现长度差异(44字节是Base64字符串的ASCII字节长度,32字节是HMACSHA256签名的正确长度)。

修复后的关键代码片段

修改接收端中处理encryptedPayload和expectedSignature的部分:

// 修正:将Base64编码的data解码为字节数组
byte[] encryptedPayload = Convert.FromBase64String(val.encryptedContent.data);
// 修正:将Base64编码的dataSignature解码为字节数组
byte[] expectedSignature = Convert.FromBase64String(val.encryptedContent.dataSignature);
byte[] actualSignature;

using (HMACSHA256 hmac = new HMACSHA256(decryptedSymmetricKey))
{
    actualSignature = hmac.ComputeHash(encryptedPayload);
}

额外优化建议

  • 避免使用已过时的RSACryptoServiceProvider,改用RSA类:
    RSA rsa = certif.GetRSAPrivateKey();
    byte[] decryptedSymmetricKey = rsa.Decrypt(encryptedSymmetricKey, RSAEncryptionPadding.OaepSHA1);
    
  • AesCryptoServiceProvider已过时,推荐使用Aes类:
    using Aes aes = Aes.Create();
    aes.Key = decryptedSymmetricKey;
    aes.Padding = PaddingMode.PKCS7;
    aes.Mode = CipherMode.CBC;
    byte[] iv = new byte[16];
    Array.Copy(decryptedSymmetricKey, iv, iv.Length);
    aes.IV = iv;
    

内容的提问来源于stack exchange,提问作者Teeces

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 15:35:27