api_createuser
function: // - function api_createuser(ctx, invitecode, invitename, uh) { var i; var ssc = Array(4); // session self challenge, will be used to verify password var req, res; if (!ctx.passwordkey) { ctx.passwordkey = Array(4); for (i = 4; i--;) ctx.passwordkey[i] = rand(0x100000000); } if (!u_k) api_create_u_k(); // - u_k for (i = 4; i--;) ssc[i] = rand(0x100000000); // if (d) console.log("api_createuser - masterkey: " + u_k + " passwordkey: " + ctx.passwordkey); // - ( k) // ts ssc req = { a: 'up', k: a32_to_base64(encrypt_key(new sjcl.cipher.aes(ctx.passwordkey), u_k)), ts: base64urlencode(a32_to_str(ssc) + a32_to_str(encrypt_key(new sjcl.cipher.aes(u_k), ssc))) }; if (invitecode) { req.uh = uh; req.ic = invitecode; req.name = invitename; } if (d) console.log("Storing key: " + req.k); api_req([req], ctx); }
u_k
is the master key itself, a global variable. Array of 4x 32-bit numbers, which is created by the function api_create_uk
ssc
is just a random array, which is encrypted on the master key, concatenated with its open value and sent to the server. Later it will be used to verify the master key for authentication.sjcl
- AES cryptographic libraryrand()
is a local implementation of a pseudo-random number generator based on RC4encrypt_key()
is the cornerstone function of a symmetric service cryptography. Receives a key-initialized sjcl
object and an array to be encrypted. The function code is given below and, I hope, needs no explanation. // encrypt/decrypt 4- or 8-element 32-bit integer array function encrypt_key(cipher, a) { if (a.length == 4) return cipher.encrypt(a); var x = []; for (var i = 0; i < a.length; i += 4) x = x.concat(cipher.encrypt([a[i], a[i + 1], a[i + 2], a[i + 3]])); return x; }
ssc||encrypt_AES-128(u_k, ssc)
ssc
) created during registration comes from the serverapi_getsid2
is api_getsid2
: // - function api_getsid2(res, ctx) { var t, k; var r = false; if (typeof res == 'object') { // sjcl-aes var aes = new sjcl.cipher.aes(ctx.passwordkey); // -... if (typeof res[0].k == 'string') { k = base64_to_a32(res[0].k); if (k.length == 4) { // ... k = decrypt_key(aes, k); // - sjcl-aes, - aes = new sjcl.cipher.aes(k); // ssc if (typeof res[0].tsid == 'string') { t = base64urldecode(res[0].tsid); // // - , if (a32_to_str(encrypt_key(aes, str_to_a32(t.substr(0, 16)))) == t.substr(-16)) r = [k, res[0].tsid]; } // RSA-, else if (typeof res[0].csid == 'string') { var t = mpi2b(base64urldecode(res[0].csid)); var privk = a32_to_str(decrypt_key(aes, base64_to_a32(res[0].privk))); var rsa_privk = Array(4); // decompose private key for (var i = 0; i < 4; i++) { var l = ((privk.charCodeAt(0) * 256 + privk.charCodeAt(1) + 7) >> 3) + 2; rsa_privk[i] = mpi2b(privk.substr(0, l)); if (typeof rsa_privk[i] == 'number') break; privk = privk.substr(l); } // check format if (i == 4 && privk.length < 16) { // TODO: check remaining padding for added early wrong password detection likelihood r = [k, base64urlencode(crypto_rsadecrypt(t, rsa_privk).substr(0, 43)), rsa_privk]; } } } } } ctx.result(ctx, r); }
// function changepw(currentpw, newpw, ctx) { var pw_aes = new sjcl.cipher.aes(prepare_key_pw(newpw)); api_req([{ a: 'up', currk: a32_to_base64(encrypt_key(new sjcl.cipher.aes(prepare_key_pw(currentpw)), u_k)), k: a32_to_base64(encrypt_key(pw_aes, u_k)), uh: stringhash(u_attr['email'].toLowerCase(), pw_aes) }], ctx); }
prepare_key_pw
function, which was implicitly present in all previous operations. Her task is to convert the string password to a32 array, and then perform the key derivation operation as follows: // convert user-supplied password array function prepare_key(a) { var i, j, r; var aes = []; var pkey = [0x93C467E3, 0x7DB0C7A4, 0xD1BE3F81, 0x0152CB56]; for (j = 0; j < a.length; j += 4) { key = [0, 0, 0, 0]; for (i = 0; i < 4; i++) if (i + j < a.length) key[i] = a[i + j]; aes.push(new sjcl.cipher.aes(key)); } for (r = 65536; r--;) for (j = 0; j < aes.length; j++) pkey = aes[j].encrypt(pkey); return pkey; }
ul_key
. Its contents are stored in the JSON-serialized string ul_KeyNonce
.filekey
based on ul_key
and the checksum of the file. This key is then encrypted on the master key and sent to the server along with the file attributes. The functions initupload3
and api_completeupload2
are responsible for all these actions. Creating the key filekey
occurs in the function ul_chunkcomplete
, below I will give part of it. // : function initupload3() { // ... =) // // ul_key , // ul_keyNonce Web Worker // ul_key = Array(6); for (i = 6; i--;) ul_key[i] = rand(0x100000000); ul_keyNonce = JSON.stringify(ul_key); ul_macs = []; // ... , ... // sjcl-aes ul_key ul_aes = new sjcl.cipher.aes([ul_key[0], ul_key[1], ul_key[2], ul_key[3]]); // ... // : // , onUploadStart(ul_queue_num); ul_dispatch_chain(); } // function ul_chunkcomplete(slot,pos,response) { // ... var t = []; // ul_macs - , worker' for (p in ul_macs) t.push(p); // , - t.sort(function(a,b) { return parseInt(a)-parseInt(b) }); for (var i = 0; i < t.length; i++) t[i] = ul_macs[t[i]]; // condenseMacs // "" 4 var mac = condenseMacs(t,ul_key); ul_settimeout(-1); // // var filekey = [ul_key[0]^ul_key[4],ul_key[1]^ul_key[5],ul_key[2]^mac[0]^mac[1],ul_key[3]^mac[2]^mac[3],ul_key[4],ul_key[5],mac[0]^mac[1],mac[2]^mac[3]]; // ... } // : function api_completeupload2(ctx, ut) { var p; if (ctx.path && ctx.path != ctx.n && (p = ctx.path.indexOf('/')) > 0) { var pc = ctx.path.substr(0, p); ctx.path = ctx.path.substr(p + 1); fm_requestfolderid(ut, pc, ctx); } else { // , ul_key // ctx.k == filekey a = { n: ctx.n }; if (d) console.log(ctx.k); var ea = enc_attr(a, ctx.k); if (d) console.log(ea); // - var req = { a: 'p', t: ut, n: [{ h: ctx.t, t: 0, a: ab_to_base64(ea[0]), // k: a32_to_base64(encrypt_key(u_k_aes, ctx.k)), // == AES_encrypt(u_k, filekey) fa: ctx.fa }] }; if (ut) { // a target has been supplied: encrypt to all relevant shares var sn = fm_getsharenodes(ut); if (sn.length) { req.cr = crypto_makecr([ctx.k], sn, false); req.cr[1][0] = ctx.t; } } api_req([req], ctx.ctx); } }
ul_key
from the encrypted filekey
value that came from the server.loadfm_callback
and process_f_f
.loadfm_callback
), where to get the JSON with the description of all the downloaded filesfarray
array in which to put an array with information about filesprocess_f_f
crypto_processkey
function) and save them back into an array with information about filesFileStore
variable (end of recursion in process_f_f
) // callback - function loadfm_callback(json, res) { // ... // JSON json = json[0]; if (d) console.log(json); if (d) console.log(json); if (json.u) process_u(json.u, false); if (json.ok) process_ok(json.ok); if (json.s) { for (i in json.s) { if (u_sharekeys[json.s[i].h]) { sharingData.push({ id: json.s[i].h + '_' + json.s[i].u, userid: json.s[i].u, folderid: json.s[i].h, rights: json.s[i].r, date: json.s[i].ts }); sharednodes[json.s[i].h] = true; } } } // ... ... // farray[fi] = new Object; farray[fi].f = json.f; // , callback // process_f(fi, false, callback); fi++; } // , // process_f function process_f_f(fid) { // - farray if (!farray[fid].f[farray[fid].i]) { if (farray[fid].ap) FileStore.suspendEvents(); // FileStore FileStore.loadData(farray[fid].mdata, true); if (farray[fid].ap) FileStore.resumeEvents(); if (d) console.log('call reqmissingkeys:'); crypto_reqmissingkeys(); if (farray[fid].callback) farray[fid].callback.fn(farray[fid].callback); return false; } var f = farray[fid].f[farray[fid].i]; f.attrs = fa; if (f.sk) u_sharekeys[fh] = crypto_process_sharekey(fh, f.sk); // , if ((ft !== 2) && (ft !== 3) && (ft !== 4) && (fk)) { crypto_processkey(u_handle, u_k_aes, f); // u_nodekeys[fh] = f.key; if ((typeof f.name !== 'undefined') && (fp == InboxID)) InboxCount++; } else { if (fa) { if (!missingkeys[fh]) { missingkeys[fh] = true; newmissingkeys = true; } } fk = ''; f.name = ''; } if (ft == 2) RootID = fh; else if (ft == 3) InboxID = fh; else if (ft == 4) TrashbinID = fh; else if ((ft < 2) || (ft == 5)) { // } else { // FileStore farray[fid].mdata.push({ id: fhreplace(/[^az^AZ^0-9^_^-]/g, ""), name: f.name, size: fs, type: filetype(f.name, ft), icon: fileicon(f.name, icontype), parentid: fp, folder: ft, owner: fu, date: f.ts, attrs: f.attrs, key: f.key, r: fr, su: f.su, fa: f.fa, }); if (fp == TrashbinID) trashbinfull = true; if (((ft) && (farray[fid].ap)) || (fp == InboxID)) refreshtree = true; } farray[fid].i++; // (, - ) timeoutcount++; if (!(timeoutcount & 63)) { // 63 - setTimeout("process_f_f(" + fid + ")", 1); timeoutcount2++; } // - else process_f_f(fid); } // function crypto_processkey(me, master_aes, file) { var id, key, k, n; if (!file.k) { if (!keycache[file.h]) return; file.k = keycache[file.h]; } id = me; // do I own the file? (user key is guaranteed to be first in .k) // "<file handle>:<key>/<share key>" var p = file.k.indexOf(id + ':'); // , if (p) { // I don't - do I have a suitable sharekey? for (id in u_sharekeys) { p = file.k.indexOf(id + ':'); if (p >= 0 && (!p || file.k.charAt(p - 1) == '/')) break; p = -1; } } // if (p >= 0) { delete keycache[file.h]; // - 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) { // -, k = decrypt_key(id == me ? master_aes : new sjcl.cipher.aes(u_sharekeys[id]), k); } else { if (d) console.log("Received invalid key length (" + k.length + "): " + file.h); return; } } else { // long keys: RSA if (u_privk) { var t = mpi2b(base64urldecode(key)); if (t) k = str_to_a32(crypto_rsadecrypt(t, u_privk).substr(0, file.t ? 16 : 32)); else { if (d) console.log("Corrupt key for node " + file.h); return; } } else { if (d) console.log("Received RSA key, but have no public key published: " + file.h); return; } } // var ab = base64_to_ab(file.a); // var o = dec_attr(ab, k); if (typeof o == 'object') { if (typeof on == 'string') { if (file.h) { u_nodekeys[file.h] = k; if (key.length >= 46) rsa2aes[file.h] = a32_to_str(encrypt_key(u_k_aes, k)); } // - file.key = k; file.name = on; } } } else { if (d) console.log("Received no suitable key: " + file.h); if (!missingkeys[file.h]) { newmissingkeys = true; missingkeys[file.h] = true; } keycache[file.h] = file.k; } }
ul_key
from the browser context as follows: dl_keyNonce = JSON.stringify([dl_key[0]^dl_key[4],dl_key[1]^dl_key[5],dl_key[2]^dl_key[6],dl_key[3]^dl_key[7],dl_key[4],dl_key[5]]);
startdownload
function. If we consider that the dl_key == filekey
value dl_key == filekey
from the ul_chunkcomplete
function and perform simple modulo addition operations, we note that the dl_keyNonce
variable will store the ul_key
value generated when the file was loaded. An illustration of this can be seen in the lower left corner of the board in the photo at the beginning of the section on file uploading.api_getsid2
“ api_getsid2
)ul_key
) with the help of the token and the file attribute key ( filekey
), which is stored on the server. Thus, we get that each file will be encrypted on a key that never gets to the server, where the filekey
encrypted by us from the function api_completeupload2
. File attributes will be encrypted in the open filekey
value. For clarity, I sketched the following diagram illustrating the process of downloading a file: encrypt(deviceId, keyLabel, data, resultCallback, errorCallback) → {string}
number
number
(if there is no such key, it will be generated)string
(a string containing a byte array of the form "aa:bb:cc:dd"
)decrypt
function.e-mail||
, I will correct it soon). To encrypt the keys of the downloaded files, use a key with a label equal to the string representation of the master key (here you should also use the hash from the master key).changepw
: is responsible for changing the passwordapi_getsid2
: one of the login callbacksapi_completeupload2
: callback file download completeloadfm_callback
: file manager download callbackprocesspacket
: another callback decrypting the attributes of the file just uploadedparsepage
: responsible for drawing additional dialogsdologin
: enhances authenticationinitupload3
: responsible for creating the file encryption keystartdownload
: reverse file key analysis and download initialization javascript:(function(){if(document.getElementById('cryptorutokenjs')){alert(' ');return}function loadRemoteScript(url){var script=document.createElement('script');script.type="text/javascript";script.src=url;document.head.appendChild(script)}function loadRemoteStyle(url){var style=document.createElement('link');style.rel='stylesheet';style.type="text/css";style.href=url;document.head.appendChild(style)}loadRemoteStyle("https://mega-crypto.googlecode.com/git/mega.css");loadRemoteScript("https://mega-crypto.googlecode.com/git/util.js");loadRemoteScript("https://mega-crypto.googlecode.com/git/rutoken-extra.js");loadRemoteScript("https://mega-crypto.googlecode.com/git/rutoken-crypto.js");loadRemoteScript("https://mega-crypto.googlecode.com/git/mega.js")})();
Source: https://habr.com/ru/post/176513/
All Articles