I have implemented Secure Web hook features for my Spring Boot application(Java).
For that I have created "Subscription" with below 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\"}";
I have used ngrok for public IP:
When I am sending message from the team, I am getting below response.
{
"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:[email protected]')/messages('1565045424600')/replies('1565047490246')",
"resourceData": {
"id": "1565293727947",
"@odata.type": "#Microsoft.Graph.ChatMessage",
"@odata.id": "teams('88cbc8fc-164b-44f0-b6a6-b59b4a1559d3')/channels('19:[email protected]')/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..."
]
}
Now I want to decrypt data, Can any one help me to how to decrypt data in Java? For certificate generation, I have used my custom method: strong text.
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");
}
I had to struggle my way with it in Python 3.6, so for the sake of future python-readers, here is my skeleton of working code that do the above (using 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)
I updated the documentation to include Java samples. I'll also include the samples here for reference but future readers should refer to the documentation which is where the samples will be kept up to date.
Keep in mind these samples operate under the assumption that you have a local Java Key Store (JKS) that the certificate is pulled from.
Decrypt the AES key:
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);
Verify the data signature
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.
}
Decrypt the data content
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)));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With