Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
mega.py
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Hey
mega.py
Commits
8a4ea77b
Commit
8a4ea77b
authored
May 30, 2020
by
monkeyminer
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
https://code.richard.do/richardARPANET/mega.py
parents
f81a5642
631ca606
Pipeline
#522
failed with stages
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
91 additions
and
41 deletions
+91
-41
HISTORY.rst
HISTORY.rst
+2
-2
README.rst
README.rst
+10
-2
setup.cfg
setup.cfg
+1
-1
src/mega/crypto.py
src/mega/crypto.py
+22
-1
src/mega/mega.py
src/mega/mega.py
+56
-35
No files found.
HISTORY.rst
View file @
8a4ea77b
...
...
@@ -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)
...
...
README.rst
View file @
8a4ea77b
...
...
@@ -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:
...
...
setup.cfg
View file @
8a4ea77b
...
...
@@ -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
...
...
src/mega/crypto.py
View file @
8a4ea77b
...
...
@@ -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
)
...
...
src/mega/mega.py
View file @
8a4ea77b
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
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment