基於Cython和內置distutils庫,實現python源碼加密(非混淆模式)

原由

python自己只能作混淆,不能加密,多年的商業軟件開發致使有某種「潔癖」:欲將py編譯打包python

嘗試

  • pyinstaller原理是freeze打包pyc文件,利用工具可完美逆行出源碼
  • 各類混淆腳本,版本兼容不好,配置繁瑣
  • cython 常規使用只能編譯單個特殊模塊

解決

反覆嘗試摸索後,仍是利用了cython和distutils庫,自動化識別並轉換py到c源碼並編譯,放出源碼供你們參考app

""" 
利用cython和distutils編譯py到pyd[so] 注意安裝cython及本地平臺對應編譯器
http://flywuya.cnblogs.com/
 """
import os
import shutil
from distutils.core import setup
from distutils.command.build_ext import build_ext
from Cython.Build import cythonize

BUILD_CONFIG = {
    'SupportExt': ['.py', '.pyx'],
    'CopyOnlyFile': ['__main__.py', '__init__.py'],
    'CopyOnlyDir': ['assets'],
    'IgnoreDir': ['dist', 'build', '__pycache__'],
}


def copy_tree(src, dst):
    """ not like shutil.copytree, dst can be exists  """
    assert os.path.exists(src)
    assert os.path.isdir(src)
    os.makedirs(dst, exist_ok=True)

    for fn in os.listdir(src):
        s = os.path.join(src, fn)
        t = os.path.join(dst, fn)
        if os.path.isfile(s):
            shutil.copy2(s, t)
        elif os.path.isdir(s):
            copy_tree(s, t)


def build_module(source_file, dst_dir, tmp_dir):
    """ cythonize && build ext """
    assert os.path.isfile(source_file)
    assert not os.path.isabs(source_file)
    assert os.path.exists(dst_dir)
    os.makedirs(tmp_dir, exist_ok=True)

    build_cython = os.path.join(tmp_dir, 'build.cython')
    build_temp = os.path.join(tmp_dir, 'build.temp')
    build_lib = dst_dir

    ext_modules = cythonize(
        source_file,
        build_dir=build_cython,
        language_level=3,
    )

    class build_here(build_ext):
        def initialize_options(self):
            super().initialize_options()
            self.build_temp = build_temp
            self.build_lib = build_lib
    setup(
        ext_modules=ext_modules,
        script_args=['build_ext'],
        cmdclass=dict(build_ext=build_here)
    )


def build_modules(source_dir, dst_dir, tmp_dir):
    """ scan && build modules in source_dir """
    assert os.path.exists(source_dir)
    assert not os.path.isabs(source_dir)
    assert not os.path.isabs(dst_dir)
    os.makedirs(dst_dir, exist_ok=True)

    for root, dirs, files in os.walk(source_dir):
        rel_pth = root[len(source_dir)+1:]

        for ignore in BUILD_CONFIG['IgnoreDir']:
            if ignore in dirs:
                dirs.remove(ignore)

        for dn in dirs:
            if dn in BUILD_CONFIG['CopyOnlyDir']:
                copy_tree(
                    os.path.join(root, dn),
                    os.path.join(dst_dir, rel_pth, dn)
                )
                dirs.remove(dn)

        for fn in files:
            _, ext = os.path.splitext(fn)
            os.makedirs(
                os.path.join(dst_dir, rel_pth),
                exist_ok=True
            )
            if fn in BUILD_CONFIG['CopyOnlyFile']:
                shutil.copy2(
                    os.path.join(root, fn),
                    os.path.join(dst_dir, rel_pth, fn)
                )
            elif ext.lower() in BUILD_CONFIG['SupportExt']:
                build_module(
                    os.path.join(root, fn),
                    dst_dir,
                    os.path.join(tmp_dir, rel_pth),
                )
            else:
                shutil.copy2(
                    os.path.join(root, fn),
                    os.path.join(dst_dir, rel_pth, fn)
                )


if __name__ == "__main__":

    # 這裏填寫要編譯的目錄
    tasks = [
        'app',
    ]

    others = [
        'requirements.txt',
        'packages',
    ]

    BUILD_CONFIG['CopyOnlyFile'].extend(['settings.py'])

    for task in tasks:
        build_modules(
            task,
            os.path.join('dist', task),
            os.path.join('build', task),
        )

    for other in others:
        if os.path.isfile(other):
            bn = os.path.basename(other)
            shutil.copy2(other, os.path.join('dist', bn))
        elif os.path.isdir(other):
            bn = os.path.basename(other)
            copy_tree(other, os.path.join('dist', bn))
相關文章
相關標籤/搜索