Last active
December 5, 2016 13:40
-
-
Save qgustavor/9b9af6c8baa8693720a8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
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
I see now that I forgot testing code in the revision history...