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

Support Windows #31

Merged
merged 9 commits into from
Mar 26, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 10 additions & 17 deletions pyencrypt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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')
Expand All @@ -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"""
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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')
Expand All @@ -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')
Expand Down
34 changes: 26 additions & 8 deletions pyencrypt/encrypt.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import sys
import re
from pathlib import Path
from typing import Optional
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion pyencrypt/license.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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()
Expand Down
11 changes: 9 additions & 2 deletions tests/test_encrypt.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_license.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
Expand Down