Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace PyCrypto with tinyaes for encryption support #4652

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 6 additions & 33 deletions PyInstaller/archive/pyz_crypto.py
Expand Up @@ -14,32 +14,6 @@
BLOCK_SIZE = 16


def import_aes(module_name):
"""
Tries to import the AES module from PyCrypto.

PyCrypto 2.4 and 2.6 uses different name of the AES extension.
"""
return __import__(module_name, fromlist=[module_name.split('.')[-1]])


def get_crypto_hiddenimports():
"""
These module names are appended to the PyInstaller analysis phase.
:return: Name of the AES module.
"""
try:
# The _AES.so module exists only in PyCrypto 2.6 and later. Try to import
# that first.
modname = 'Crypto.Cipher._AES'
import_aes(modname)
except ImportError:
# Fallback to AES.so, which should be there in PyCrypto 2.4 and earlier.
modname = 'Crypto.Cipher.AES'
import_aes(modname)
return modname


class PyiBlockCipher(object):
"""
This class is used only to encrypt Python modules.
Expand All @@ -52,15 +26,14 @@ def __init__(self, key=None):
self.key = key.zfill(BLOCK_SIZE)
assert len(self.key) == BLOCK_SIZE

# Import the right AES module.
self._aesmod = import_aes(get_crypto_hiddenimports())
import tinyaes
self._aesmod = tinyaes

def encrypt(self, data):
iv = os.urandom(BLOCK_SIZE)
return iv + self.__create_cipher(iv).encrypt(data)
return iv + self.__create_cipher(iv).CTR_xcrypt_buffer(data)

def __create_cipher(self, iv):
# The 'BlockAlgo' class is stateful, this factory method is used to
# re-initialize the block cipher class with each call to encrypt() and
# decrypt().
return self._aesmod.new(self.key.encode(), self._aesmod.MODE_CFB, iv)
# The 'AES' class is stateful, this factory method is used to
# re-initialize the block cipher class with each call to xcrypt().
return self._aesmod.AES(self.key.encode(), iv)
6 changes: 4 additions & 2 deletions PyInstaller/building/build_main.py
Expand Up @@ -161,6 +161,9 @@ def __init__(self, scripts, pathex=None, binaries=None, datas=None,
runtime_hooks
An optional list of scripts to use as users' runtime hooks. Specified
as file names.
cipher
Add optional instance of the pyz_crypto.PyiBlockCipher class
(with a provided key).
win_no_prefer_redirects
If True, prefers not to follow version redirects when searching for
Windows SxS Assemblies.
Expand Down Expand Up @@ -227,8 +230,7 @@ def __init__(self, scripts, pathex=None, binaries=None, datas=None,
with open_file(pyi_crypto_key_path, 'w', encoding='utf-8') as f:
f.write('# -*- coding: utf-8 -*-\n'
'key = %r\n' % cipher.key)
logger.info('Adding dependencies on pyi_crypto.py module')
self.hiddenimports.append(pyz_crypto.get_crypto_hiddenimports())
self.hiddenimports.append('tinyaes')

self.excludes = excludes or []
self.scripts = TOC()
Expand Down
15 changes: 5 additions & 10 deletions PyInstaller/building/makespec.py
Expand Up @@ -17,7 +17,6 @@
import os
import sys
import argparse
from distutils.version import LooseVersion

from .. import HOMEPATH, DEFAULT_SPECPATH
from .. import log as logging
Expand Down Expand Up @@ -398,18 +397,14 @@ def main(scripts, name=None, onefile=None,
scripts = list(map(Path, scripts))

if key:
# Tries to import PyCrypto since we need it for bytecode obfuscation. Also make sure its
# version is >= 2.4.
# Tries to import tinyaes since we need it for bytecode obfuscation.
try:
import Crypto
is_version_acceptable = LooseVersion(Crypto.__version__) >= LooseVersion('2.4')
if not is_version_acceptable:
logger.error('PyCrypto version must be >= 2.4, older versions are not supported.')
sys.exit(1)
import tinyaes # noqa: F401 (test import)
except ImportError:
logger.error('We need PyCrypto >= 2.4 to use byte-code obfuscation but we could not')
logger.error('We need tinyaes to use byte-code obfuscation but we '
'could not')
logger.error('find it. You can install it with pip by running:')
logger.error(' pip install PyCrypto')
logger.error(' pip install tinyaes')
sys.exit(1)
cipher_init = cipher_init_template % {'key': key}
else:
Expand Down
38 changes: 9 additions & 29 deletions PyInstaller/loader/pyimod02_archive.py
Expand Up @@ -249,40 +249,20 @@ def __init__(self):
self.key = key.zfill(CRYPT_BLOCK_SIZE)
assert len(self.key) == CRYPT_BLOCK_SIZE

# Import the right AES module.
self._aes = self._import_aesmod()

def _import_aesmod(self):
"""
Tries to import the AES module from PyCrypto.

