Unzipping encrypted compressed report from Amazon Selling Partner

120

Question: Unzipping encrypted compressed report from Amazon Selling Partner

I am using the getReport operation to fetch the documentId, which later I use to download the report document itself which is encrypted and compressed.

The code looks like this:

const documentData = await this.sellingPartner.callAPI({   operation: "getReportDocument",   endpoint: "reports",   path: { reportDocumentId: reportData.reportDocumentId } })  const request = https.get(documentData.url, function(res) {   const data = [];   res.on("data", chunk => data.push(chunk));   res.on("end", () => {     const key = new Buffer.from(documentData.encryptionDetails.key, 'base64')     const initializationVector = new Buffer.from(documentData.encryptionDetails.initializationVector, 'base64')      const input = Buffer.concat(data)      let result;     try {       result = aes.decryptText(         aes.CIPHERS.AES_256,         key,         initializationVector,         input       )     } catch (e) {       console.log(e)     }      console.log(">>>>")     console.log(result)      zlib.gunzip(result, (err, unzipped) => {       debugger     });   }); } 

The current error I am getting is from zlib:

Error: incorrect header check     at Zlib.zlibOnError [as onerror] (node:zlib:189:17) 

I am getting the same even if I pass the unencrypted value directly to zlib.

There is a Sample Java code example in the docs, but I cannot understand very well where they do the decryption: before unzipping or after?

In any case, what is the right way to solve this: unzip and decrypt or decrypt and unzip? The former javascript error does not work at all, the latter almost works but fails at the unzipping part.

How can I solve the unzip problem?

Total Answers: 1

92

Answers 1: of Unzipping encrypted compressed report from Amazon Selling Partner

Short Answer: Decrypt first and then decompress if compression type is specified.

Longer answer:

After a little research I stumbled upon this example (sadly written javascript error in python not in javascript) but it covers the steps to do in more detail.

https://github.com/amzn/selling-partner-api-docs/issues/186#issuecomment-756108550

It contains both compressed or not compressed cases. Specifically for compressed it looks like this:

import gzip import requests  from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes  def ase_cbc_decryptor(key, iv, encryption):     cipher = Cipher(algorithms.AES(base64.b64decode(key)), modes.CBC(base64.b64decode(iv)))     decryptor = cipher.decryptor()     decrypted_text = decryptor.update(encryption)     unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()     unpaded_text = unpadder.update(decrypted_text)     return unpaded_text + unpadder.finalize()  def get_report_document_content(self, key, iv, url, compression_type=None):     resp = requests.get(url=url)     resp_content = resp.content      decrypted_content = ase_cbc_decryptor(key=key, iv=iv, encryption=resp_content)      if compression_type == 'GZIP':        decrypted_content = gzip.decompress(decrypted_content)      code = 'utf-8'     if 'cp1252' in resp.headers.get('Content-Type', '').lower():         code = 'Cp1252'     return decrypted_content.decode(code) 

P.S. keep in mind you need to use AES in CBC mode

Short example here: https://gist.github.com/manuks/5cef1e536ef791e97b39

var keyhex = "8479768f48481eeb9c8304ce0a58481eeb9c8304ce0a5e3cb5e3cb58479768f4"; //length 32   var blockSize = 16;  function encryptAES(input) {     try {         var iv = require('crypto').randomBytes(16);         //console.info('iv',iv);         var data = new Buffer(input).toString('binary');         //console.info('data',data);                  key = new Buffer(keyhex, "hex");         //console.info(key);         var cipher = require('crypto').createCipheriv('aes-256-cbc', key, iv);         // UPDATE: crypto changed in v0.10          // https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10           var nodev = process.version.match(/^v(\d+)\.(\d+)/);          var encrypted;          if( nodev[1] === '0' && parseInt(nodev[2]) < 10) {             encrypted = cipher.update(data, 'binary') + cipher.final('binary');         } else {             encrypted =  cipher.update(data, 'utf8', 'binary') +  cipher.final('binary');         }          var encoded = new Buffer(iv, 'binary').toString('hex') + new Buffer(encrypted, 'binary').toString('hex');          return encoded;     } catch (ex) {       // handle error       // most likely, entropy sources are drained       console.error(ex);     } } function decryptAES(encoded) {       var combined = new Buffer(encoded, 'hex');            key = new Buffer(keyhex, "hex");          // Create iv     var iv = new Buffer(16);          combined.copy(iv, 0, 0, 16);     edata = combined.slice(16).toString('binary');      // Decipher encrypted data     var decipher = require('crypto').createDecipheriv('aes-256-cbc', key, iv);      // UPDATE: crypto changed in v0.10     // https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10       var nodev = process.version.match(/^v(\d+)\.(\d+)/);      var decrypted, plaintext;     if( nodev[1] === '0' && parseInt(nodev[2]) < 10) {           decrypted = decipher.update(edata, 'binary') + decipher.final('binary');             plaintext = new Buffer(decrypted, 'binary').toString('utf8');     } else {         plaintext = (decipher.update(edata, 'binary', 'utf8') + decipher.final('utf8'));     }     return plaintext; } var input="testing";  var encrypted = encryptAES(input); console.info('encrypted:', encrypted); var decryped = decryptAES(encrypted); console.info('decryped:',decryped);