diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index be5de53..c903176 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,17 +16,16 @@ concurrency: jobs: build: - - runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: + os: [ubuntu-20.04, macos-12, windows-2022] python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] - + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/pyencrypt/cli.py b/pyencrypt/cli.py index 74b780a..25cb125 100644 --- a/pyencrypt/cli.py +++ b/pyencrypt/cli.py @@ -10,8 +10,7 @@ from pyencrypt import __description__, __version__ from pyencrypt.decrypt import decrypt_file -from pyencrypt.encrypt import (can_encrypt, encrypt_file, encrypt_key, - generate_so_file) +from pyencrypt.encrypt import (can_encrypt, encrypt_file, encrypt_key, generate_so_file) from pyencrypt.generate import generate_aes_key from pyencrypt.license import MAX_DATETIME, MIN_DATETIME, generate_license_file @@ -34,13 +33,7 @@ """ PYTHON_MAJOR, PYTHON_MINOR = sys.version_info[:2] -LAODER_FILE_NAME = click.style( - "encrypted/loader.cpython-{major}{minor}{abi}-{platform}.so".format( - major=PYTHON_MAJOR, minor=PYTHON_MINOR, abi=sys.abiflags, platform=sys.platform - ), - blink=True, - fg='blue' -) +LOADER_FILE_NAME = click.style("encrypted/{}", blink=True, fg='blue') LICENSE_FILE_NAME = click.style("license.lic", blink=True, fg='blue') SUCCESS_ANSI = click.style('successfully', fg='green') @@ -55,17 +48,17 @@ FINISH_ENCRYPT_MSG = f""" Encryption completed {SUCCESS_ANSI}. -Please copy {LAODER_FILE_NAME} into your encrypted directory. +Please copy {LOADER_FILE_NAME} into your encrypted directory. And then remove `encrypted` directory. Finally, add `import loader` at the top of your entry file.\ """ FINISH_DECRYPT_MSG = f""" -Decryption completed {SUCCESS_ANSI}. Your origin source code has be put: %s +Decryption completed {SUCCESS_ANSI}. Your origin source code has be put: {{work_dir}} """ FINISH_GENERATE_LOADER_MSG = f""" -Generate loader file {SUCCESS_ANSI}. Your loader file is located in {LAODER_FILE_NAME} +Generate loader file {SUCCESS_ANSI}. Your loader file is located in {LOADER_FILE_NAME} """ FINISH_GENERATE_LICENSE_MSG = f""" @@ -187,11 +180,11 @@ def encrypt_command(ctx, pathname, replace, key, with_license, mac, ipv4, before raise Exception(f'{path} is not a valid path.') cipher_key, d, n = encrypt_key(key.encode()) # 需要放进导入器中 - generate_so_file(cipher_key, d, n, license=with_license) + loader_extension = generate_so_file(cipher_key, d, n, license=with_license) if with_license is True: generate_license_file(key, Path(os.getcwd()), after, before, mac, ipv4) click.echo(FINISH_GENERATE_LICENSE_MSG) - click.echo(FINISH_ENCRYPT_MSG) + click.echo(FINISH_ENCRYPT_MSG.format(loader_extension.name)) @cli.command(name='decrypt') @@ -227,7 +220,7 @@ def decrypt_command(ctx, pathname, replace, key): else: raise Exception(f'{path} is not a valid path.') - click.echo(FINISH_DECRYPT_MSG % work_dir) + click.echo(FINISH_DECRYPT_MSG.format(work_dir=work_dir)) @cli.command(name='generate') @@ -237,8 +230,8 @@ def decrypt_command(ctx, pathname, replace, key): def generate_loader(ctx, key): """Generate loader file using specified key""" cipher_key, d, n = encrypt_key(key.encode()) - generate_so_file(cipher_key, d, n, Path(os.getcwd())) - click.echo(FINISH_GENERATE_LOADER_MSG) + loader_extension = generate_so_file(cipher_key, d, n, Path(os.getcwd())) + click.echo(FINISH_GENERATE_LOADER_MSG.format(loader_extension.name)) @cli.command(name='license') diff --git a/pyencrypt/encrypt.py b/pyencrypt/encrypt.py index 9d4dc28..1c328d3 100644 --- a/pyencrypt/encrypt.py +++ b/pyencrypt/encrypt.py @@ -1,4 +1,5 @@ import os +import sys import re from pathlib import Path from typing import Optional @@ -57,13 +58,14 @@ def generate_so_file(cipher_key: str, d: int, n: int, base_dir: Optional[Path] = need_import_files = ['ntt.py', 'aes.py', 'decrypt.py', 'license.py'] for file in need_import_files: file_path = path / file - decrypt_source_ls.append(REMOVE_SELF_IMPORT.sub('', file_path.read_text())) + decrypt_source_ls.append(REMOVE_SELF_IMPORT.sub('', file_path.read_text(encoding="utf-8"))) loader_source_path = path / 'loader.py' - loader_source = REMOVE_SELF_IMPORT.sub('', loader_source_path.read_text()).replace( - "__private_key = ''", f"__private_key = '{private_key}'", 1 - ).replace("__cipher_key = ''", f"__cipher_key = '{cipher_key}'", 1).replace( - 'license = None', f'license = {license}', 1 + loader_source = ( + REMOVE_SELF_IMPORT.sub("", loader_source_path.read_text(encoding="utf-8")) + .replace("__private_key = ''", f"__private_key = '{private_key}'", 1) + .replace("__cipher_key = ''", f"__cipher_key = '{cipher_key}'", 1) + .replace("license = None", f"license = {license}", 1) ) if base_dir is None: @@ -79,9 +81,14 @@ def generate_so_file(cipher_key: str, d: int, n: int, base_dir: Optional[Path] = # Origin file loader_origin_file_path = temp_dir / 'loader_origin.py' loader_origin_file_path.touch(exist_ok=True) - loader_origin_file_path.write_text(f"{decrypt_source}\n{loader_source}") + loader_origin_file_path.write_text( + f"{decrypt_source}\n{loader_source}", encoding="utf-8" + ) - loader_file_path.write_text(python_minifier.minify(loader_origin_file_path.read_text())) + loader_file_path.write_text( + python_minifier.minify(loader_origin_file_path.read_text(encoding="utf-8")), + encoding="utf-8", + ) from setuptools import setup # isort:skip from Cython.Build import cythonize @@ -91,7 +98,18 @@ def generate_so_file(cipher_key: str, d: int, n: int, base_dir: Optional[Path] = script_args=['build_ext', '--build-lib', temp_dir.as_posix()], cmdclass={'build_ext': build_ext}, ) - return list(temp_dir.glob('loader.cpython-*-*.so'))[0].absolute() + if sys.platform.startswith('win'): + # loader.cp36-win_amd64.pyd + pattern = 'loader.cp*-*.pyd' + else: + # loader.cpython-36m-x86_64-linux-gnu.so + # loader.cpython-36m-darwin.so + pattern = "loader.cpython-*-*.so" + + loader_extension = next(temp_dir.glob(pattern), None) + if loader_extension is None: + raise Exception(f"Can't find loader extension in {temp_dir.as_posix()}") + return loader_extension.absolute() def encrypt_file(path: Path, key: str, delete_origin: bool = False, new_path: Optional[Path] = None): diff --git a/pyencrypt/license.py b/pyencrypt/license.py index 37e4374..2d3abc4 100644 --- a/pyencrypt/license.py +++ b/pyencrypt/license.py @@ -12,7 +12,7 @@ DATE_FORMAT = '%Y-%m-%dT%H:%M:%S%z' MIN_DATETIME = datetime.now().astimezone() -MAX_DATETIME = datetime.max.replace(year=9998).astimezone() +MAX_DATETIME = datetime(year=2999, month=12, day=31).astimezone() def get_mac_address() -> str: diff --git a/tests/conftest.py b/tests/conftest.py index 6f15770..87ff186 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,7 +30,7 @@ def file_and_loader(request, tmp_path_factory): file_path.write_text("""\ def {function_name}(): {code} - """.format(function_name=function_name, code=code)) + """.format(function_name=function_name, code=code), encoding='utf-8') # generate loader.so key = generate_aes_key() new_path = file_path.with_suffix('.pye') @@ -74,7 +74,7 @@ def package_and_loader(request, tmp_path_factory): file_path.write_text("""\ def {function_name}(): {code} - """.format(function_name=function_name, code=code)) + """.format(function_name=function_name, code=code), encoding='utf-8') new_path = file_path.with_suffix('.pye') key = generate_aes_key() diff --git a/tests/test_encrypt.py b/tests/test_encrypt.py index f5f7736..fe20324 100644 --- a/tests/test_encrypt.py +++ b/tests/test_encrypt.py @@ -1,6 +1,7 @@ import os from pathlib import Path import shutil +import sys import pytest from pyencrypt.encrypt import can_encrypt, encrypt_file, encrypt_key, generate_so_file @@ -47,7 +48,10 @@ def test_generate_so_file(self, key, tmp_path): assert generate_so_file(cipher_key, d, n, tmp_path) assert (tmp_path / 'encrypted' / 'loader.py').exists() is True assert (tmp_path / 'encrypted' / 'loader_origin.py').exists() is True - assert list((tmp_path / 'encrypted').glob('loader.cpython-*-*.so')) != [] + if sys.platform.startswith('win'): + assert next((tmp_path / 'encrypted').glob('loader.cp*-*.pyd'), None) is not None + else: + assert next((tmp_path / 'encrypted').glob('loader.cpython-*-*.so'), None) is not None @pytest.mark.parametrize('key', [ AES_KEY, @@ -58,7 +62,10 @@ def test_generate_so_file_default_path(self, key): assert generate_so_file(cipher_key, d, n) assert (Path(os.getcwd()) / 'encrypted' / 'loader.py').exists() is True assert (Path(os.getcwd()) / 'encrypted' / 'loader_origin.py').exists() is True - assert list((Path(os.getcwd()) / 'encrypted').glob('loader.cpython-*-*.so')) != [] + if sys.platform.startswith('win'): + assert next((Path(os.getcwd()) / 'encrypted').glob('loader.cp*-*.pyd'), None) is not None + else: + assert next((Path(os.getcwd()) / 'encrypted').glob('loader.cpython-*-*.so'), None) is not None @pytest.mark.parametrize( diff --git a/tests/test_license.py b/tests/test_license.py index 2ecd133..df5db90 100644 --- a/tests/test_license.py +++ b/tests/test_license.py @@ -81,7 +81,7 @@ def test_check_license_invalid(self, key, tmp_path): license_file_path = generate_license_file(key, path=tmp_path) license_data = json.loads(license_file_path.read_text()) license_data['signature'] = 'invalid' - license_file_path.write_text(json.dumps(license_data)) + license_file_path.write_text(json.dumps(license_data), encoding='utf-8') with pytest.raises(Exception) as excinfo: check_license(license_file_path, key) assert str(excinfo.value) == 'License signature is invalid.'