commit 6472ab5de35bbe7f165178164b3d3910ad3115e2
Author: Mickaël Schoentgen <mschoentgen@nuxeo.com>
Date:   Thu Feb 20 17:55:36 2020 +0100

    NXDRIVE-2057: Move to tinyaes for cryptography stuff

diff --git a/docs/changes/4.4.2.md b/docs/changes/4.4.2.md
index 31bcb770..adb3ccdf 100644
--- a/docs/changes/4.4.2.md
+++ b/docs/changes/4.4.2.md
@@ -44,6 +44,7 @@ Release date: `20xx-xx-xx`
 - [NXDRIVE-1993](https://jira.nuxeo.com/browse/NXDRIVE-1993): Use Dependabot to keep dependencies up-to-date
 - [NXDRIVE-2001](https://jira.nuxeo.com/browse/NXDRIVE-2001): Pin the Pip version to ease reproductible builds
 - [NXDRIVE-2050](https://jira.nuxeo.com/browse/NXDRIVE-2050): Provide auto-update scripts for wide deployment
+- [NXDRIVE-2057](https://jira.nuxeo.com/browse/NXDRIVE-2057): Move to tinyaes for cryptography stuff
 
 ## Tests
 
@@ -61,7 +62,9 @@ Release date: `20xx-xx-xx`
 - Added `botocore` 1.15.0
 - Added `docutils` 0.15.2
 - Added `jmespath` 0.9.4
+- Added `tinyaes` 1.0.0
 - Added `s3transfer` 0.3.2
+- Added `pycryptodomex`
 - Upgraded `altgraph` from 0.16.1 to 0.17
 - Upgraded `certify` from 2019.9.11 to 2019.11.28
 - Upgraded `cffi` from 1.12.3 to 1.14.0
@@ -113,3 +116,5 @@ Release date: `20xx-xx-xx`
 - Added updater/constants.py::`AutoUpdateState`
 - Added updater/utils.py::`auto_updates_state()`
 - Removed utils.py::`safe_os_filename()`. Use `safe_filename()` instead.
+- Removed `lazy` keyword argument from utils.py::`decrypt()`
+- Removed `lazy` keyword argument from utils.py::`encrypt()`
diff --git a/nxdrive/utils.py b/nxdrive/utils.py
index fc06bc23..068fbd55 100644
--- a/nxdrive/utils.py
+++ b/nxdrive/utils.py
@@ -46,6 +46,8 @@ from .options import Options
 if TYPE_CHECKING:
     from .proxy import Proxy  # noqa
 
+# AES
+BLOCK_SIZE = 16
 
 DEFAULTS_CERT_DETAILS = {
     "subject": [],
@@ -755,58 +757,48 @@ def get_certificate_details(hostname: str = "", cert_data: str = "") -> Dict[str
 
 
 @lru_cache(maxsize=4, typed=True)
-def encrypt(
-    plaintext: Union[bytes, str], secret: Union[bytes, str], lazy: bool = True
-) -> bytes:
+def encrypt(data: Union[bytes, str], key: Union[bytes, str]) -> bytes:
     """ Symmetric encryption using AES. """
 
     import base64
-    from Cryptodome.Random import get_random_bytes
-    from Cryptodome.Cipher import AES
+    import tinyaes
 
-    plaintext = force_encode(plaintext)
-    secret = force_encode(secret)
-    secret = _lazysecret(secret) if lazy else secret
-    iv = get_random_bytes(AES.block_size)
-    encobj = AES.new(secret, AES.MODE_CFB, iv)
-    return base64.b64encode(iv + encobj.encrypt(plaintext))
+    data = force_encode(data)
+    key = force_encode(key)
+    if len(key) > BLOCK_SIZE:
+        key = key[:BLOCK_SIZE]
+    else:
+        key = key.zfill(BLOCK_SIZE)
+
+    iv = os.urandom(BLOCK_SIZE)
+    encobj = tinyaes.AES(key, iv)
+    return base64.b64encode(iv + encobj.CTR_xcrypt_buffer(data))
 
 
 @lru_cache(maxsize=4, typed=True)
-def decrypt(
-    ciphertext: Union[bytes, str], secret: Union[bytes, str], lazy: bool = True
-) -> Optional[bytes]:
+def decrypt(data: Union[bytes, str], key: Union[bytes, str]) -> Optional[bytes]:
     """ Symmetric decryption using AES. """
 
     import base64
-    from Cryptodome.Cipher import AES
+    import tinyaes
+
+    key = force_encode(key)
+    if len(key) > BLOCK_SIZE:
+        key = key[:BLOCK_SIZE]
+    else:
+        key = key.zfill(BLOCK_SIZE)
 
-    ciphertext = force_encode(ciphertext)
-    secret = force_encode(secret)
-    secret = _lazysecret(secret) if lazy else secret
-    ciphertext = base64.b64decode(ciphertext)
-    iv = ciphertext[: AES.block_size]
-    ciphertext = ciphertext[AES.block_size :]
+    data = base64.b64decode(force_encode(data))
+    iv, data = data[:BLOCK_SIZE], data[BLOCK_SIZE:]
 
     # Don't fail on decrypt
     try:
-        encobj = AES.new(secret, AES.MODE_CFB, iv)
-        return encobj.decrypt(ciphertext)
+        res: bytes = tinyaes.AES(key, iv).CTR_xcrypt_buffer(data)
+        return res
     except Exception:
         return None
 
 
-@lru_cache(maxsize=4)
-def _lazysecret(secret: bytes, blocksize: int = 32, padding: bytes = b"}") -> bytes:
-    """Pad secret if not legal AES block size (16, 24, 32)."""
-    length = len(secret)
-    if length > blocksize:
-        return secret[: -(length - blocksize)]
-    if length not in (16, 24, 32):
-        return secret + (blocksize - length) * padding
-    return secret
-
-
 @lru_cache(maxsize=4)
 def compute_urls(url: str) -> Iterator[str]:
     """
diff --git a/requirements.txt b/requirements.txt
index 3ac10c6f..ecde6a49 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -110,37 +110,6 @@ psutil==5.7.0 \
 pycparser==2.19 \
     --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3
     # via cffi
-pycryptodomex==3.9.6 \
-    --hash=sha256:a979d2c7bcc67282b7ec2600db384c63d37d74e250edb99168483605a380bf62 \
-    --hash=sha256:718329c6ca60260f1c27b8392e372dd51e4e691f7dcb88adc53eb3b76af6363c \
-    --hash=sha256:f39f5b58d8fe348ed604bb44a89ca93b26130c275db2b249f718f1538cb70500 \
-    --hash=sha256:3b9306b360bddbc8e098b16eab7adacf49389d212db9c3739588ab840a1ca868 \
-    --hash=sha256:38050b3fd86c74c6c79e40bbe824bec6431c3e4e36f6080ed544673ba2dc133a \
-    --hash=sha256:d67b6e0bae0777a2c6c83275fbd7cbf53cd5f23c2028f908b0f7d996466e5b15 \
-    --hash=sha256:132f1e5fa84921f25695a313a6d4988847dfaee7fb1fd0d1fbe03ef678836f58 \
-    --hash=sha256:c96b7762b601dc8a58d7712235c3c152868116f58a7ffa40dcd1c6f6cd97405e \
-    --hash=sha256:e15f39fcfb949cfd5536cc9647daba942b1a99b67e4d7211e3bdbcedbc2f823c \
-    --hash=sha256:b350f9ad09b692aed57e669fc3f8cf918557fae9f0229c6ce9286a6fe8c1b60f \
-    --hash=sha256:918bc5a0170fe8ed7b72f202245b34f84a1997f5ca1520b9c7db71126e5acd62 \
-    --hash=sha256:544628ae67d61c31c28a60e621dadd738b303c5266492355d5ebdb6e7dd1e78f \
-    --hash=sha256:5002388178845683c330a02f4faeddfe7cd477b87824987cca4718fa0c4f2085 \
-    --hash=sha256:e380448f1e39736f6230ec284cd6d771956ad802d6ce5bc56947a2481080cac1 \
-    --hash=sha256:f667ac7ae29c19530f199854635f1a97e73d0bfd24163e0db6bdba7dba04eb9f \
-    --hash=sha256:bfa99692d3c8f994c5850cc8a894cba001abd76d34069a8bfaad173dd46387d6 \
-    --hash=sha256:be838abc8557a21a60d453c5a4e64c738966b8a0b7d7f8f97eb8bb44041ca452 \
-    --hash=sha256:a8ea72adde0d010f89abece5f024b1be95a5c52472e9a57b3ac7d59aee3c8238 \
-    --hash=sha256:4f77360b23a21db32a4c35dacffac33dc30ac6a5a77162a34e99ab11ab631516 \
-    --hash=sha256:f545f776e45f74c41329e4020463fdd4d0cd0a7501bdf9e50251dafe7bd959a9 \
-    --hash=sha256:51be76756abfc1ddc97e1e2e3c38f4e62fb940161162368308ea9e5919e86c34 \
-    --hash=sha256:17ad1ebaa00806305d34550fe5d3c776e38a27b8a2678dfb7871ef0209d64e46 \
-    --hash=sha256:c7babb64484080057a24c74a82dbf7997904b1710b74caf62e261610f989b437 \
-    --hash=sha256:04646e40ef5788bad6d415e52862ffcdf2ac2d888ba4a5c82d5cb44607a042f7 \
-    --hash=sha256:27736fa02a2d3502e1ca4b150457e56ce3b98f132462f540073498884e5f8975 \
-    --hash=sha256:466e36ba74a7e725625e717fad3f36e0b9293c247b7d0439c66528026ef2834f \
-    --hash=sha256:ea7b48ce8dbbc86ebadcfe56ebc10d413bdd12c9a5ff0b9147a41993f12b80b3 \
-    --hash=sha256:e5236f2171b21e704d1854fd809a7228eb22e29c894af31459e41986e6a53f87 \
-    --hash=sha256:6ff9d4a06bc40211eee05cd88436740d698a01233f4aaff9eb70d8a90e578966 \
-    --hash=sha256:c021b66f5b3c4ea0c45422ec3241bfea4a16651e1ee5459a136639d0716ccb3c
 pyobjc-core==6.1 ; sys_platform == "darwin" \
     --hash=sha256:470ccd754efb468a59426942673dfc3c5c59f33a5b8cae8a7fc1be975c2d4128 \
     --hash=sha256:751cdeb436cb181af2e2413015b072075590577c410c7f345080a38dd028b8ec \
@@ -242,6 +211,10 @@ tld==0.11.9 \
     --hash=sha256:1d4ee60fbbefa70cad44329825dbedfb0fed6f1ef3b66d23f5cc3f2f3bbbd803 \
     --hash=sha256:c5fe79df74b68ebc33406dfadc69f6484dee8005035a129fdb40b8fabfd06e9f
     # via pypac
+tinyaes==1.0.0 \
+    --hash=sha256:65c7a8da94907e47082aae5cb63aa4473265d340ae2ac79df9c044b1ffd46d98 \
+    --hash=sha256:62ba79fd0253527c607c09fa1d288ef22616ef559da6fbb555bd1141e86d5886 \
+    --hash=sha256:9baf6724a1acc0cafe5a188e6f821650ad44314fb3a54aa20540c2bc8e97964e
 urllib3==1.25.8 \
     --hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \
     --hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 4f30a01d..8698425c 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -8,9 +8,8 @@ from pathlib import Path, _posix_flavour, _windows_flavour
 from time import sleep
 from unittest.mock import patch
 
-import pytest
-
 import nxdrive.utils
+import pytest
 from nxdrive.constants import APP_NAME, WINDOWS
 from nxdrive.options import Options
 
@@ -129,29 +128,23 @@ def test_current_thread_id():
     assert thread_id > 0
 
 
-def test_encrypt_decrypt():
-    enc = nxdrive.utils.encrypt
-    dec = nxdrive.utils.decrypt
-
-    pwd = b"Administrator"
-
-    # Test secret with length > block size
-    token = b"12345678-acbd-1234-cdef-1234567890ab"
-    cipher = enc(pwd, token)
-    assert dec(cipher, token) == pwd
-
-    # Test secret with length multiple of 2
-    token = "12345678-acbd-1234-cdef-12345678"
-    cipher = enc(pwd, token)
-    assert dec(cipher, token) == pwd
+@pytest.mark.parametrize(
+    "key",
+    [
+        "12345678-acbd-1234-cdef-1234567890ab",  # length > block size
+        "12345678-acbd-1234-cdef-12345678",  # length multiple of 2
+        "12345678-acbd-1234-cdef-1234567",  # length not multiple of 2
+        "",
+    ],
+)
+def test_encrypt_decrypt(key):
+    data = b"Administrator"
+    cipher = nxdrive.utils.encrypt(data, key)
+    assert nxdrive.utils.decrypt(cipher, key) == data
 
-    # Test secret with length not multiple of 2
-    token = "12345678-acbd-1234-cdef-123456"
-    cipher = enc(pwd, token)
-    assert dec(cipher, token) == pwd
 
-    # Decrypt failure
-    assert dec("", token) is None
+def test_decrypt_failure():
+    assert nxdrive.utils.decrypt("", "12345678-acbd-1234-cdef-1234567890ab") is None
 
 
 @windows_only(reason="Unix has no drive concept")
