Decryption of Business Chat Attachment

The documentation gives instructions to decode the key back to it's original value from hex, and to use that to decrypt the attachment.

The decryption-key is originally provided as hex in the attachment payload.


However, I can't seem to figure out how to decode to that original value.


Any advice would be greatly appreciated.

I believe I have found the answer so I'll post it here in case anyone else encounters this issue.


There is a document here that at the time of this posting had a detailed example on Page 15.


I found it helpful in my case to convert encrypted data to hex but I'm not sure that's entirly necessary

For anyone using node, this is the code that worked for me:


const fs = require("fs");
const crypto = require("crypto");

let keyStr = "00SOMEKEYINHEXTHATYOUGETFROMTHEATTACHMENTPAYLOADd".slice(2);
let fileStr = fs.readFileSync("input.raw.encrypted.data").toString("hex");
let iv = Buffer.alloc(16, 0);

let decipher = crypto.createDecipheriv("aes-256-ctr",d Buffer.from(keyStr, "hex"), iv);
decipher.setAutoPadding(false);

let deciphered = decipher.update(fileStr, "hex");

fs.writeFileSync("file.jpg", deciphered.toString("base64"), "base64");

Hopefully this helps.

Hello all! I also wanted to provide a solution for people who use PHP. I hope this helps someone who might run into problems.

Here an example how you can access the encoded attachment data by calling the /preDownload endpoint, decode the attachment payload and process it.

// get the request
$raw_input = file_get_contents('php://input');
$data = json_decode(gzdecode($raw_input), true);

// check if there are attachments
if(!isset($data['attachments']) || empty($data['attachments'])) {
     return false;
}

// jwt for Authorization for the preDownload
$jwt_payload = [
     'iss' => <MSP_ID>,
     'iat' => time()
];

// iterate through all attachments 
foreach($this->data['attachments'] as $k => $attachment) {
     //get hex encoded signature and convert to base64
     $hex_encoded_signature = $attachment['signature'];
     $base64_encoded_signature = base64_encode(pack('H*', $hex_encoded_signature));
     
     $predownload_headers = [
          "Authorization" => 'Bearer '.utf8_decode(jwt_encode($jwt_payload, base64_decode(<MSP_SECRET>))),
          "source-id" => <BUSINESS_CHAT_ID>,
          "MMCS-Url" => $attachment['url'],
          "MMCS-Signature" => $base64_encoded_signature,
          "MMCS-Owner" => $attachment['owner']
     ];

     // get the download url of the attachment
     $pre_download_data = json_decode(perform_pre_download($predownload_headers), true);
     $download_url = $pre_download_data['download-url'];
     
     // Now that we have the url to download the encrypted payload we have to decode the payload data
     
     // get the encoded file and the key to decode it
     $binary = @file_get_contents($download_url);
     $decrypted_key = $attachment['key'];

     // decode via openssl
     $binary = openssl_decrypt(
          base64_encode($binary),
          "aes-256-ctr",
          pack("H*", substr($decrypted_key, 2)),
          OPENSSL_ZERO_PADDING,
          pack("H*", "00000000000000000000000000000000")
     );

     // from here on you can access the decoded image and process it in your code

     // you can add specific checks to validate the attachments like:
     $image = @getimagesizefromstring($binary);

     // check for valid mime types
     if (empty($image['mime']) && !in_array($image['mime'], ['image/jpeg','image/png', 'image/gif'])) {
        continue;   // skip this attachment
     }

     // make sure to store the image on your server since the 'download-url' from the perform_pre_download function will not 
     // be accessable after some time 
     file_put_contents(<PATH_TO_THE_FILE>, $binary);
}

// function to access the url where you can get the encoded attachment data 
function perform_pre_download($headers) {
     //prepare headers
     $request_header = [];

     //parse headers
     foreach($headers as $k => $v) {
          $request_header[] = $k.': '.$v;
     }

     $ch = curl_init();
     curl_setopt($ch, CURLOPT_URL, <URL_TO_PREDOWNLOAD_ENDPOINT>);
     curl_setopt($ch, CURLOPT_HTTPHEADER, $request_header);
     curl_setopt($ch, CURLOPT_HEADER, 0);
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
     curl_setopt($ch, CURLOPT_TIMEOUT, 5);
     curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
     curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

     // get response body
     $return = curl_exec($ch);

     // end curl request
     curl_close($ch);

     return $return;
}

// create jwt token
function jwt_encode(array $payload, string $secret):string
{
     $header = encode_base64_url_safe(json_encode(['typ' => 'JWT', 'alg' => 'HS256']));
     $payload = encode_base64_url_safe(json_encode($payload));
     $signature = encode_base64_url_safe(hash_hmac('sha256', $header.'.'.$payload, $secret, true));
     return $header.'.'.$payload.'.'.$signature;
}

NodeJs: Decryption of Business Chat Attachment

I've tried the NodeJS steps above but to no unveil. I'm not sure what I'm missing here. Although I'm unable to display the image. I'm confident that this is a decipher issue.

    // PreDownload: Grabbing the url from previous step
    const { data } = await axios.get("https://cvws.icloud-content.com/M/M...", { responseType: 'arraybuffer' });

    const iv = Buffer.alloc(16); // buffer alloc fills with zeros
    const key = Buffer.from(attachmentKey.slice(2), 'hex',);
    const decipher = crypto.createDecipheriv("aes-256-ctr", key, iv);
    decipher.setAutoPadding(false); // No Padding

    let decrypted = decipher.update(data); // if input is a buffer don't choose a encoding its ignored
    decrypted += decipher.final('base64'); // tried 'binary' & 'base64'

    // Write decrypted data to myImg.jpeg
    fs.writeFileSync("myImg.jpeg", decrypted)

NodeJS: decipher attachment - SOLVED

To answer my own question above how to fully decipher attachment and display image. The image is now able to display.

 // PreDownload: Grabbing the url from previous step
    const { data } = await axios.get("https://cvws.icloud-content.com/M/M...", { responseType: 'arraybuffer' });

    const iv = Buffer.alloc(16); // buffer alloc fills with zeros
    const key = Buffer.from(attachmentKey.slice(2), 'hex',);
    const decipher = crypto.createDecipheriv("aes-256-ctr", key, iv);
    decipher.setAutoPadding(false); // No Padding

    let decrypted = decipher.update(data); // if input is a buffer don't choose a encoding its ignored
    decrypted += decipher.final('base64'); // tried 'binary' & 'base64'

    // Write decode decrypted data into a buffer than write to file
    fs.writeFileSync("myImg.jpeg", Buffer.from(decrypted, 'base64'))
Decryption of Business Chat Attachment
 
 
Q