def _process_file(self, file, shared_keys): if file['t'] == 0 or file['t'] == 1: keys = dict( keypart.split(':', 1) for keypart in file['k'].split('/') if ':' in keypart) uid = file['u'] key = None # my objects if uid in keys: key = decrypt_key(base64_to_a32(keys[uid]), self.master_key) # shared folders elif 'su' in file and 'sk' in file and ':' in file['k']: shared_key = decrypt_key(base64_to_a32(file['sk']), self.master_key) key = decrypt_key(base64_to_a32(keys[file['h']]), shared_key) if file['su'] not in shared_keys: shared_keys[file['su']] = {} shared_keys[file['su']][file['h']] = shared_key # shared files elif file['u'] and file['u'] in shared_keys: for hkey in shared_keys[file['u']]: shared_key = shared_keys[file['u']][hkey] if hkey in keys: key = keys[hkey] key = decrypt_key(base64_to_a32(key), shared_key) break if file['h'] and file['h'] in shared_keys.get('EXP', ()): shared_key = shared_keys['EXP'][file['h']] encrypted_key = str_to_a32( base64_url_decode(file['k'].split(':')[-1])) key = decrypt_key(encrypted_key, shared_key) file['shared_folder_key'] = shared_key if key is not None: # file if file['t'] == 0: k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7]) file['iv'] = key[4:6] + (0, 0) file['meta_mac'] = key[6:8] # folder else: k = key file['key'] = key file['k'] = k attributes = base64_url_decode(file['a']) attributes = decrypt_attr(attributes, k) file['a'] = attributes # other => wrong object elif file['k'] == '': file['a'] = False elif file['t'] == 2: self.root_id = file['h'] file['a'] = {'n': 'Cloud Drive'} elif file['t'] == 3: self.inbox_id = file['h'] file['a'] = {'n': 'Inbox'} elif file['t'] == 4: self.trashbin_id = file['h'] file['a'] = {'n': 'Rubbish Bin'} return file
def _login_common(self, res, password): if res in (-2, -9): raise MegaIncorrectPasswordExcetion("Incorrect e-mail and/or password.") enc_master_key = base64_to_a32(res['k']) self.master_key = decrypt_key(enc_master_key, password) if 'tsid' in res: tsid = base64urldecode(res['tsid']) key_encrypted = a32_to_str( encrypt_key(str_to_a32(tsid[:16]), self.master_key)) if key_encrypted == tsid[-16:]: self.sid = res['tsid'] elif 'csid' in res: enc_rsa_priv_key = base64_to_a32(res['privk']) rsa_priv_key = decrypt_key(enc_rsa_priv_key, self.master_key) privk = a32_to_str(rsa_priv_key) self.rsa_priv_key = [0, 0, 0, 0] for i in range(4): l = ((ord(privk[0]) * 256 + ord(privk[1]) + 7) / 8) + 2 self.rsa_priv_key[i] = mpi2int(privk[:l]) privk = privk[l:] enc_sid = mpi2int(base64urldecode(res['csid'])) decrypter = RSA.construct( (self.rsa_priv_key[0] * self.rsa_priv_key[1], 0, self.rsa_priv_key[2], self.rsa_priv_key[0], self.rsa_priv_key[1])) sid = '%x' % decrypter.key._decrypt(enc_sid) sid = binascii.unhexlify('0' + sid if len(sid) % 2 else sid) self.sid = base64urlencode(sid[:43])
def _login_process(self, resp, password): encrypted_master_key = base64_to_a32(resp['k']) self.master_key = decrypt_key(encrypted_master_key, password) if 'tsid' in resp: tsid = base64_url_decode(resp['tsid']) key_encrypted = a32_to_str( encrypt_key(str_to_a32(tsid[:16]), self.master_key)) if key_encrypted == tsid[-16:]: self.sid = resp['tsid'] elif 'csid' in resp: encrypted_rsa_private_key = base64_to_a32(resp['privk']) rsa_private_key = decrypt_key(encrypted_rsa_private_key, self.master_key) private_key = a32_to_str(rsa_private_key) # The private_key contains 4 MPI integers concatenated together. rsa_private_key = [0, 0, 0, 0] for i in range(4): # An MPI integer has a 2-byte header which describes the number # of bits in the integer. bitlength = (private_key[0] * 256) + private_key[1] bytelength = math.ceil(bitlength / 8) # Add 2 bytes to accommodate the MPI header bytelength += 2 rsa_private_key[i] = mpi_to_int(private_key[:bytelength]) private_key = private_key[bytelength:] first_factor_p = rsa_private_key[0] second_factor_q = rsa_private_key[1] private_exponent_d = rsa_private_key[2] # In MEGA's webclient javascript, they assign [3] to a variable # called u, but I do not see how it corresponds to pycryptodome's # RSA.construct and it does not seem to be necessary. rsa_modulus_n = first_factor_p * second_factor_q phi = (first_factor_p - 1) * (second_factor_q - 1) public_exponent_e = modular_inverse(private_exponent_d, phi) rsa_components = ( rsa_modulus_n, public_exponent_e, private_exponent_d, first_factor_p, second_factor_q, ) rsa_decrypter = RSA.construct(rsa_components) encrypted_sid = mpi_to_int(base64_url_decode(resp['csid'])) sid = '%x' % rsa_decrypter._decrypt(encrypted_sid) sid = binascii.unhexlify('0' + sid if len(sid) % 2 else sid) self.sid = base64_url_encode(sid[:43])
def get_files(self): files_data = self.api_req({'a': 'f', 'c': 1}) for file in files_data['f']: if file['t'] in (0, 1): key = file['k'].split(':')[1] key = decrypt_key(base64_to_a32(key), self.master_key) # file if file['t'] == 0: k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7]) # directory else: k = key attributes = base64urldecode(file['a']) attributes = dec_attr(attributes, k) file['a'] = attributes file['k'] = key # Root ("Cloud Drive") elif file['t'] == 2: self.root_id = file['h'] # Inbox elif file['t'] == 3: self.inbox_id = file['h'] # Trash Bin elif file['t'] == 4: self.trashbin_id = file['h'] return files_data
def make_wallet(root_key, date, passphrase, fake_passphrase, kdf_type, salt): """ make_wallet(root_key, date, passphrase, fake_passphrase, kdf_type, salt) root_key :: 32 bytes (pseudo)-random data. date :: Wallet creation date. Integer <= 65535. == ((creation day) - (2013-01-01)) / 7 passphrase :: The passphrase to decrypt the wallet. fake_passphrase :: Alternate passphrase. Decrypts to a different wallet. kdf_type :: The numerical code for the desired KDF type. salt :: A 2-byte random value. Returns a wallet ciphertext, which can be decrypted with the passphrase. Format: [12 bit salt][4 bit KDF type][2 byte creation date][4 byte checksum][32 byte encrypted root_key] Salt goes first because SSSS works better with random most significant bits. """ assert(len(root_key) == 32) assert(date >= 0 and date <= 65535) assert(kdf_type in (0,1,2,8,9)) assert(len(salt) == 2) big_endian_date = chr(date >> 8) + chr(date & 0xff) # Encode the KDF in the bottom 4 bits of the salt kdf_byte = chr( kdf_type + (ord(salt[1]) & 0xF0) ) salt = salt[0] + kdf_byte # Use the provided salt value + the date as the salt new_salt = salt + big_endian_date # 4 byte salt kdf = crypto.kdfs[kdf_type] encrypted_root_key = crypto.encrypt_key(root_key, new_salt, passphrase, kdf) fake_root_key = crypto.decrypt_key(encrypted_root_key, new_salt, fake_passphrase, kdf) decryption_checksum = crypto.make_key_checksum(root_key, fake_root_key) return salt + big_endian_date + decryption_checksum + encrypted_root_key
def get_upload_link(self, file): """ Get a files public link inc. decrypted key Requires upload() response as input """ if 'f' in file: file = file['f'][0] public_handle = self._api_request({'a': 'l', 'n': file['h']}) file_key = file['k'][file['k'].index(':') + 1:] decrypted_key = a32_to_base64( decrypt_key(base64_to_a32(file_key), self.master_key)) return (f'{self.schema}://{self.domain}' f'/#!{public_handle}!{decrypted_key}') else: raise ValueError('''Upload() response required as input, use get_link() for regular file input''')
def _init_shared_keys(self, files, shared_keys): """ Init shared key not associated with a user. Seems to happen when a folder is shared, some files are exchanged and then the folder is un-shared. Keys are stored in files['s'] and files['ok'] """ ok_dict = {} for ok_item in files['ok']: shared_key = decrypt_key(base64_to_a32(ok_item['k']), self.master_key) ok_dict[ok_item['h']] = shared_key for s_item in files['s']: if s_item['u'] not in shared_keys: shared_keys[s_item['u']] = {} if s_item['h'] in ok_dict: shared_keys[s_item['u']][s_item['h']] = ok_dict[s_item['h']] self.shared_keys = shared_keys
def decrypt_wallet(wallet, passphrase): assert(len(wallet) == 2 + 2 + 4 + 32) salt = wallet[0:2] big_endian_date = wallet[2:4] new_salt = salt + big_endian_date decryption_checksum = wallet[4:8] encrypted_root_key = wallet[8:40] kdf_type = ord(wallet[1]) & 0x0F assert(kdf_type in (0,1,2,8,9)) kdf = crypto.kdfs[kdf_type] root_key = crypto.decrypt_key(encrypted_root_key, new_salt, passphrase, kdf) if not crypto.check_key_checksum(root_key, decryption_checksum): raise Exception("Decryption checksum is bad. Is password incorrect?") date = (ord(big_endian_date[0]) << 8) + ord(big_endian_date[1]) return root_key, date