Skip to content

Instantly share code, notes, and snippets.

@manuelfdo
Last active August 23, 2023 07:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manuelfdo/94a14c0314b07e311f07b240921eab86 to your computer and use it in GitHub Desktop.
Save manuelfdo/94a14c0314b07e311f07b240921eab86 to your computer and use it in GitHub Desktop.
Example of decoding Ecwid payment request using Web Cryptography API / SublteCrypto. https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto
var bodyData = 'ECWID PAYLOAD';
var clientId = 'YOUR CLIENT SECRET';
// Added required padding to make the payload a multiple of 4. We can do this using a repeat or a simple while.
// var paddingLength = 4 - (bodyData.length % 4);
// if (paddingLength !== 4) {
// bodyData + '='.repeat(paddingLength);
// }
while (bodyData.length % 4 !== 0) {
bodyData += '=';
}
var originalBase64 = bodyData.replace(/-/g, "+").replace(/_/g, "/");
var decodedBase64 = atob(originalBase64);
var iv = str2ab(decodedBase64.substring(0, 16));
var cipherOrder = b642ab(originalBase64);
var keyData = str2ab(clientId.substring(0, 16));
async function decryptOrder() {
var key = await importKey();
try {
return await crypto.subtle.decrypt(
{ name: "AES-CBC", iv: iv },
key,
cipherOrder
);
} catch (exception) {
console.error(
"An error occurred, Name: ",
exception.name,
", Message: ",
exception.message
);
}
}
async function importKey() {
var key = await crypto.subtle.importKey(
"raw",
keyData,
{ name: "AES-CBC" },
true,
["decrypt"]
);
return key;
}
// Concept from https://stackoverflow.com/a/11058858. Convert String to Array Buffer
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
// Concept from https://stackoverflow.com/a/21797381/9014097 Convert Base64 to Array Buffer.
function b642ab(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
(async () => {
var decrypted = await decryptOrder();
var decryptedStr = new TextDecoder().decode(decrypted);
console.log(decryptedStr.substring(16));
})();
@parasdaryanani
Copy link

@manuelfdo, thank you for putting this together!

I've noticed that the Ecwid payload can sometimes start after an arbitrary number of bytes.
Usually 15 or 16, but it's not consistent, due to which I think it may be worth tweaking the algorithm slightly.

Examples:

  1. Working with 15 bytes, sometimes I get:
const ecwidOrder = JSON.parse(decryptedStr.substring(15));
// SyntaxError: Unexpected token '�', "�{"storeId"... is not valid JSON
  1. Working with 16 bytes, sometimes I get:
const ecwidOrder = JSON.parse(decryptedStr.substring(16));
// SyntaxError: Unexpected token '"', ""storeId"... is not valid JSON
  1. Working with 15 OR 16 bytes, sometimes it just works.

  2. My solution (which may or may not be correct):

const firstIndexOfOpenBrace = decryptedStr.indexOf('{"storeId');
const ecwidOrder = JSON.parse(decryptedStr.substring(firstIndexOfOpenBrace));

@manuelfdo
Copy link
Author

@parasdaryanani Thats a good solution! I saw the problem with 15/16 bytes but haven't had time to check why it happens.

@manuelfdo
Copy link
Author

@parasdaryanani Hey! I’ve modified the gist to include the required padding to make the payload multiple of 4, that should solve the issue with 15/16 bytes.

@parasdaryanani
Copy link

@manuelfdo brilliant, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment