Remove import-froms for crypto.

This commit is contained in:
Ethan Dalool 2020-03-23 21:07:41 -07:00
parent 6d59de416e
commit 015e5a2773

View file

@ -19,12 +19,6 @@ from tenacity import retry, wait_exponential, retry_if_exception_type
from . import crypto from . import crypto
from . import errors from . import errors
from .crypto import (
a32_to_base64, encrypt_key, base64_url_encode, encrypt_attr, base64_to_a32,
base64_url_decode, decrypt_attr, a32_to_str, get_chunks, str_to_a32,
decrypt_key, mpi_to_int, stringhash, prepare_key, make_id, makebyte,
modular_inverse, interleave_xor_8
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -41,7 +35,7 @@ class Mega:
self.timeout = 160 # max secs to wait for resp from api requests self.timeout = 160 # max secs to wait for resp from api requests
self.sid = None self.sid = None
self.sequence_num = random.randint(0, 0xFFFFFFFF) self.sequence_num = random.randint(0, 0xFFFFFFFF)
self.request_id = make_id(10) self.request_id = crypto.make_id(10)
self._trash_folder_node_id = None self._trash_folder_node_id = None
self.shared_keys = {} self.shared_keys = {}
@ -128,22 +122,22 @@ class Mega:
(account_version, user_salt) = self._api_account_version_and_salt(email) (account_version, user_salt) = self._api_account_version_and_salt(email)
logger.debug('User account is version %d.', account_version) logger.debug('User account is version %d.', account_version)
if account_version >= 2: if account_version >= 2:
user_salt = base64_to_a32(user_salt) user_salt = crypto.base64_to_a32(user_salt)
# Parameters specified by MEGA's webclient security.js, search for # Parameters specified by MEGA's webclient security.js, search for
# "numOfIterations" and deriveKeyWithWebCrypto to cross-reference. # "numOfIterations" and deriveKeyWithWebCrypto to cross-reference.
pbkdf2_key = hashlib.pbkdf2_hmac( pbkdf2_key = hashlib.pbkdf2_hmac(
hash_name='sha512', hash_name='sha512',
password=password.encode(), password=password.encode(),
salt=a32_to_str(user_salt), salt=crypto.a32_to_str(user_salt),
iterations=100000, iterations=100000,
dklen=32 dklen=32
) )
password_aes = str_to_a32(pbkdf2_key[:16]) password_aes = crypto.str_to_a32(pbkdf2_key[:16])
user_hash = base64_url_encode(pbkdf2_key[-16:]) user_hash = crypto.base64_url_encode(pbkdf2_key[-16:])
else: else:
password_a32 = str_to_a32(password) password_a32 = crypto.str_to_a32(password)
password_aes = prepare_key(password_a32) password_aes = crypto.prepare_key(password_a32)
user_hash = stringhash(email, password_aes) user_hash = crypto.stringhash(email, password_aes)
resp = self._api_start_session(email, user_hash) resp = self._api_start_session(email, user_hash)
if isinstance(resp, int): if isinstance(resp, int):
@ -156,10 +150,10 @@ class Mega:
password_key = [random.randint(0, 0xFFFFFFFF)] * 4 password_key = [random.randint(0, 0xFFFFFFFF)] * 4
session_self_challenge = [random.randint(0, 0xFFFFFFFF)] * 4 session_self_challenge = [random.randint(0, 0xFFFFFFFF)] * 4
k = a32_to_base64(encrypt_key(master_key, password_key)) k = crypto.a32_to_base64(crypto.encrypt_key(master_key, password_key))
ts = a32_to_str(session_self_challenge) ts = crypto.a32_to_str(session_self_challenge)
ts += a32_to_str(encrypt_key(session_self_challenge, master_key)) ts += crypto.a32_to_str(crypto.encrypt_key(session_self_challenge, master_key))
ts = base64_url_encode(ts) ts = crypto.base64_url_encode(ts)
user = self._api_request({'a': 'up', 'k': k, 'ts': ts}) user = self._api_request({'a': 'up', 'k': k, 'ts': ts})
resp = self._api_start_session(user) resp = self._api_start_session(user)
@ -168,24 +162,24 @@ class Mega:
self._login_process(resp, password_key) self._login_process(resp, password_key)
def _login_process(self, resp, password): def _login_process(self, resp, password):
encrypted_master_key = base64_to_a32(resp['k']) encrypted_master_key = crypto.base64_to_a32(resp['k'])
self.master_key = decrypt_key(encrypted_master_key, password) self.master_key = crypto.decrypt_key(encrypted_master_key, password)
# tsid is for temporary sessions # tsid is for temporary sessions
if 'tsid' in resp: if 'tsid' in resp:
tsid = base64_url_decode(resp['tsid']) tsid = crypto.base64_url_decode(resp['tsid'])
key_encrypted = a32_to_str( key_encrypted = crypto.a32_to_str(
encrypt_key(str_to_a32(tsid[:16]), self.master_key) crypto.encrypt_key(crypto.str_to_a32(tsid[:16]), self.master_key)
) )
if key_encrypted == tsid[-16:]: if key_encrypted == tsid[-16:]:
self.sid = resp['tsid'] self.sid = resp['tsid']
# csid is for user logins # csid is for user logins
elif 'csid' in resp: elif 'csid' in resp:
encrypted_rsa_private_key = base64_to_a32(resp['privk']) encrypted_rsa_private_key = crypto.base64_to_a32(resp['privk'])
rsa_private_key = decrypt_key( rsa_private_key = crypto.decrypt_key(
encrypted_rsa_private_key, self.master_key encrypted_rsa_private_key, self.master_key
) )
private_key = a32_to_str(rsa_private_key) private_key = crypto.a32_to_str(rsa_private_key)
# The private_key contains 4 MPI integers concatenated together. # The private_key contains 4 MPI integers concatenated together.
rsa_private_key = [0, 0, 0, 0] rsa_private_key = [0, 0, 0, 0]
for i in range(4): for i in range(4):
@ -195,7 +189,7 @@ class Mega:
bytelength = math.ceil(bitlength / 8) bytelength = math.ceil(bitlength / 8)
# Add 2 bytes to accommodate the MPI header # Add 2 bytes to accommodate the MPI header
bytelength += 2 bytelength += 2
rsa_private_key[i] = mpi_to_int(private_key[:bytelength]) rsa_private_key[i] = crypto.mpi_to_int(private_key[:bytelength])
private_key = private_key[bytelength:] private_key = private_key[bytelength:]
first_factor_p = rsa_private_key[0] first_factor_p = rsa_private_key[0]
@ -206,7 +200,7 @@ class Mega:
# RSA.construct and it does not seem to be necessary. # RSA.construct and it does not seem to be necessary.
rsa_modulus_n = first_factor_p * second_factor_q rsa_modulus_n = first_factor_p * second_factor_q
phi = (first_factor_p - 1) * (second_factor_q - 1) phi = (first_factor_p - 1) * (second_factor_q - 1)
public_exponent_e = modular_inverse(private_exponent_d, phi) public_exponent_e = crypto.modular_inverse(private_exponent_d, phi)
rsa_components = ( rsa_components = (
rsa_modulus_n, rsa_modulus_n,
@ -217,11 +211,11 @@ class Mega:
) )
rsa_decrypter = RSA.construct(rsa_components) rsa_decrypter = RSA.construct(rsa_components)
encrypted_sid = mpi_to_int(base64_url_decode(resp['csid'])) encrypted_sid = crypto.mpi_to_int(crypto.base64_url_decode(resp['csid']))
sid = '%x' % rsa_decrypter._decrypt(encrypted_sid) sid = '%x' % rsa_decrypter._decrypt(encrypted_sid)
sid = binascii.unhexlify('0' + sid if len(sid) % 2 else sid) sid = binascii.unhexlify('0' + sid if len(sid) % 2 else sid)
self.sid = base64_url_encode(sid[:43]) self.sid = crypto.base64_url_encode(sid[:43])
def _parse_url(self, url): def _parse_url(self, url):
""" """
@ -247,13 +241,13 @@ class Mega:
key = None key = None
# my objects # my objects
if uid in keys: if uid in keys:
key = decrypt_key(base64_to_a32(keys[uid]), self.master_key) key = crypto.decrypt_key(crypto.base64_to_a32(keys[uid]), self.master_key)
# shared folders # shared folders
elif 'su' in file and 'sk' in file and ':' in file['k']: elif 'su' in file and 'sk' in file and ':' in file['k']:
shared_key = decrypt_key( shared_key = crypto.decrypt_key(
base64_to_a32(file['sk']), self.master_key crypto.base64_to_a32(file['sk']), self.master_key
) )
key = decrypt_key(base64_to_a32(keys[file['h']]), shared_key) key = crypto.decrypt_key(crypto.base64_to_a32(keys[file['h']]), shared_key)
if file['su'] not in self.shared_keys: if file['su'] not in self.shared_keys:
self.shared_keys[file['su']] = {} self.shared_keys[file['su']] = {}
self.shared_keys[file['su']][file['h']] = shared_key self.shared_keys[file['su']][file['h']] = shared_key
@ -263,26 +257,26 @@ class Mega:
shared_key = self.shared_keys[file['u']][hkey] shared_key = self.shared_keys[file['u']][hkey]
if hkey in keys: if hkey in keys:
key = keys[hkey] key = keys[hkey]
key = decrypt_key(base64_to_a32(key), shared_key) key = crypto.decrypt_key(crypto.base64_to_a32(key), shared_key)
break break
if file['h'] and file['h'] in self.shared_keys.get('EXP', ()): if file['h'] and file['h'] in self.shared_keys.get('EXP', ()):
shared_key = self.shared_keys['EXP'][file['h']] shared_key = self.shared_keys['EXP'][file['h']]
encrypted_key = str_to_a32( encrypted_key = crypto.str_to_a32(
base64_url_decode(file['k'].split(':')[-1]) crypto.base64_url_decode(file['k'].split(':')[-1])
) )
key = decrypt_key(encrypted_key, shared_key) key = crypto.decrypt_key(encrypted_key, shared_key)
file['shared_folder_key'] = shared_key file['shared_folder_key'] = shared_key
if key is not None: if key is not None:
if file['t'] == NODE_TYPE_FILE: if file['t'] == NODE_TYPE_FILE:
k = interleave_xor_8(key) k = crypto.interleave_xor_8(key)
file['iv'] = key[4:6] + (0, 0) file['iv'] = key[4:6] + (0, 0)
file['meta_mac'] = key[6:8] file['meta_mac'] = key[6:8]
else: else:
k = key k = key
file['key'] = key file['key'] = key
file['k'] = k file['k'] = k
attributes = base64_url_decode(file['a']) attributes = crypto.base64_url_decode(file['a'])
attributes = decrypt_attr(attributes, k) attributes = crypto.decrypt_attr(attributes, k)
file['a'] = attributes file['a'] = attributes
# other => wrong object # other => wrong object
elif file['k'] == '': elif file['k'] == '':
@ -309,8 +303,8 @@ class Mega:
ok_dict = {} ok_dict = {}
for ok_item in files.get('ok', []): for ok_item in files.get('ok', []):
shared_key = decrypt_key( shared_key = crypto.decrypt_key(
base64_to_a32(ok_item['k']), self.master_key crypto.base64_to_a32(ok_item['k']), self.master_key
) )
ok_dict[ok_item['h']] = shared_key ok_dict[ok_item['h']] = shared_key
for s_item in files.get('s', []): for s_item in files.get('s', []):
@ -414,8 +408,8 @@ class Mega:
file = file['f'][0] file = file['f'][0]
public_handle = self._api_request({'a': 'l', 'n': file['h']}) public_handle = self._api_request({'a': 'l', 'n': file['h']})
file_key = file['k'][file['k'].index(':') + 1:] file_key = file['k'][file['k'].index(':') + 1:]
decrypted_key = a32_to_base64( decrypted_key = crypto.a32_to_base64(
decrypt_key(base64_to_a32(file_key), self.master_key) crypto.decrypt_key(crypto.base64_to_a32(file_key), self.master_key)
) )
return ( return (
f'{self.schema}://{self.domain}' f'{self.schema}://{self.domain}'
@ -439,7 +433,7 @@ class Mega:
"Can't get a public link from that file " "Can't get a public link from that file "
"(is this a shared file?)" "(is this a shared file?)"
) )
decrypted_key = a32_to_base64(file['key']) decrypted_key = crypto.a32_to_base64(file['key'])
return ( return (
f'{self.schema}://{self.domain}' f'{self.schema}://{self.domain}'
f'/#!{public_handle}!{decrypted_key}' f'/#!{public_handle}!{decrypted_key}'
@ -465,7 +459,7 @@ class Mega:
"Can't get a public link from that file " "Can't get a public link from that file "
"(is this a shared file?)" "(is this a shared file?)"
) )
decrypted_key = a32_to_base64(file['shared_folder_key']) decrypted_key = crypto.a32_to_base64(file['shared_folder_key'])
return ( return (
f'{self.schema}://{self.domain}' f'{self.schema}://{self.domain}'
f'/#F!{public_handle}!{decrypted_key}' f'/#F!{public_handle}!{decrypted_key}'
@ -661,18 +655,18 @@ class Mega:
except (errors.RequestError, KeyError): except (errors.RequestError, KeyError):
pass pass
master_key_cipher = AES.new(a32_to_str(self.master_key), AES.MODE_ECB) master_key_cipher = AES.new(crypto.a32_to_str(self.master_key), AES.MODE_ECB)
ha = base64_url_encode( ha = crypto.base64_url_encode(
master_key_cipher.encrypt(node_data['h'].encode("utf8") + node_data['h'].encode("utf8")) master_key_cipher.encrypt(node_data['h'].encode("utf8") + node_data['h'].encode("utf8"))
) )
share_key = secrets.token_bytes(16) share_key = secrets.token_bytes(16)
ok = base64_url_encode(master_key_cipher.encrypt(share_key)) ok = crypto.base64_url_encode(master_key_cipher.encrypt(share_key))
share_key_cipher = AES.new(share_key, AES.MODE_ECB) share_key_cipher = AES.new(share_key, AES.MODE_ECB)
node_key = node_data['k'] node_key = node_data['k']
encrypted_node_key = base64_url_encode( encrypted_node_key = crypto.base64_url_encode(
share_key_cipher.encrypt(a32_to_str(node_key)) share_key_cipher.encrypt(crypto.a32_to_str(node_key))
) )
node_id = node_data['h'] node_id = node_data['h']
@ -718,7 +712,7 @@ class Mega:
): ):
if file is None: if file is None:
if is_public: if is_public:
file_key = base64_to_a32(file_key) file_key = crypto.base64_to_a32(file_key)
request = { request = {
'a': 'g', 'a': 'g',
'g': 1, 'g': 1,
@ -732,7 +726,7 @@ class Mega:
} }
file_data = self._api_request(request) file_data = self._api_request(request)
k = interleave_xor_8(file_key) k = crypto.interleave_xor_8(file_key)
iv = file_key[4:6] + (0, 0) iv = file_key[4:6] + (0, 0)
meta_mac = file_key[6:8] meta_mac = file_key[6:8]
else: else:
@ -748,8 +742,8 @@ class Mega:
raise errors.RequestError('File not accessible anymore') raise errors.RequestError('File not accessible anymore')
file_url = file_data['g'] file_url = file_data['g']
file_size = file_data['s'] file_size = file_data['s']
attribs = base64_url_decode(file_data['at']) attribs = crypto.base64_url_decode(file_data['at'])
attribs = decrypt_attr(attribs, k) attribs = crypto.decrypt_attr(attribs, k)
if dest_filename is not None: if dest_filename is not None:
file_name = dest_filename file_name = dest_filename
@ -766,7 +760,7 @@ class Mega:
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile(
mode='w+b', prefix='megapy_', delete=False mode='w+b', prefix='megapy_', delete=False
) as temp_output_file: ) as temp_output_file:
k_str = a32_to_str(k) k_str = crypto.a32_to_str(k)
counter = Counter.new( counter = Counter.new(
128, initial_value=((iv[0] << 32) + iv[1]) << 64 128, initial_value=((iv[0] << 32) + iv[1]) << 64
) )
@ -774,9 +768,9 @@ class Mega:
mac_str = '\0' * 16 mac_str = '\0' * 16
mac_encryptor = AES.new(k_str, AES.MODE_CBC, mac_str.encode("utf8")) mac_encryptor = AES.new(k_str, AES.MODE_CBC, mac_str.encode("utf8"))
iv_str = a32_to_str([iv[0], iv[1], iv[0], iv[1]]) iv_str = crypto.a32_to_str([iv[0], iv[1], iv[0], iv[1]])
for chunk_start, chunk_size in get_chunks(file_size): for chunk_start, chunk_size in crypto.get_chunks(file_size):
chunk = input_file.read(chunk_size) chunk = input_file.read(chunk_size)
chunk = aes.decrypt(chunk) chunk = aes.decrypt(chunk)
temp_output_file.write(chunk) temp_output_file.write(chunk)
@ -801,7 +795,7 @@ class Mega:
logger.info( logger.info(
'%s of %s downloaded', file_info.st_size, file_size '%s of %s downloaded', file_info.st_size, file_size
) )
file_mac = str_to_a32(mac_str) file_mac = crypto.str_to_a32(mac_str)
# check mac integrity # check mac integrity
if ( if (
file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3] file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3]
@ -826,7 +820,7 @@ class Mega:
# generate random aes key (128) for file # generate random aes key (128) for file
ul_key = [random.randint(0, 0xFFFFFFFF) for _ in range(6)] ul_key = [random.randint(0, 0xFFFFFFFF) for _ in range(6)]
k_str = a32_to_str(ul_key[:4]) k_str = crypto.a32_to_str(ul_key[:4])
count = Counter.new( count = Counter.new(
128, initial_value=((ul_key[4] << 32) + ul_key[5]) << 64 128, initial_value=((ul_key[4] << 32) + ul_key[5]) << 64
) )
@ -837,9 +831,9 @@ class Mega:
mac_str = '\0' * 16 mac_str = '\0' * 16
mac_encryptor = AES.new(k_str, AES.MODE_CBC, mac_str.encode("utf8")) mac_encryptor = AES.new(k_str, AES.MODE_CBC, mac_str.encode("utf8"))
iv_str = a32_to_str([ul_key[4], ul_key[5], ul_key[4], ul_key[5]]) iv_str = crypto.a32_to_str([ul_key[4], ul_key[5], ul_key[4], ul_key[5]])
if file_size > 0: if file_size > 0:
for chunk_start, chunk_size in get_chunks(file_size): for chunk_start, chunk_size in crypto.get_chunks(file_size):
chunk = input_file.read(chunk_size) chunk = input_file.read(chunk_size)
upload_progress += len(chunk) upload_progress += len(chunk)
@ -856,7 +850,7 @@ class Mega:
block = chunk[i:i + 16] block = chunk[i:i + 16]
if len(block) % 16: if len(block) % 16:
block += makebyte('\0' * (16 - len(block) % 16)) block += crypto.makebyte('\0' * (16 - len(block) % 16))
mac_str = mac_encryptor.encrypt(encryptor.encrypt(block)) mac_str = mac_encryptor.encrypt(encryptor.encrypt(block))
# encrypt file and upload # encrypt file and upload
@ -879,7 +873,7 @@ class Mega:
logger.info('Chunks uploaded') logger.info('Chunks uploaded')
logger.info('Setting attributes to complete upload') logger.info('Setting attributes to complete upload')
logger.info('Computing attributes') logger.info('Computing attributes')
file_mac = str_to_a32(mac_str) file_mac = crypto.str_to_a32(mac_str)
# determine meta mac # determine meta mac
meta_mac = (file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3]) meta_mac = (file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3])
@ -887,15 +881,15 @@ class Mega:
dest_filename = dest_filename or os.path.basename(filename) dest_filename = dest_filename or os.path.basename(filename)
attribs = {'n': dest_filename} attribs = {'n': dest_filename}
encrypt_attribs = base64_url_encode( encrypt_attribs = crypto.base64_url_encode(
encrypt_attr(attribs, ul_key[:4]) crypto.encrypt_attr(attribs, ul_key[:4])
) )
key = [ key = [
ul_key[0] ^ ul_key[4], ul_key[1] ^ ul_key[5], ul_key[0] ^ ul_key[4], ul_key[1] ^ ul_key[5],
ul_key[2] ^ meta_mac[0], ul_key[3] ^ meta_mac[1], ul_key[4], ul_key[2] ^ meta_mac[0], ul_key[3] ^ meta_mac[1], ul_key[4],
ul_key[5], meta_mac[0], meta_mac[1] ul_key[5], meta_mac[0], meta_mac[1]
] ]
encrypted_key = a32_to_base64(encrypt_key(key, self.master_key)) encrypted_key = crypto.a32_to_base64(crypto.encrypt_key(key, self.master_key))
logger.info('Sending request to update attributes') logger.info('Sending request to update attributes')
# update attributes # update attributes
request = { request = {
@ -921,8 +915,8 @@ class Mega:
# encrypt attribs # encrypt attribs
attribs = {'n': name} attribs = {'n': name}
encrypt_attribs = base64_url_encode(encrypt_attr(attribs, ul_key[:4])) encrypt_attribs = crypto.base64_url_encode(crypto.encrypt_attr(attribs, ul_key[:4]))
encrypted_key = a32_to_base64(encrypt_key(ul_key[:4], self.master_key)) encrypted_key = crypto.a32_to_base64(crypto.encrypt_key(ul_key[:4], self.master_key))
# update attributes # update attributes
request = { request = {
@ -973,9 +967,9 @@ class Mega:
# create new attribs # create new attribs
attribs = {'n': new_name} attribs = {'n': new_name}
# encrypt attribs # encrypt attribs
encrypt_attribs = base64_url_encode(encrypt_attr(attribs, file['k'])) encrypt_attribs = crypto.base64_url_encode(crypto.encrypt_attr(attribs, file['k']))
encrypted_key = a32_to_base64( encrypted_key = crypto.a32_to_base64(
encrypt_key(file['key'], self.master_key) crypto.encrypt_key(file['key'], self.master_key)
) )
# update attributes # update attributes
request = { request = {
@ -1115,7 +1109,7 @@ class Mega:
else: else:
raise TypeError(f'Invalid dest_node {dest_node}.') raise TypeError(f'Invalid dest_node {dest_node}.')
folder_key = base64_to_a32(folder_key) folder_key = crypto.base64_to_a32(folder_key)
nodes = self.get_public_folder_files(folder_handle) nodes = self.get_public_folder_files(folder_handle)
@ -1134,8 +1128,8 @@ class Mega:
import_list = [] import_list = []
for node in nodes: for node in nodes:
k = node['k'].split(':')[1] k = node['k'].split(':')[1]
k = decrypt_key(base64_to_a32(k), folder_key) k = crypto.decrypt_key(crypto.base64_to_a32(k), folder_key)
new_k = a32_to_base64(encrypt_key(k, self.master_key)) new_k = crypto.a32_to_base64(crypto.encrypt_key(k, self.master_key))
node_import_args = { node_import_args = {
'h': node['h'], 'h': node['h'],
@ -1147,7 +1141,7 @@ class Mega:
# This is the root public folder. # This is the root public folder.
if dest_name is not None: if dest_name is not None:
new_a = {'n': dest_name} new_a = {'n': dest_name}
new_a = base64_url_encode(encrypt_attr(new_a, k)) new_a = crypto.base64_url_encode(crypto.encrypt_attr(new_a, k))
node_import_args['a'] = new_a node_import_args['a'] = new_a
else: else:
node_import_args['a'] = node['a'] node_import_args['a'] = node['a']
@ -1181,11 +1175,11 @@ class Mega:
if 'at' not in data or 's' not in data: if 'at' not in data or 's' not in data:
raise ValueError("Unexpected result", data) raise ValueError("Unexpected result", data)
key = base64_to_a32(file_key) key = crypto.base64_to_a32(file_key)
k = interleave_xor_8(key) k = crypto.interleave_xor_8(key)
size = data['s'] size = data['s']
unencrypted_attrs = decrypt_attr(base64_url_decode(data['at']), k) unencrypted_attrs = crypto.decrypt_attr(crypto.base64_url_decode(data['at']), k)
if not unencrypted_attrs: if not unencrypted_attrs:
return None return None
result = {'size': size, 'name': unencrypted_attrs['n']} result = {'size': size, 'name': unencrypted_attrs['n']}
@ -1206,11 +1200,11 @@ class Mega:
pl_info = self.get_public_file_info(file_handle, file_key) pl_info = self.get_public_file_info(file_handle, file_key)
dest_name = pl_info['name'] dest_name = pl_info['name']
key = base64_to_a32(file_key) key = crypto.base64_to_a32(file_key)
k = interleave_xor_8(key) k = crypto.interleave_xor_8(key)
encrypted_key = a32_to_base64(encrypt_key(key, self.master_key)) encrypted_key = crypto.a32_to_base64(crypto.encrypt_key(key, self.master_key))
encrypted_name = base64_url_encode(encrypt_attr({'n': dest_name}, k)) encrypted_name = crypto.base64_url_encode(crypto.encrypt_attr({'n': dest_name}, k))
request = { request = {
'a': 'p', 'a': 'p',
't': dest_node['h'], 't': dest_node['h'],