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

Remove the --key/cipher bytecode encryption. #6999

Merged
merged 1 commit into from
Jun 29, 2023
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
36 changes: 2 additions & 34 deletions PyInstaller/archive/pyz_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,8 @@
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
#-----------------------------------------------------------------------------

import os

from PyInstaller import log as logging

BLOCK_SIZE = 16
logger = logging.getLogger(__name__)


class PyiBlockCipher:
"""
This class is used only to encrypt Python modules.
"""
def __init__(self, key=None):
logger.log(
logging.DEPRECATION,
"Bytecode encryption will be removed in PyInstaller v6. Please remove cipher and block_cipher parameters "
"from your spec file to avoid breakages on upgrade. For the rationale/alternatives see "
"https://github.com/pyinstaller/pyinstaller/pull/6999"
)
assert type(key) is str
if len(key) > BLOCK_SIZE:
self.key = key[0:BLOCK_SIZE]
else:
self.key = key.zfill(BLOCK_SIZE)
assert len(self.key) == BLOCK_SIZE

import tinyaes
self._aesmod = tinyaes

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

def __create_cipher(self, iv):
# The 'AES' class is stateful, and 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)
from PyInstaller.exceptions import RemovedCipherFeatureError
raise RemovedCipherFeatureError("Please remove cipher and block_cipher parameters from your spec file.")
12 changes: 3 additions & 9 deletions PyInstaller/archive/writers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ZlibArchiveWriter:
_HEADER_LENGTH = 12 + 5
_COMPRESSION_LEVEL = 6 # zlib compression level

def __init__(self, filename, entries, code_dict=None, cipher=None):
def __init__(self, filename, entries, code_dict=None):
"""
filename
Target filename of the archive.
Expand All @@ -44,8 +44,6 @@ def __init__(self, filename, entries, code_dict=None, cipher=None):
`DATA`).
code_dict
Optional code dictionary containing code objects for analyzed/collected python modules.
cipher
Optional `Cipher` object for bytecode encryption.
"""
code_dict = code_dict or {}

Expand All @@ -56,7 +54,7 @@ def __init__(self, filename, entries, code_dict=None, cipher=None):
# Write entries' data and collect TOC entries
toc = []
for entry in entries:
toc_entry = self._write_entry(fp, entry, code_dict, cipher)
toc_entry = self._write_entry(fp, entry, code_dict)
toc.append(toc_entry)

# Write TOC
Expand All @@ -68,17 +66,15 @@ def __init__(self, filename, entries, code_dict=None, cipher=None):
# - PYZ magic pattern (4 bytes)
# - python bytecode magic pattern (4 bytes)
# - TOC offset (32-bit int, 4 bytes)
# - encryption flag (1 byte)
# - 4 unused bytes
fp.seek(0, os.SEEK_SET)

fp.write(self._PYZ_MAGIC_PATTERN)
fp.write(BYTECODE_MAGIC)
fp.write(struct.pack('!i', toc_offset))
fp.write(struct.pack('!B', cipher is not None))

@classmethod
def _write_entry(cls, fp, entry, code_dict, cipher):
def _write_entry(cls, fp, entry, code_dict):
name, src_path, typecode = entry

if typecode == 'PYMODULE':
Expand All @@ -102,8 +98,6 @@ def _write_entry(cls, fp, entry, code_dict, cipher):

# First compress, then encrypt.
obj = zlib.compress(data, cls._COMPRESSION_LEVEL)
if cipher:
obj = cipher.encrypt(obj)

# Create TOC entry
toc_entry = (name, (typecode, fp.tell(), len(obj)))
Expand Down
18 changes: 6 additions & 12 deletions PyInstaller/building/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,18 @@ def __init__(self, *tocs, **kwargs):

name
A filename for the .pyz. Normally not needed, as the generated name will do fine.
cipher
The block cipher that will be used to encrypt Python bytecode.
"""
if kwargs.get("cipher"):
from PyInstaller.exceptions import RemovedCipherFeatureError
raise RemovedCipherFeatureError(
"Please remove the 'cipher' arguments to PYZ() and Analysis() in your spec file."
)

from PyInstaller.config import CONF

super().__init__()

name = kwargs.get('name', None)
cipher = kwargs.get('cipher', None)

self.name = name
if name is None:
Expand All @@ -79,14 +81,6 @@ def __init__(self, *tocs, **kwargs):
# PyInstaller bootstrapping modules.
bootstrap_dependencies = get_bootstrap_modules()

# Bundle the crypto key.
self.cipher = cipher
if cipher:
key_file = ('pyimod00_crypto_key', os.path.join(CONF['workpath'], 'pyimod00_crypto_key.py'), 'PYMODULE')
# Insert the key as the first module in the list. The key module contains just variables and does not depend
# on other modules.
bootstrap_dependencies.insert(0, key_file)

# Compile the python modules that are part of bootstrap dependencies, so that they can be collected into the
# CArchive and imported by the bootstrap script.
self.dependencies = []
Expand Down Expand Up @@ -156,7 +150,7 @@ def assemble(self):
self.code_dict = {name: strip_paths_in_code(code) for name, code in self.code_dict.items()}

# Create the archive
ZlibArchiveWriter(self.name, archive_toc, code_dict=self.code_dict, cipher=self.cipher)
ZlibArchiveWriter(self.name, archive_toc, code_dict=self.code_dict)
logger.info("Building PYZ (ZlibArchive) %s completed successfully.", self.name)


Expand Down
20 changes: 5 additions & 15 deletions PyInstaller/building/build_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

from PyInstaller import DEFAULT_DISTPATH, DEFAULT_WORKPATH, HOMEPATH, compat
from PyInstaller import log as logging
from PyInstaller.archive import pyz_crypto
from PyInstaller.building.api import COLLECT, EXE, MERGE, PYZ
from PyInstaller.building.datastruct import TOC, Target, Tree, _check_guts_eq, normalize_toc, normalize_pyz_toc
from PyInstaller.building.osx import BUNDLE
Expand Down Expand Up @@ -305,8 +304,6 @@ def __init__(
ignored (as though they were not found).
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, prefer not to follow version redirects when searching for Windows SxS Assemblies.
win_private_assemblies
Expand All @@ -317,6 +314,11 @@ def __init__(
An optional dict of package/module names and collection mode strings. Valid collection mode strings:
'pyz' (default), 'pyc', 'py', 'pyz+py' (or 'py+pyz')
"""
if cipher is not None:
from PyInstaller.exceptions import RemovedCipherFeatureError
raise RemovedCipherFeatureError(
"Please remove the 'cipher' arguments to PYZ() and Analysis() in your spec file."
)
super().__init__()
from PyInstaller.config import CONF

Expand Down Expand Up @@ -377,15 +379,6 @@ def __init__(
# Custom runtime hook files that should be included and started before any existing PyInstaller runtime hooks.
self.custom_runtime_hooks = runtime_hooks or []

if cipher:
logger.info('Will encrypt Python bytecode with provided cipher key')
# Create a Python module which contains the decryption key which will be used at runtime by
# pyi_crypto.PyiBlockCipher.
pyi_crypto_key_path = os.path.join(CONF['workpath'], 'pyimod00_crypto_key.py')
with open(pyi_crypto_key_path, 'w', encoding='utf-8') as f:
f.write('# -*- coding: utf-8 -*-\nkey = %r\n' % cipher.key)
self.hiddenimports.append('tinyaes')

self._input_binaries = []
self._input_datas = []

Expand Down Expand Up @@ -442,8 +435,6 @@ def __init__(
('_input_binaries', _check_guts_toc),
('_input_datas', _check_guts_toc),

# 'cipher': no need to check as it is implied by an additional hidden import

# calculated/analysed values
('_python_version', _check_guts_eq),
('scripts', _check_guts_toc_mtime),
Expand Down Expand Up @@ -929,7 +920,6 @@ def build(spec, distpath, workpath, clean_build):
'Splash': Splash,
# Python modules available for .spec.
'os': os,
'pyi_crypto': pyz_crypto,
}

# Execute the specfile. Read it as a binary file...
Expand Down
32 changes: 5 additions & 27 deletions PyInstaller/building/makespec.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@

import argparse
import os
import sys

from PyInstaller import DEFAULT_SPECPATH, HOMEPATH
from PyInstaller import log as logging
from PyInstaller.building.templates import (
bundleexetmplt, bundletmplt, cipher_absent_template, cipher_init_template, onedirtmplt, onefiletmplt, splashtmpl
)
from PyInstaller.building.templates import bundleexetmplt, bundletmplt, onedirtmplt, onefiletmplt, splashtmpl
from PyInstaller.compat import expand_path, is_darwin, is_win

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -91,13 +88,9 @@ def make_variable_path(filename, conversions=path_conversions):
return None, filename


def deprecated_key_option(x):
logger.log(
logging.DEPRECATION,
"Bytecode encryption will be removed in PyInstaller v6. Please remove your --key=xxx argument to avoid "
"breakages on upgrade. For the rationale/alternatives see https://github.com/pyinstaller/pyinstaller/pull/6999"
)
return x
def removed_key_option(x):
from PyInstaller.exceptions import RemovedCipherFeatureError
raise RemovedCipherFeatureError("Please remove your --key=xxx argument.")


# An object used in place of a "path string", which knows how to repr() itself using variable names instead of
Expand Down Expand Up @@ -356,7 +349,7 @@ def __add_options(parser):
'--key',
dest='key',
help=argparse.SUPPRESS,
type=deprecated_key_option,
type=removed_key_option,
)
g.add_argument(
'--splash',
Expand Down Expand Up @@ -732,20 +725,6 @@ def main(
# With absolute paths replace prefix with variable HOMEPATH.
scripts = list(map(Path, scripts))

if key:
# Try to import tinyaes as we need it for bytecode obfuscation.
try:
import tinyaes # noqa: F401 (test import)
except ImportError:
logger.error(
'We need tinyaes to use byte-code obfuscation but we could not find it. You can install it '
'with pip by running:\n pip install tinyaes'
)
sys.exit(1)
cipher_init = cipher_init_template % {'key': key}
else:
cipher_init = cipher_absent_template

# Translate the default of ``debug=None`` to an empty list.
if debug is None:
debug = []
Expand Down Expand Up @@ -788,7 +767,6 @@ def main(
'upx_exclude': upx_exclude,
'runtime_tmpdir': runtime_tmpdir,
'exe_options': exe_options,
'cipher_init': cipher_init,
# Directory with additional custom import hooks.
'hookspath': hookspath,
# List with custom runtime hook files.
Expand Down
16 changes: 2 additions & 14 deletions PyInstaller/building/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

onefiletmplt = """# -*- mode: python ; coding: utf-8 -*-
%(preamble)s
%(cipher_init)s

a = Analysis(
%(scripts)s,
Expand All @@ -28,10 +27,9 @@
excludes=%(excludes)s,
win_no_prefer_redirects=%(win_no_prefer_redirects)s,
win_private_assemblies=%(win_private_assemblies)s,
cipher=block_cipher,
noarchive=%(noarchive)s,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data)
%(splash_init)s
exe = EXE(
pyz,
Expand All @@ -58,7 +56,6 @@

onedirtmplt = """# -*- mode: python ; coding: utf-8 -*-
%(preamble)s
%(cipher_init)s

a = Analysis(
%(scripts)s,
Expand All @@ -72,10 +69,9 @@
excludes=%(excludes)s,
win_no_prefer_redirects=%(win_no_prefer_redirects)s,
win_private_assemblies=%(win_private_assemblies)s,
cipher=block_cipher,
noarchive=%(noarchive)s,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data)
%(splash_init)s
exe = EXE(
pyz,
Expand Down Expand Up @@ -106,14 +102,6 @@
)
"""

cipher_absent_template = """
block_cipher = None
"""

cipher_init_template = """
block_cipher = pyi_crypto.PyiBlockCipher(key=%(key)r)
"""

bundleexetmplt = """app = BUNDLE(
exe,
name='%(name)s.app',
Expand Down
6 changes: 6 additions & 0 deletions PyInstaller/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ def __str__(self):
"exists and whether the hook is compatible with your version of {1}: You might want to read more about "
"hooks in the manual and provide a pull-request to improve PyInstaller.".format(self.args[0], self.args[1])
)


class RemovedCipherFeatureError(SystemExit):
def __str__(self):
return f"Bytecode encryption was removed in PyInstaller v6.0. {self.args[0]}" \
" For the rationale and alternatives see https://github.com/pyinstaller/pyinstaller/pull/6999"