如何在Microsoft Graph中解密Webhook响应数据?

时间:2020-03-05 12:21:28

标签: encryption microsoft-graph-api microsoft-graph-teams

我已经为Spring Boot应用程序(Java)实现了Secure Web hook功能。

为此,我使用下面的JSON创建了“订阅”。

String subscriptionMessageTemplate = "{\"changeType\": \"created,updated\",\"notificationUrl\": \"%s/notify/messages\",\"lifecycleNotificationUrl\":\"%s/notify/messages/lifeCycle\", \"resource\": \"/teams/{id}/channels/19:{id}@thread.skype/messages\", \"clientState\": \"secretClientValue\",\"includeResourceData\": true,\"encryptionCertificate\": \"%s\",\"expirationDateTime\":\"%s\",\"encryptionCertificateId\": \"1\"}";

我已经将ngrok用于公共IP: enter image description here

当我从团队发送消息时,我的反应不佳。

{
    "value": [
        {
            "subscriptionId": "76222963-cc7b-42d2-882d-8aaa69cb2ba3",
            "changeType": "created",
            // Other properties typical in a resource change notification
            "resource": "teams('d29828b8-c04d-4e2a-b2f6-07da6982f0f0')/channels('19:f127a8c55ad949d1a238464d22f0f99e@thread.skype')/messages('1565045424600')/replies('1565047490246')",
            "resourceData": {
                "id": "1565293727947",
                "@odata.type": "#Microsoft.Graph.ChatMessage",
                "@odata.id": "teams('88cbc8fc-164b-44f0-b6a6-b59b4a1559d3')/channels('19:8d9da062ec7647d4bb1976126e788b47@thread.tacv2')/messages('1565293727947')/replies('1565293727947')"
            },
            "encryptedContent": {
                "data": "{encrypted data that produces a full resource}",
        "dataSignature": "<HMAC-SHA256 hash>",
                "dataKey": "{encrypted symmetric key from Microsoft Graph}",
                "encryptionCertificateId": "MySelfSignedCert/DDC9651A-D7BC-4D74-86BC-A8923584B0AB",
                "encryptionCertificateThumbprint": "07293748CC064953A3052FB978C735FB89E61C3D"
            }
        }
    ],
    "validationTokens": [
        "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU..."
    ]
}

现在我想解密数据,有人可以帮助我如何用Java解密数据吗? 为了生成证书,我使用了自定义方法: 强文本。

 private void generateSelfSignedX509Certificate(KeyPair keyPair) throws Exception {

    // yesterday
    Date validityBeginDate = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
    // in 2 years
    Date validityEndDate = new Date(System.currentTimeMillis() + 2 * 365 * 24 * 60 * 60 * 1000);

    // GENERATE THE X509 CERTIFICATE
    X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
    X500Principal dnName = new X500Principal("CN=John Doe");

    certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
    certGen.setSubjectDN(dnName);
    certGen.setIssuerDN(dnName); // use the same
    certGen.setNotBefore(validityBeginDate);
    certGen.setNotAfter(validityEndDate);
    certGen.setPublicKey(keyPair.getPublic());
    certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");

    this.certificate = certGen.generate(keyPair.getPrivate(), "BC");
}

2 个答案:

答案 0 :(得分:1)

我必须在Python 3.6中努力解决它,因此为了将来的python阅读器,这是我做上述工作的工作代码框架(使用pycryptodome==3.9.7):

import json
import hashlib, hmac
from base64 import b64decode, b64encode
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Util.Padding import unpad
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES

...
...

encrypted_symmetric_key: bytes = b64decode(encrypted_symmetric_key.encode())
encrypted_payload = b64decode(encrypted_payload.encode())

rsa_key = RSA.import_key(private_key, passphrase=private_key_passphrase)
cipher = PKCS1_OAEP.new(rsa_key)
# if length of encrypted_symmetric_key is > 128 we will get ciphertext with incorrect length, to avoid that lets split and decrypt in chunks
default_length = 128
length = len(encrypted_symmetric_key)
if length < default_length:
    decrypt_byte = cipher.decrypt(encrypted_symmetric_key)
else:
    offset = 0
    res = []
    while length - offset > 0:
        if length - offset > default_length:
            res.append(cipher.decrypt(encrypted_symmetric_key[offset:offset + default_length]))
        else:
            res.append(cipher.decrypt(encrypted_symmetric_key[offset:]))
        offset += default_length
    decrypt_byte = b''.join(res)
decrypted_symmetric_key = decrypt_byte

hash_state_machine = hmac.new(decrypted_symmetric_key, msg=encrypted_payload, digestmod=hashlib.sha256)
raw_signature = hash_state_machine.digest()

actual_signature_bytes: bytes = b64encode(raw_signature)
actual_signature: str = actual_signature_bytes.decode()

if actual_signature != expected_data_signature:
   raise Exception("data hash is not as expected")

iv = decrypted_symmetric_key[:16]
cipher2 = AES.new(decrypted_symmetric_key, AES.MODE_CBC, iv=iv)

message_str = unpad(cipher2.decrypt(encrypted_payload), block_size=16).decode()
message_dict = json.loads(message_str)

答案 1 :(得分:0)

我更新了documentation以包括Java示例。我还将在此处提供示例以供参考,但是以后的读者应该参考文档,以使这些示例保持最新。
请记住,这些示例在假设您具有从中提取证书的本地Java密钥库(JKS)的前提下进行操作。

解密AES密钥:

String storename = ""; //name/path of the jks store
String storepass = ""; //password used to open the jks store
String alias = ""; //alias of the certificate when store in the jks store, should be passed as encryptionCertificateId when subscribing and retrieved from the notification
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(storename), storepass.toCharArray());
Key asymmetricKey = ks.getKey(alias, storepass.toCharArray());
byte[] encryptedSymetricKey = Base64.decodeBase64("<value from dataKey property>");
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, asymmetricKey);
byte[] decryptedSymmetricKey = cipher.doFinal(encryptedSymetricKey);

验证数据签名

byte[] decryptedSymmetricKey = "<the aes key decrypted in the previous step>";
byte[] decodedEncryptedData = Base64.decodeBase64("data property from encryptedContent object");
Mac mac = Mac.getInstance("HMACSHA256");
SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "HMACSHA256");
mac.init(skey);
byte[] hashedData = mac.doFinal(decodedEncryptedData);
String encodedHashedData = new String(Base64.encodeBase64(hashedData));
if (comparisonSignature.equals(encodedHashedData);)
{
    // Continue with decryption of the encryptedPayload.
}
else
{
    // Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}

解密数据内容

SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "AES");
IvParameterSpec ivspec = new IvParameterSpec(Arrays.copyOf(decryptedSymmetricKey, 16));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skey, ivspec);
String decryptedResourceData = new String(cipher.doFinal(Base64.decodeBase64(encryptedData)));