无法正确解密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等数据正常)。
修改步骤如下:
从
encryptedPayload中分离IV和实际加密数据:- 前16字节是IV
- 剩余字节是真正需要解密的内容
修正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
相关产品推荐
相关产品推荐

