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

无法正确解密Microsoft日历事件富通知内容求助

问题:Microsoft Graph日历订阅通知解密开头乱码

我创建了一个Microsoft Graph订阅,用来接收日历事件创建和修改的通知,但解密通知里的标题、起止时间等详情时,开头部分总是乱码。

解密后的数据示例

Decrypted data: x�������p��테스트123","start":{"@odata.type":"#microsoft.graph.dateTimeTimeZone","dateTime":"2024-03-27T03:00:00.0000000Z","timeZone":"tzone://Microsoft/Utc"},"end":{"@odata.type":"#microsoft.graph.dateTimeTimeZone","dateTime":"2024-03-27T03:30:00.0000000Z","timeZone":"tzone://Microsoft/Utc"},"organizer":{"@odata.type":"#microsoft.graph.recipient","emailAddress":{"@odata.type":"#microsoft.graph.emailAddress","name":"관리자","address":"example@example.com"}}

补充的解密后数据:

x�������p��*테스트123","start":{"@odata.type":"#microsoft.graph.dateTimeTimeZone","dateTime":"2024-03-27T03:00:00.0000000Z","timeZone":"tzone://Microsoft/Utc"},"end":{"@odata.type":"#microsoft.graph.dateTimeTimeZone","dateTime":"2024-03-27T03:30:00.0000000Z","timeZone":"tzone://Microsoft/Utc"},"organizer":{"@odata.type":"#microsoft.graph.recipient","emailAddress":{"@odata.type":"#microsoft.graph.emailAddress","name":"관리자","address":"example@example.com"}}

我期望解密后的数据应该以{"subject": "test开头,而非乱码开头。我已经尝试修改过PaddingMode,但没有效果,找不到问题根源。

我的解密代码(C#)

DirectoryInfo currentDirectory = new DirectoryInfo(System.IO.Directory.GetCurrentDirectory());

var pfxCertificateFile = currentDirectory.GetFiles("*.pfx").First();

X509Certificate2 certificate = new X509Certificate2(pfxCertificateFile.Name, {MYPW}, X509KeyStorageFlags.MachineKeySet);

var privateKey = certificate.GetRSAPrivateKey();

byte[] encryptedSymmetricKey = Convert.FromBase64String(encryptedKey);  

var decryptedSymmetricKey = privateKey.Decrypt(encryptedSymmetricKey, RSAEncryptionPadding.OaepSHA1);

byte[] encryptedPayload = Convert.FromBase64String(encryptedData);

string decryptedResourceData = null;

using (HMACSHA256 hmac = new HMACSHA256(decryptedSymmetricKey))
{
    byte[] actualSignature = hmac.ComputeHash(encryptedPayload);
    byte[] expectedSignature = Convert.FromBase64String(dataSignature);

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

        aesProvider.IV = new byte[16];

        Array.Copy(decryptedSymmetricKey, aesProvider.IV, aesProvider.IV.Length);

        using (var decryptor = aesProvider.CreateDecryptor())
        using (MemoryStream msDecrypt = new MemoryStream(encryptedPayload))
        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
        {
            using (var ms = new MemoryStream())
            {
                csDecrypt.CopyTo(ms);

                byte[] decryptedbytes = ms.ToArray();

                string base64decrypteddata = Convert.ToBase64String(decryptedbytes);

                _logger.LogInformation($"decrypted raw data (base64): {base64decrypteddata}");

                ms.Seek(0, SeekOrigin.Begin);

                using (StreamReader srdecrypt = new StreamReader(ms))
                {
                    decryptedResourceData = srdecrypt.ReadToEnd();
                }
            }
        }
    }
}

我的订阅创建代码(C#)

DirectoryInfo currentDirectory = new DirectoryInfo(System.IO.Directory.GetCurrentDirectory());

var pfxCertificateFile = currentDirectory.GetFiles("*.pfx").First();

X509Certificate2 certificate = new X509Certificate2(pfxCertificateFile.Name, {MYPW}, X509KeyStorageFlags.MachineKeySet);

string publicKeyBase64 = Convert.ToBase64String(certificate.RawData);

var subscription = new Subscription
{
    ChangeType = "created,updated",
    NotificationUrl = "https://example.com/CalanderSync/notifications",
    Resource = $"users/{userId}/events?$select=subject,start,end,organizer",
    ExpirationDateTime = DateTimeOffset.UtcNow.AddHours(8),
    ClientState = Guid.NewGuid().ToString(),
    IncludeResourceData = true,
    EncryptionCertificate = publicKeyBase64,
    EncryptionCertificateId = "{CERTIDOFAPP}"
};

解决方案

问题出在IV的获取方式错误。根据Microsoft Graph的加密规范,加密payload时使用的IV并不是从对称密钥中截取前16字节,而是和加密后的payload一起传递的——实际的IV是加密payload的前16字节,剩下的部分才是真正的加密数据。

你当前的代码直接用对称密钥生成IV,导致解密时初始块解密错误,出现开头乱码,后面的数据因为CBC模式的特性,只要IV之后的块对齐,就能正常解密(这也和你观察到的现象一致:开头乱码,后面的start、end等数据正常)。

修改步骤如下:

  1. 从encryptedPayload中分离IV和实际加密数据:

    • 前16字节是IV
    • 剩余字节是真正需要解密的内容
  2. 修正AES配置中的IV来源,不再从对称密钥复制。

修改后的解密代码关键部分:

if (actualSignature.SequenceEqual(expectedSignature))
{
    // 分离IV和加密数据:前16字节是IV,剩下的是加密内容
    byte[] iv = new byte[16];
    byte[] actualEncryptedData = new byte[encryptedPayload.Length - 16];
    Array.Copy(encryptedPayload, iv, iv.Length);
    Array.Copy(encryptedPayload, iv.Length, actualEncryptedData, 0, actualEncryptedData.Length);

    AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider
    {
        Key = decryptedSymmetricKey,
        Padding = PaddingMode.PKCS7,
        Mode = CipherMode.CBC,
        IV = iv // 使用从payload中提取的IV
    };

    using (var decryptor = aesProvider.CreateDecryptor())
    // 注意:这里使用actualEncryptedData而非原encryptedPayload
    using (MemoryStream msDecrypt = new MemoryStream(actualEncryptedData))
    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
    {
        using (var ms = new MemoryStream())
        {
            csDecrypt.CopyTo(ms);
            byte[] decryptedbytes = ms.ToArray();
            
            _logger.LogInformation($"decrypted raw data (base64): {Convert.ToBase64String(decryptedbytes)}");
            
            ms.Seek(0, SeekOrigin.Begin);
            using (StreamReader srdecrypt = new StreamReader(ms, Encoding.UTF8)) // 指定UTF8编码更可靠
            {
                decryptedResourceData = srdecrypt.ReadToEnd();
            }
        }
    }
}

另外补充两点:

  • 创建订阅时,EncryptionCertificate应该传递公钥的Base64编码(PKCS#8格式),而不是证书的RawData。你当前用certificate.RawData是证书的DER编码,不符合要求。正确的获取方式是:
    // 获取公钥的PKCS#8格式并转为Base64
    string publicKeyBase64 = Convert.ToBase64String(certificate.GetRSAPublicKey().ExportSubjectPublicKeyInfo());
    
  • 解密时指定Encoding.UTF8读取StreamReader,避免编码问题导致的乱码。

这样修改后,就能正确解密出完整的JSON数据,开头不会再出现乱码。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 22:00:02