Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// DEPENDS ON CRYPTO JS
// Compatible with App Script, Node/io and Browser environments
// COMMON BLOCK
function base64urldecode(data) {
data += '=='.substr((2 - data.length * 3) & 3);
data = data.replace(/\-/g, '+').replace(/_/g, '/').replace(/,/g, '');
if (typeof Utilities !== 'undefined') {
// Apps Script:
return Utilities.base64Decode(data).map(function (e) {
// Change from Int8Array to Uint8Array then to String
return String.fromCharCode((e + 256) % 256);
}).join('');
}
if (typeof atob === 'function') {
// Browser:
return atob(data);
}
// Node:
return String.fromCharCode.apply(null, Buffer.from(data, 'base64') /* return Uint8Array */ );
}
// END OF COMMON BLOCK
// base64_to_a32 BLOCK
function str_to_a32(b) {
var a = Array((b.length + 3) >> 2);
for (var i = 0; i < b.length; i++) {
a[i >> 2] |= (b.charCodeAt(i) << (24 - (i & 3) * 8));
}
return a;
}
function base64_to_a32(s) {
return str_to_a32(base64urldecode(s));
}
// END base64_to_a32 BLOCK
// base64_to_ab BLOCK
function str_to_ab(b) {
var ab, i;
b += Array(16 - ((b.length - 1) & 15)).join(String.fromCharCode(0));
return b;
}
function base64_to_ab(a) {
return str_to_ab(base64urldecode(a));
}
// END OF base64_to_ab BLOCK
// dec_attr BLOCK
function a32_to_ab(a) {
var ab = new Array(4 * a.length);
for (var i = 0; i < a.length; i++) {
ab[4 * i] = a[i] >>> 24;
ab[4 * i + 1] = a[i] >>> 16 & 255;
ab[4 * i + 2] = a[i] >>> 8 & 255;
ab[4 * i + 3] = a[i] & 255;
}
return ab;
}
function ab_to_str_depad(ab) {
var b, i;
return String.fromCharCode.apply(null, ab.buffer).trim();
}
function from8(utf8) {
return decodeURIComponent(escape(utf8))
}
function dec_attr(attr, key) {
var aes;
var b;
attr = CryptoJS.enc.Latin1.parse(attr).words; // OK
key = CryptoJS.lib.WordArray.create(
[key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7]]
);
var iv = CryptoJS.lib.WordArray.create([0, 0, 0, 0]);
attr = a32_to_ab(CryptoJS.AES.decrypt(
CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.lib.WordArray.create(attr),
iv: iv,
key: key
}), key, {
iv: iv,
padding: {
pad: function () {},
unpad: function () {}
}
}
).words);
b = String.fromCharCode.apply(null, attr).replace(/\0/g, '');
if (b.substr(0, 6) !== 'MEGA{"') {
throw Error('Decryption failed');
}
return JSON.parse(from8(b.substr(4)));
}
// END OF dec_attr BLOCK
// decrypt_key BLOCK
function decrypt_key(sharekey, a) {
var x = [];
for (var i = 0; i < a.length; i += 4) {
x = x.concat(CryptoJS.AES.decrypt(
CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.lib.WordArray.create(
[a[i], a[i + 1], a[i + 2], a[i + 3]]
)
}),
CryptoJS.lib.WordArray.create(sharekey), {
iv: CryptoJS.lib.WordArray.create([0, 0, 0, 0]),
padding: {
pad: function () {},
unpad: function () {}
}
}).words);
}
return x;
}
// END OF decrypt_key BLOCK
// MAIN FUNCTION
// - file is the returned API object
// - sharedkey is the shared encryption key from the download hash
function getMegaFileName(file, sharedKey) {
var id, key, k, n, p, decKey;
if (!file.k) {
throw Error('Missing file key');
}
id = file.p;
p = file.k.indexOf(id + ':');
if (p >= 0 && (!p || file.k.charAt(p - 1) === '/')) {
file.fk = 1;
} else {
p = -1;
}
if (p >= 0) {
var pp = file.k.indexOf('/', p);
if (pp < 0) {
pp = file.k.length;
}
p += id.length + 1;
key = file.k.substr(p, pp - p);
// we have found a suitable key: decrypt!
if (key.length < 46) {
// short keys: AES
k = base64_to_a32(key);
// check for permitted key lengths (4 === folder, 8 === file)
if (k.length === 4 || k.length === 8) {
decKey = null;
var shareKey = base64_to_a32(sharedKey);
if (Array.isArray(shareKey)) {
var len = shareKey.length;
if (len === 4 || len === 6 || len === 8) {
decKey = decrypt_key(shareKey, k);
} else {
throw Error('Wrong shared key size');
}
}
if (!decKey) {
throw Error('Coult not get file key');
}
k = decKey;
} else {
throw Error('Wrong file key size');
}
} else {
throw Error('Wrong key type');
}
var ab = base64_to_ab(file.a); // OK
var o = dec_attr(ab, k); // OK
if (typeof o === 'object' && typeof o.n === 'string') {
return o.n;
} else if (o === false) {
throw Error('File name not defined');
}
} else {
// This script can be adapted to handle folders
// But this one don't handle
throw Error('File handle mismatch or it\'s a folder');
}
}
// END OF MAIN FUNCTION
@qgustavor

This comment has been minimized.

Copy link
Owner Author

qgustavor commented Aug 17, 2015

I see now that I forgot testing code in the revision history...

@qgustavor

This comment has been minimized.

Copy link
Owner Author

qgustavor commented Aug 17, 2015

Re posting info as GitHub showed the last comment as a repeated comment and I deleted it:

It's adapted MEGA.nz code: it's uses CryptoJS because it's the one of the few libraries with don't require polyfills on apps script (i.e. don't use typed arrays), changed error handling (to throw Error instead of returning falsy values), removed dead code like RSA keys (which are used in other context but not this one), and worked to the code be compatible with all tested platforms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.