Unzipping encrypted compressed report from Amazon Selling Partner - javascript

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 does not work at all, the latter almost works but fails at the unzipping part.
How can I solve the unzip problem?

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 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);

Related

I'am Getting 'bad decrypt' error In Node.js

const crypto = require('crypto')
class encryptedDataClass {
constructor(massage){
this.algorithm = 'aes-256-cbc'
this.initVector = crypto.randomBytes(16);
this.massage = massage;
this.Securitykey = crypto.randomBytes(32);
}
encrypted(){
const cipher = crypto.createCipheriv(this.algorithm, this.Securitykey, this.initVector);
let encryptedData = cipher.update(this.massage, "utf-8", "hex");
return encryptedData += cipher.final("hex");
}
decrypted(){
const decipher = crypto.createDecipheriv(this.algorithm, this.Securitykey,
this.initVector);
let decryptedData = decipher.update(this.massage, "hex", "utf-8");
return decryptedData += decipher.final("utf-8");
}
}
const secureName = new
encryptedDataClass("850749d212e39c8e24aee37bbb43e3c1eaee69ea592eeaeb93da5c83437f64a0")
console.log(secureName.decrypted())
I created that key using encrypted function but I can't decode that key, I'm getting an error:
06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
How can I fix this error?
As AKX mentioned, you're creating a new IV everytime and when decrypting, you need the original IV. See my solution below. I'm storing the IV and later retrieving it by splitting the string on the "."
const crypto = require("crypto");
const algorithm = "aes-192-cbc"; // algorithm to use
const password = 'defineaPassword';
const key = crypto.scryptSync(password, 'salt', 24); // create key
const iv = crypto.randomBytes(16); // generate different ciphertext everytime
module.exports = {
encryptAndBase64 (messagetext) {
try {
const iv = crypto.randomBytes(16); // generate different ciphertext everytime
const cipher = crypto.createCipheriv(algorithm, key, iv);
const ciphertext = cipher.update(messagetext, 'utf8', 'hex') + cipher.final('hex'); // encrypted text
return `${iv.toString('base64')}.${ciphertext}`;
} catch (e) {
console.log(`error while encrypting: ${e.message}`);
return messagetext;
}
},
decrypt (messagetext) {
const split = messagetext.split('.');
const iv = split[0];
messagetext = split[1];
const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(iv, 'base64'));
return decipher.update(messagetext, 'hex', 'utf8') + decipher.final('utf8'); // deciphered text
},
};

Malformed UTF-8 data when decoding a BASE64 to String with ciphertext

I want to decode a BASE64 to String with ciphertext, key, and iv using Crypto-JS. I get the error Malformed UTF-8 data.
My code:
const CryptoJS = require('crypto-js')
const fs = require('fs');
// BASE64
const str = fs.readFileSync('file.txt','utf8')
const genRanHex = function(_0x8d0344) {
for (var _0x15f7b3 = [], _0x2491b5 = '0123456789abcdef', _0x2b0a63 = 0x0; _0x2b0a63 < _0x8d0344; _0x2b0a63++) _0x15f7b3['push'](_0x2491b5['charAt'](Math['floor'](Math['random']() * _0x2491b5['length'])));
return _0x15f7b3['join']('');
}
var KEYS = genRanHex(0x10) + '3c07f4b0efef700a',
ciphertext = str['substring'](0x18),
key = CryptoJS['enc']['Hex']['parse'](KEYS),
iv = CryptoJS.enc.Base64.parse(str.substring(0x0, 0x18))
try {
const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {iv }).toString(CryptoJS.enc.Utf8);
var originalText = JSON.parse(decrypted)
console.log(originalText)
} catch (err) {
console.error(err)
}

Why does signing algorithm in C# give different result than the one in Javascript

This is the algorithm for signing the data in C# using a private key from a certificate that is used from both me and the client in order to define an unique key to identify the user:
X509Certificate2 keyStore = new X509Certificate2(AppDomain.CurrentDomain.BaseDirectory + "Certifikatat\\" + certPath, certPass, X509KeyStorageFlags.Exportable);
RSA privateKey = keyStore.GetRSAPrivateKey();
byte[] iicSignature = privateKey.SignData(Encoding.ASCII.GetBytes("K31418036C|2022-5-16 13:30:41|406|st271ir481|al492py609|zz463gy579|340"), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
byte[] iic = ((HashAlgorithm)CryptoConfig,CreateFromName("MD5")).ComputeHash(iicSignature);
I then pass the private key to my Javascript using Bouncy Castle:
X509Certificate2 keyStore = new X509Certificate2(AppDomain.CurrentDomain.BaseDirectory + "Certifikatat\\" + certPath, certPass, X509KeyStorageFlags.Exportable);
RSA privateKey = keyStore.GetRSAPrivateKey();
var eky = DotNetUtilities.GetRsaKeyPair(privateKey);
Pkcs8Generator pkcs8Gen = new Pkcs8Generator(eky.Private);
Org.BouncyCastle.Utilities.IO.Pem.PemObject pkcs8 = pkcs8Gen.Generate();
PemWriter pemWriter = new PemWriter(new StringWriter());
pemWriter.WriteObject(pkcs8);
pemWriter.Writer.Flush();
return pemWriter.Writer.ToString();
This one is the algorithm used in Javascript:
window.crypto.subtle.importKey(
"pkcs8",
pemToArrayBuffer(pkcs8Pem), {
name: "RSASSA-PKCS1-v1_5",
hash: {
name: "SHA-256"
},
},
false, ["sign"]
)
.then(function(privateKey) {
console.log(privateKey);
// Sign: RSA with SHA256 and PKCS#1 v1.5 padding
window.crypto.subtle.sign({
name: "RSASSA-PKCS1-v1_5",
},
privateKey,
new TextEncoder().encode("K31418036C|2022-5-16 13:30:41|406|st271ir481|al492py609|zz463gy579|340")
)
.then(function(signature) {
var iic = md5(signature);
console.log(ab2b64(signature));
})
.catch(function(err) {
console.error(err);
});
})
.catch(function(err) {
console.error(err);
});
function ab2b64(arrayBuffer) {
return window.btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}
function removeLines(str) {
str = str.replace("\r", "");
return str.replace("\n", "");
}
function base64ToArrayBuffer(b64) {
var byteString = atob(b64);
var byteArray = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
byteArray[i] = byteString.charCodeAt(i);
}
return byteArray;
}
function pemToArrayBuffer(pem) {
var b64Lines = removeLines(pem);
var b64Prefix = b64Lines.replace('-----BEGIN PRIVATE KEY-----', '');
var b64Final = b64Prefix.replace('-----END PRIVATE KEY-----', '');
return base64ToArrayBuffer(b64Final);
}
The signatures returned are different for some reason. I need them to be the same or else it's all pointless because the client won't be authenticated.
The results are as follow:
C#:
57CF663ACBEDE6305309682BA7261412
Javascript:
c099d176dcd95c59d748d6066dcd462e
I had to convert my signature to base64 and then encode it with atob() after that i needed this md5 library to hash the data and then use .toUpperCase() to reproduce the correct result.
The complete code looks like this:
md5(atob(ab2b64(signature))).toUpperCase();
Now i get the same result from both C# and JS.

Why is my function returning unicode replacement characters

I'm trying to add encryption for passwords, and want to store the hashes and have the encryption/decryption being done on signup/login, the encryption goes fine from utf8 to hex, but when I try to decrypt, I get back a bunch of weird letters that look like:
"\ufffd'\rF\ufffd\ufffd\¡\ufffd6>\ufffd\ufffd#B,0\u0005\u0007\ufffd?\ufffd;\ufffd\u0018\u001e\"oؕ"
I've been trying to figure out how it could be using the wrong encoding data, as I have the hex and utf8 tags in the right places; I've been trying to look at other posts too, but I'm afraid they go over my head over how they're supposed to help me.
This is in the first file that I export it from
var crypto = require("crypto");
var secretkey = "twinkies";
var key = {
encrypt: function(pass){
var mykey = crypto.createCipher('aes-128-cbc', secretkey);
var finpass = mykey.update(pass, 'utf8', 'hex');
finpass += mykey.final('hex');
return finpass;
},
decrypt: function(pass){
var mykey = crypto.createCipher('aes-128-cbc', secretkey);
var finpass = mykey.update(pass, 'hex', 'utf8');
finpass += mykey.final('utf8');
return finpass;
}
};
this is the second file that uses the methods
app.post("/api/users/create", function(req, res) {
console.log(req.body.pword);
var newpass = key.encrypt(req.body.pword);
var oldpass = key.decrypt(newpass);
var enddata = {
Ciphored_password : newpass,
Deciphored_password : oldpass
}
console.log(res.json(enddata));
I use postman to test my localhost api, and am putting in 'bigthonks' as the password, when it console logs the enddata the Ciphored_password looks like:
982cb6d27f65fbb642c8c7b710e6c349
and the Deciphored_password:
"\ufffd'\rF\ufffd\ufffd\¡\ufffd6>\ufffd\ufffd#B,0\u0005\u0007\ufffd?\ufffd;\ufffd\u0018\u001e\"oؕ"
It turns out I'm an idiot... I missed one of the first steps of bugfixing which is looking at methodnames,
var crypto = require("crypto");
var secretkey = "twinkies";
var key = {
encrypt: function(pass){
var mykey = crypto.createCipher('aes-128-cbc', secretkey);
var finpass = mykey.update(pass, 'utf8', 'hex');
finpass += mykey.final('hex');
return finpass;
},
decrypt: function(pass){
var mykey = crypto.createCipher('aes-128-cbc', secretkey);
var finpass = mykey.update(pass, 'hex', 'utf8');
finpass += mykey.final('utf8');
return finpass;
}
};
on my decrypt method, when I'm generating the decipher for it i use the createCipher() method instead of the createDecipher() method, really big woops on my part.
I easily fixed it by changing the decrypt method to this:
decrypt: function(pass){
var mykey = crypto.createDecipher('aes-128-cbc', secretkey);
var finpass = mykey.update(pass, 'hex', 'utf8');
finpass += mykey.final('utf8');
return finpass;
}
and now it works.

How do I create an encrypt and decrypt function in NodeJS?

I need to create an encryption and decryption function in my NodeJS application. Can anyone help and point me in the right direction?
After a bit of digging, I was able to answer my own question... Sorry for the lack of clarity and detail. Will work on that for the next question.
I've included the functions to encrypt and decrypt, and the "helping" functions to generate a key and generate the initialization vector as well.
var crypto = require('crypto');
var encrypt = function encrypt(input, password) {
var key = generateKey(password);
var initializationVector = generateInitializationVector(password);
var data = new Buffer(input.toString(), 'utf8').toString('binary');
var cipher = crypto.createCipheriv('aes-256-cbc', key, initializationVector.slice(0,16));
var encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
var encoded = new Buffer(encrypted, 'binary').toString('base64');
return encoded;
};
var decrypt = function decrypt(input, password) {
var key = generateKey(password);
var initializationVector = generateInitializationVector(password);
var input = input.replace(/\-/g, '+').replace(/_/g, '/');
var edata = new Buffer(input, 'base64').toString('binary');
var decipher = crypto.createDecipheriv('aes-256-cbc', key, initializationVector.slice(0,16));
var decrypted = decipher.update(edata, 'hex', 'utf8');
decrypted += decipher.final('utf8');
var decoded = new Buffer(decrypted, 'binary').toString('utf8');
return decoded;
};
var generateKey = function generateKey(password) {
var cryptographicHash = crypto.createHash('md5');
cryptographicHash.update(password);
key = cryptographicHash.digest('hex');
return key;
}
var generateInitializationVector = function generateInitializationVector(password) {
var cryptographicHash = crypto.createHash('md5');
cryptographicHash.update(password + key);
initializationVector = cryptographicHash.digest('hex');
return initializationVector;
}
var password = 'MyPassword';
var originalStr = 'hello world!';
var encryptedStr = encrypt(originalStr, password);
var decryptedStr = decrypt(encryptedStr, password);
Hats off to adviner for the inspiration of the solution.
His post can be here: here
I had originally gotten this working with this post by dave, but that didn't work for input values with a character length greater than 15. The updated code above works for inputs of any length.

Categories