Skip to content

Instantly share code, notes, and snippets.

@qgustavor
Last active December 5, 2016 13:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save qgustavor/9b9af6c8baa8693720a8 to your computer and use it in GitHub Desktop.
Save qgustavor/9b9af6c8baa8693720a8 to your computer and use it in GitHub Desktop.
// 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
Copy link
Author

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

@qgustavor
Copy link
Author

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