PyCrypto 2.4 and 2.6 uses different name of the AES extension.
"""
# The _AES.so module exists only in PyCrypto 2.6 and later. Try to import
# that first.
modname = 'Crypto.Cipher._AES'

kwargs = dict(fromlist=['Crypto', 'Cipher'])
try:
mod = __import__(modname, **kwargs)
except ImportError:
modname = 'Crypto.Cipher.AES'
mod = __import__(modname, **kwargs)

import tinyaes
self._aesmod = tinyaes
# Issue #1663: Remove the AES module from sys.modules list. Otherwise
# it interferes with using 'Crypto.Cipher' module in users' code.
if modname in sys.modules:
del sys.modules[modname]
return mod
# it interferes with using 'tinyaes' module in users' code.
del sys.modules['tinyaes']

def __create_cipher(self, iv):
# The 'BlockAlgo' class is stateful, this factory method is used to
# re-initialize the block cipher class with each call to encrypt() and
# decrypt().
return self._aes.new(self.key, self._aes.MODE_CFB, iv)
# The 'AES' class is stateful, this factory method is used to
# re-initialize the block cipher class with each call to xcrypt().
return self._aesmod.AES(self.key.encode(), iv)

def decrypt(self, data):
return self.__create_cipher(data[:CRYPT_BLOCK_SIZE]).decrypt(data[CRYPT_BLOCK_SIZE:])
cipher = self.__create_cipher(data[:CRYPT_BLOCK_SIZE])
return cipher.CTR_xcrypt_buffer(data[CRYPT_BLOCK_SIZE:])


class ZlibArchiveReader(ArchiveReader):
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Expand Up @@ -67,7 +67,7 @@ Requirements and Tested Platforms
- Python:

- 3.5-3.7
- PyCrypto_ 2.4+ (only if using bytecode encryption)
- tinyaes_ 1.0+ (only if using bytecode encryption)

- Windows (32bit/64bit):

Expand Down Expand Up @@ -159,6 +159,6 @@ http://www.pyinstaller.org/funding.html for how to support PyInstaller.



.. _PyCrypto: https://www.dlitz.net/software/pycrypto/
.. _tinyaes: https://github.com/naufraghi/tinyaes-py
.. _`manual`: https://pyinstaller.readthedocs.io/en/latest/

2 changes: 1 addition & 1 deletion doc/_common_definitions.txt
Expand Up @@ -45,7 +45,7 @@
.. _pip-Win: https://sites.google.com/site/pydatalog/python/pip-for-windows
.. _plistlib: https://docs.python.org/3/library/plistlib.html
.. _png2icns: http://icns.sourceforge.net/
.. _PyCrypto: https://pypi.python.org/pypi/pycrypto/
.. _tinyaes: https://github.com/naufraghi/tinyaes-py
.. _PyInstaller.org: https://github.com/pyinstaller/pyinstaller/wiki/Community
.. _`PyInstaller at GitHub`: https://github.com/pyinstaller/pyinstaller
.. _`PyInstaller code signing recipe`: https://github.com/pyinstaller/pyinstaller/wiki/Recipe-OSX-Code-Signing
Expand Down
2 changes: 1 addition & 1 deletion doc/usage.rst
Expand Up @@ -203,7 +203,7 @@ To encrypt the Python bytecode modules stored in the bundle,
pass the ``--key=``\ *key-string* argument on
the command line.

For this to work, you must have the PyCrypto_
For this to work, you must have the tinyaes_
module installed.
The *key-string* is a string of 16 characters which is used to
encrypt each file of Python byte-code before it is stored in
Expand Down
2 changes: 2 additions & 0 deletions news/2365.bugfix.rst
@@ -0,0 +1,2 @@
Update the base library to support encrypting Python bytecode (``--key``
option) again. Many thanks to Matteo Bertini for finally fixing this.
2 changes: 2 additions & 0 deletions news/3093.bugfix.rst
@@ -0,0 +1,2 @@
Update the base library to support encrypting Python bytecode (``--key``
option) again. Many thanks to Matteo Bertini for finally fixing this.
2 changes: 2 additions & 0 deletions news/3133.bugfix.rst
@@ -0,0 +1,2 @@
Update the base library to support encrypting Python bytecode (``--key``
option) again. Many thanks to Matteo Bertini for finally fixing this.
2 changes: 2 additions & 0 deletions news/3160.bugfix.rst
@@ -0,0 +1,2 @@
Update the base library to support encrypting Python bytecode (``--key``
option) again. Many thanks to Matteo Bertini for finally fixing this.
2 changes: 2 additions & 0 deletions news/3198.bugfix.rst
@@ -0,0 +1,2 @@
Update the base library to support encrypting Python bytecode (``--key``
option) again. Many thanks to Matteo Bertini for finally fixing this.
2 changes: 2 additions & 0 deletions news/3316.bugfix.rst
@@ -0,0 +1,2 @@
Update the base library to support encrypting Python bytecode (``--key``
option) again. Many thanks to Matteo Bertini for finally fixing this.
2 changes: 2 additions & 0 deletions news/3619.bugfix.rst
@@ -0,0 +1,2 @@
Update the base library to support encrypting Python bytecode (``--key``
option) again. Many thanks to Matteo Bertini for finally fixing this.
2 changes: 2 additions & 0 deletions news/4241.bugfix.rst
@@ -0,0 +1,2 @@
Update the base library to support encrypting Python bytecode (``--key``
option) again. Many thanks to Matteo Bertini for finally fixing this.
2 changes: 2 additions & 0 deletions news/4652.bugfix.rst
@@ -0,0 +1,2 @@
Update the base library to support encrypting Python bytecode (``--key``
option) again. Many thanks to Matteo Bertini for finally fixing this.
10 changes: 4 additions & 6 deletions tests/functional/test_basic.py
Expand Up @@ -26,7 +26,6 @@
from PyInstaller.utils.tests import importorskip, skipif, skipif_win, \
skipif_winorosx, skipif_notwin, skipif_notosx, skipif_no_compiler, \
skipif_notlinux, xfail
from PyInstaller.utils.hooks import is_module_satisfies


def test_run_from_path_environ(pyi_builder):
Expand Down Expand Up @@ -162,17 +161,16 @@ def test_email(pyi_builder):
""")


@skipif(is_module_satisfies('Crypto >= 3'), reason='Bytecode encryption is not '
'compatible with pycryptodome.')
@importorskip('Crypto')
@importorskip('tinyaes')
def test_feature_crypto(pyi_builder):
pyi_builder.test_source(
"""
from pyimod00_crypto_key import key
from pyimod02_archive import CRYPT_BLOCK_SIZE

# Issue 1663: Crypto feature caused issues when using PyCrypto module.
import Crypto.Cipher.AES
# Test against issue #1663: importing a package in the bootstrap
# phase should not interfere with subsequent imports.
import tinyaes

assert type(key) is str
# The test runner uses 'test_key' as key.
Expand Down
3 changes: 3 additions & 0 deletions tests/requirements-tools.txt
Expand Up @@ -36,3 +36,6 @@ flake8-diff
pywin32; sys_platform == 'win32'

lxml

# crypto support (`--key` option)
tinyaes ~= 1.0