Commit 8a4ea77b authored by monkeyminer's avatar monkeyminer
parents f81a5642 631ca606
Pipeline #522 failed with stages
......@@ -3,10 +3,10 @@
Release History
===============
1.0.7 (unreleased)
1.0.7 (2020-03-25)
------------------
- Nothing changed yet.
- Fix login by calculating public RSA exponent instead of hardcoding.
1.0.6 (2020-02-03)
......
......@@ -11,7 +11,7 @@ Mega.py
|Build Status| |Downloads|
Python library for the Mega.co.nz API, currently supporting:
Python library for the `Mega.co.nz <https://mega.nz/aff=Zo6IxNaHw14>`_ API, currently supporting:
- login
- uploading
......@@ -29,6 +29,14 @@ For more detailed information see API_INFO.md
How To Use
----------
.. _create-mega-account:
Create a Mega account
~~~~~~~~~~~~~~~~~~~~~~~
First, `create an account with Mega <https://mega.nz/aff=Zo6IxNaHw14>`_
.
.. _install-megapy-package:
Install mega.py package
......@@ -36,7 +44,7 @@ Install mega.py package
.. code:: python
#Run the following command, or run setup from the latest github source
Run the following command, or run setup from the latest github source
pip install mega.py
.. _import-megapy:
......
......@@ -11,7 +11,7 @@ norecursedirs = .git
[flake8]
exclude = .git,__pycache__,legacy,build,dist,.tox
max-complexity = 15
ignore = E741,W504
ignore = E741,W504,W503
[yapf]
based_on_style = pep8
......
......@@ -104,11 +104,32 @@ def str_to_a32(b):
def mpi_to_int(s):
"""
A Multi-precision integer is encoded as a series of bytes in big-endian
order. The first two bytes are a header which tell the number of bits in
the integer. The rest of the bytes are the integer.
"""
return int(binascii.hexlify(s[2:]), 16)
def extended_gcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = extended_gcd(b % a, a)
return (g, x - (b // a) * y, y)
def modular_inverse(a, m):
g, x, y = extended_gcd(a, m)
if g != 1:
raise Exception('modular inverse does not exist')
else:
return x % m
def base64_url_decode(data):
data += '==' [(2 - len(data) * 3) % 4:]
data += '=='[(2 - len(data) * 3) % 4:]
for search, replace in (('-', '+'), ('_', '/'), (',', '')):
data = data.replace(search, replace)
return base64.b64decode(data)
......
import math
import re
import json
import logging
......@@ -22,7 +23,8 @@ from .errors import ValidationError, RequestError
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
decrypt_key, mpi_to_int, stringhash, prepare_key, make_id, makebyte,
modular_inverse
)
logger = logging.getLogger(__name__)
......@@ -121,22 +123,39 @@ class Mega:
)
private_key = a32_to_str(rsa_private_key)
self.rsa_private_key = [0, 0, 0, 0]
# The private_key contains 4 MPI integers concatenated together.
rsa_private_key = [0, 0, 0, 0]
for i in range(4):
l = int(
((private_key[0]) * 256 + (private_key[1]) + 7) / 8
) + 2
self.rsa_private_key[i] = mpi_to_int(private_key[:l])
private_key = private_key[l:]
# 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']))
rsa_decrypter = RSA.construct(
(
self.rsa_private_key[0] * self.rsa_private_key[1], 257,
self.rsa_private_key[2], self.rsa_private_key[0],
self.rsa_private_key[1]
)
)
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])
......@@ -288,9 +307,8 @@ class Mega:
if foldername != '':
for file in files.items():
if (
file[1]['a'] and
file[1]['t'] and
file[1]['a']['n'] == foldername
file[1]['a'] and file[1]['t']
and file[1]['a']['n'] == foldername
):
if parent_desc == file[1]['p']:
parent_desc = file[0]
......@@ -318,23 +336,20 @@ class Mega:
parent_dir_name, files=files
)
if (
filename and parent_node_id and
file[1]['a'] and file[1]['a']['n'] == filename and
parent_node_id == file[1]['p']
filename and parent_node_id and file[1]['a']
and file[1]['a']['n'] == filename
and parent_node_id == file[1]['p']
):
if (
exclude_deleted and
self._trash_folder_node_id == file[1]['p']
exclude_deleted
and self._trash_folder_node_id == file[1]['p']
):
continue
return file
if (
filename and
file[1]['a'] and file[1]['a']['n'] == filename
):
if (filename and file[1]['a'] and file[1]['a']['n'] == filename):
if (
exclude_deleted and
self._trash_folder_node_id == file[1]['p']
exclude_deleted
and self._trash_folder_node_id == file[1]['p']
):
continue
return file
......@@ -584,13 +599,13 @@ class Mega:
def _export_file(self, node):
node_data = self._node_data(node)
self._api_request([
{
self._api_request(
[{
'a': 'l',
'n': node_data['h'],
'i': self.request_id
}
])
}]
)
return self.get_link(node)
def export(self, path=None, node_id=None):
......@@ -613,7 +628,9 @@ class Mega:
master_key_cipher = AES.new(a32_to_str(self.master_key), AES.MODE_ECB)
ha = 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)
......@@ -731,7 +748,9 @@ class Mega:
aes = AES.new(k_str, AES.MODE_CTR, counter=counter)
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]])
for chunk_start, chunk_size in get_chunks(file_size):
......@@ -794,7 +813,9 @@ class Mega:
completion_file_handle = None
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]])
if file_size > 0:
for chunk_start, chunk_size in get_chunks(file_size):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment